2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
10 import static com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
12 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.getField;
13 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.invokeMethod;
14 import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.putField;
16 import com.google.common.collect.ImmutableList;
17 import com.google.common.collect.ImmutableMap;
18 import java.io.IOException;
19 import java.lang.reflect.Method;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.List;
24 import java.util.Map.Entry;
25 import net.bytebuddy.ByteBuddy;
26 import net.bytebuddy.description.field.FieldDescription;
27 import net.bytebuddy.description.method.MethodDescription;
28 import net.bytebuddy.description.type.TypeDefinition;
29 import net.bytebuddy.description.type.TypeDefinition.Sort;
30 import net.bytebuddy.description.type.TypeDescription;
31 import net.bytebuddy.description.type.TypeDescription.Generic;
32 import net.bytebuddy.dynamic.DynamicType.Builder;
33 import net.bytebuddy.dynamic.scaffold.InstrumentedType;
34 import net.bytebuddy.implementation.Implementation;
35 import net.bytebuddy.implementation.Implementation.Context;
36 import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
37 import net.bytebuddy.implementation.bytecode.Duplication;
38 import net.bytebuddy.implementation.bytecode.StackManipulation;
39 import net.bytebuddy.implementation.bytecode.TypeCreation;
40 import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
41 import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
42 import net.bytebuddy.implementation.bytecode.constant.TextConstant;
43 import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
44 import net.bytebuddy.implementation.bytecode.member.MethodReturn;
45 import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
46 import net.bytebuddy.jar.asm.MethodVisitor;
47 import net.bytebuddy.jar.asm.Opcodes;
48 import net.bytebuddy.matcher.ElementMatchers;
49 import org.eclipse.jdt.annotation.Nullable;
50 import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter;
51 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
52 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
53 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator;
54 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
55 import org.opendaylight.mdsal.binding.dom.codec.util.BindingSchemaMapping;
56 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
57 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
58 import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
59 import org.opendaylight.mdsal.binding.model.api.Type;
60 import org.opendaylight.yangtools.yang.binding.Augmentable;
61 import org.opendaylight.yangtools.yang.binding.DataContainer;
62 import org.opendaylight.yangtools.yang.binding.DataObject;
63 import org.opendaylight.yangtools.yang.binding.Identifiable;
64 import org.opendaylight.yangtools.yang.binding.Identifier;
65 import org.opendaylight.yangtools.yang.model.api.AnyDataSchemaNode;
66 import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
67 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
68 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
70 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
71 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
72 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
73 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
74 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
75 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
76 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
77 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
81 final class DataObjectStreamerGenerator<T extends DataObjectStreamer<?>> implements ClassGenerator<T> {
82 static final String INSTANCE_FIELD = "INSTANCE";
84 private static final Logger LOG = LoggerFactory.getLogger(DataObjectStreamerGenerator.class);
85 private static final Generic BB_VOID = TypeDefinition.Sort.describe(void.class);
86 private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class);
87 private static final Generic BB_DOSR = TypeDefinition.Sort.describe(DataObjectSerializerRegistry.class);
88 private static final Generic BB_BESV = TypeDefinition.Sort.describe(BindingStreamEventWriter.class);
89 private static final Generic BB_IOX = TypeDefinition.Sort.describe(IOException.class);
91 private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
92 private static final int PUB_CONST = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
93 | Opcodes.ACC_SYNTHETIC;
95 private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataObjectStreamer.class)
96 .modifiers(PUB_FINAL);
98 private static final StackManipulation REG = MethodVariableAccess.REFERENCE.loadFrom(1);
99 private static final StackManipulation OBJ = MethodVariableAccess.REFERENCE.loadFrom(2);
100 private static final StackManipulation STREAM = MethodVariableAccess.REFERENCE.loadFrom(3);
101 private static final StackManipulation UNKNOWN_SIZE = IntegerConstant.forValue(
102 BindingStreamEventWriter.UNKNOWN_SIZE);
104 private static final StackManipulation START_AUGMENTATION_NODE = invokeMethod(BindingStreamEventWriter.class,
105 "startAugmentationNode", Class.class);
106 private static final StackManipulation START_CASE = invokeMethod(BindingStreamEventWriter.class,
107 "startCase", Class.class, int.class);
108 private static final StackManipulation START_CONTAINER_NODE = invokeMethod(BindingStreamEventWriter.class,
109 "startContainerNode", Class.class, int.class);
110 private static final StackManipulation END_NODE = invokeMethod(BindingStreamEventWriter.class,
113 // startMapEntryNode(obj.key(), UNKNOWN_SIZE);
114 private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
116 invokeMethod(Identifiable.class, "key"),
118 invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Identifier.class, int.class));
120 // startUnkeyedListItem(UNKNOWN_SIZE);
121 private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
123 invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
125 private static final StackManipulation STREAM_ANYDATA = invokeMethod(DataObjectStreamer.class,
126 "streamAnydata", BindingStreamEventWriter.class, String.class, Object.class);
127 private static final StackManipulation STREAM_ANYXML = invokeMethod(DataObjectStreamer.class,
128 "streamAnyxml", BindingStreamEventWriter.class, String.class, Object.class);
129 private static final StackManipulation STREAM_CHOICE = invokeMethod(DataObjectStreamer.class,
130 "streamChoice", Class.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
131 DataContainer.class);
132 private static final StackManipulation STREAM_CONTAINER = invokeMethod(DataObjectStreamer.class,
133 "streamContainer", DataObjectStreamer.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
135 private static final StackManipulation STREAM_LEAF = invokeMethod(DataObjectStreamer.class,
136 "streamLeaf", BindingStreamEventWriter.class, String.class, Object.class);
137 private static final StackManipulation STREAM_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
139 BindingStreamEventWriter.class, String.class, List.class);
140 private static final StackManipulation STREAM_ORDERED_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
141 "streamOrderedLeafList", BindingStreamEventWriter.class, String.class, List.class);
142 private static final StackManipulation STREAM_LIST = invokeMethod(DataObjectStreamer.class,
143 "streamList", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
144 BindingStreamEventWriter.class, List.class);
145 private static final StackManipulation STREAM_MAP = invokeMethod(DataObjectStreamer.class,
146 "streamMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
147 BindingStreamEventWriter.class, List.class);
148 private static final StackManipulation STREAM_ORDERED_MAP = invokeMethod(DataObjectStreamer.class,
149 "streamOrderedMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
150 BindingStreamEventWriter.class, List.class);
152 // streamAugmentations(reg, stream, obj);
153 private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
157 invokeMethod(DataObjectStreamer.class, "streamAugmentations", DataObjectSerializerRegistry.class,
158 BindingStreamEventWriter.class, Augmentable.class));
160 private final CodecContextFactory registry;
161 private final StackManipulation startEvent;
162 private final DataNodeContainer schema;
163 private final Class<?> type;
164 private final GeneratedType genType;
166 private DataObjectStreamerGenerator(final CodecContextFactory registry, final GeneratedType genType,
167 final DataNodeContainer schema, final Class<?> type, final StackManipulation startEvent) {
168 this.registry = requireNonNull(registry);
169 this.genType = requireNonNull(genType);
170 this.schema = requireNonNull(schema);
171 this.type = requireNonNull(type);
172 this.startEvent = requireNonNull(startEvent);
175 static Class<? extends DataObjectStreamer<?>> generateStreamer(final CodecClassLoader loader,
176 final CodecContextFactory registry, final Class<?> type) {
178 final Entry<GeneratedType, WithStatus> typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
179 final WithStatus schema = typeAndSchema.getValue();
181 final StackManipulation startEvent;
182 if (schema instanceof ContainerSchemaNode || schema instanceof NotificationDefinition) {
183 startEvent = classUnknownSizeMethod(START_CONTAINER_NODE, type);
184 } else if (schema instanceof ListSchemaNode) {
185 startEvent = ((ListSchemaNode) schema).getKeyDefinition().isEmpty() ? START_UNKEYED_LIST_ITEM
186 : START_MAP_ENTRY_NODE;
187 } else if (schema instanceof AugmentationSchemaNode) {
188 // startAugmentationNode(Foo.class);
189 startEvent = new StackManipulation.Compound(
190 ClassConstant.of(Sort.describe(type).asErasure()),
191 START_AUGMENTATION_NODE);
192 } else if (schema instanceof CaseSchemaNode) {
193 startEvent = classUnknownSizeMethod(START_CASE, type);
195 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
198 return loader.generateClass(type, "streamer",
199 new DataObjectStreamerGenerator<>(registry, typeAndSchema.getKey(), (DataNodeContainer) schema, type,
204 public GeneratorResult<T> generateClass(final CodecClassLoader loader, final String fqcn,
205 final Class<?> bindingInterface) {
206 LOG.trace("Definining streamer {}", fqcn);
208 @SuppressWarnings("unchecked")
209 Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
211 final ImmutableMap<String, Type> props = collectAllProperties(genType);
212 final List<ChildStream> children = new ArrayList<>(props.size());
213 for (final DataSchemaNode schemaChild : schema.getChildNodes()) {
214 if (!schemaChild.isAugmenting()) {
215 final String getterName = BindingSchemaMapping.getGetterMethodName(schemaChild);
218 getter = type.getMethod(getterName);
219 } catch (NoSuchMethodException e) {
220 throw new IllegalStateException("Failed to find getter " + getterName, e);
223 final ChildStream child = createStream(loader, props, schemaChild, getter);
230 final ImmutableList.Builder<Class<?>> depBuilder = ImmutableList.builder();
231 for (ChildStream child : children) {
232 final Class<?> dependency = child.getDependency();
233 if (dependency != null) {
234 depBuilder.add(dependency);
238 final GeneratorResult<T> result = GeneratorResult.of(builder
239 .defineMethod("serialize", BB_VOID, PUB_FINAL)
240 .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
242 .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
244 LOG.trace("Definition of {} done", fqcn);
248 private ChildStream createStream(final CodecClassLoader loader, final ImmutableMap<String, Type> props,
249 final DataSchemaNode childSchema, final Method getter) {
250 if (childSchema instanceof LeafSchemaNode) {
251 return qnameChildStream(STREAM_LEAF, getter, childSchema);
253 if (childSchema instanceof ContainerSchemaNode) {
254 return containerChildStream(getter);
256 if (childSchema instanceof ListSchemaNode) {
257 final String getterName = getter.getName();
258 final Type childType = props.get(getterName);
259 verify(childType instanceof ParameterizedType, "Unexpected type %s for %s", childType, getterName);
260 final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
261 final Class<?> valueClass;
263 valueClass = loader.loadClass(valueType.getFullyQualifiedName());
264 } catch (ClassNotFoundException e) {
265 throw new LinkageError("Failed to load " + valueType, e);
267 return listChildStream(getter, valueClass.asSubclass(DataObject.class), (ListSchemaNode) childSchema);
269 if (childSchema instanceof ChoiceSchemaNode) {
270 return choiceChildStream(getter);
272 if (childSchema instanceof AnyDataSchemaNode) {
273 return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
275 if (childSchema instanceof AnyXmlSchemaNode) {
276 return qnameChildStream(STREAM_ANYXML, getter, childSchema);
278 if (childSchema instanceof LeafListSchemaNode) {
279 return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
280 : STREAM_LEAF_LIST, getter, childSchema);
283 LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
287 private static ChildStream choiceChildStream(final Method getter) {
288 // streamChoice(Foo.class, reg, stream, obj.getFoo());
289 return new ChildStream(
290 ClassConstant.of(Sort.describe(getter.getReturnType()).asErasure()),
294 invokeMethod(getter),
298 private ChildStream containerChildStream(final Method getter) {
299 final Class<? extends DataObject> itemClass = getter.getReturnType().asSubclass(DataObject.class);
300 final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
302 // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo());
303 return new ChildStream(streamer,
304 streamerInstance(streamer),
308 invokeMethod(getter),
312 private ChildStream listChildStream(final Method getter, final Class<? extends DataObject> itemClass,
313 final ListSchemaNode childSchema) {
314 final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
315 final StackManipulation method;
316 if (childSchema.getKeyDefinition().isEmpty()) {
317 method = STREAM_LIST;
319 method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
322 // <METHOD>(Foo.class, FooStreamer.INSTACE, reg, stream, obj.getFoo());
323 return new ChildStream(streamer,
324 ClassConstant.of(Sort.describe(itemClass).asErasure()),
325 streamerInstance(streamer),
329 invokeMethod(getter),
333 private static ChildStream qnameChildStream(final StackManipulation method, final Method getter,
334 final DataSchemaNode schema) {
335 // <METHOD>(stream, "foo", obj.getFoo());
336 return new ChildStream(
338 new TextConstant(schema.getQName().getLocalName()),
340 invokeMethod(getter),
344 private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
346 return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
347 } catch (NoSuchFieldException e) {
348 throw new IllegalStateException(e);
352 private static StackManipulation classUnknownSizeMethod(final StackManipulation method, final Class<?> type) {
353 // <METHOD>(Foo.class, UNKNOWN_SIZE);
354 return new StackManipulation.Compound(
355 ClassConstant.of(Sort.describe(type).asErasure()),
360 private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
361 final Map<String, Type> props = new HashMap<>();
362 collectAllProperties(type, props);
363 return ImmutableMap.copyOf(props);
366 private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
367 for (final MethodSignature definition : type.getMethodDefinitions()) {
368 hashMap.put(definition.getName(), definition.getReturnType());
370 for (final Type parent : type.getImplements()) {
371 if (parent instanceof GeneratedType) {
372 collectAllProperties((GeneratedType) parent, hashMap);
377 private static final class SerializeImplementation implements Implementation {
378 private final List<ChildStream> children;
379 private final StackManipulation startEvent;
380 private final Class<?> bindingInterface;
382 SerializeImplementation(final Class<?> bindingInterface, final StackManipulation startEvent,
383 final List<ChildStream> children) {
384 this.bindingInterface = requireNonNull(bindingInterface);
385 this.startEvent = requireNonNull(startEvent);
386 this.children = requireNonNull(children);
390 public InstrumentedType prepare(final InstrumentedType instrumentedType) {
391 return instrumentedType
392 // private static final This INSTANCE = new This();
393 .withField(new FieldDescription.Token(INSTANCE_FIELD, PUB_CONST, instrumentedType.asGenericType()))
394 .withInitializer(InitializeInstanceField.INSTANCE);
398 public ByteCodeAppender appender(final Target implementationTarget) {
399 final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
401 // stream.<START_EVENT>(...);
402 manipulations.add(STREAM);
403 manipulations.add(startEvent);
405 // ... emit children ...
406 manipulations.addAll(children);
408 if (Augmentable.class.isAssignableFrom(bindingInterface)) {
409 // streamAugmentations(reg, stream, obj);
410 manipulations.add(STREAM_AUGMENTATIONS);
414 manipulations.add(STREAM);
415 manipulations.add(END_NODE);
417 manipulations.add(MethodReturn.VOID);
419 return new ByteCodeAppender.Simple(manipulations);
423 private static final class ChildStream extends StackManipulation.Compound {
424 private final @Nullable Class<?> dependency;
426 ChildStream(final StackManipulation... stackManipulation) {
427 super(stackManipulation);
431 ChildStream(final DataObjectStreamer<?> streamer, final StackManipulation... stackManipulation) {
432 super(stackManipulation);
433 dependency = streamer.getClass();
436 @Nullable Class<?> getDependency() {
441 private enum InitializeInstanceField implements ByteCodeAppender {
445 public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
446 final MethodDescription instrumentedMethod) {
447 final TypeDescription instrumentedType = implementationContext.getInstrumentedType();
448 StackManipulation.Size operandStackSize = new StackManipulation.Compound(
449 TypeCreation.of(instrumentedType),
451 MethodInvocation.invoke(instrumentedType.getDeclaredMethods()
452 .filter(ElementMatchers.isDefaultConstructor()).getOnly().asDefined()),
453 putField(instrumentedType, INSTANCE_FIELD))
454 .apply(methodVisitor, implementationContext);
455 return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());