Do not use BindingReflections to acquire augmentations
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataObjectStreamerGenerator.java
1 /*
2  * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
9
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;
15
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;
23 import java.util.Map;
24 import java.util.Map.Entry;
25 import java.util.Set;
26 import net.bytebuddy.ByteBuddy;
27 import net.bytebuddy.description.field.FieldDescription;
28 import net.bytebuddy.description.method.MethodDescription;
29 import net.bytebuddy.description.type.TypeDefinition;
30 import net.bytebuddy.description.type.TypeDefinition.Sort;
31 import net.bytebuddy.description.type.TypeDescription;
32 import net.bytebuddy.description.type.TypeDescription.Generic;
33 import net.bytebuddy.dynamic.DynamicType.Builder;
34 import net.bytebuddy.dynamic.scaffold.InstrumentedType;
35 import net.bytebuddy.implementation.Implementation;
36 import net.bytebuddy.implementation.Implementation.Context;
37 import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
38 import net.bytebuddy.implementation.bytecode.Duplication;
39 import net.bytebuddy.implementation.bytecode.StackManipulation;
40 import net.bytebuddy.implementation.bytecode.TypeCreation;
41 import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
42 import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
43 import net.bytebuddy.implementation.bytecode.constant.TextConstant;
44 import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
45 import net.bytebuddy.implementation.bytecode.member.MethodReturn;
46 import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
47 import net.bytebuddy.jar.asm.MethodVisitor;
48 import net.bytebuddy.jar.asm.Opcodes;
49 import net.bytebuddy.matcher.ElementMatcher;
50 import net.bytebuddy.matcher.ElementMatchers;
51 import org.eclipse.jdt.annotation.Nullable;
52 import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter;
53 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
54 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
55 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator;
56 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
57 import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping;
58 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
59 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
60 import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
61 import org.opendaylight.mdsal.binding.model.api.Type;
62 import org.opendaylight.yangtools.yang.binding.Augmentable;
63 import org.opendaylight.yangtools.yang.binding.DataContainer;
64 import org.opendaylight.yangtools.yang.binding.DataObject;
65 import org.opendaylight.yangtools.yang.binding.Identifiable;
66 import org.opendaylight.yangtools.yang.binding.Identifier;
67 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
68 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
70 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
71 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
72 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
73 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
74 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
75 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
76 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
77 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
78 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
79 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
80 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
83
84 final class DataObjectStreamerGenerator<T extends DataObjectStreamer<?>> implements ClassGenerator<T> {
85     static final String INSTANCE_FIELD = "INSTANCE";
86
87     private static final Logger LOG = LoggerFactory.getLogger(DataObjectStreamerGenerator.class);
88     private static final Generic BB_VOID = TypeDefinition.Sort.describe(void.class);
89     private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class);
90     private static final Generic BB_DOSR = TypeDefinition.Sort.describe(DataObjectSerializerRegistry.class);
91     private static final Generic BB_BESV = TypeDefinition.Sort.describe(BindingStreamEventWriter.class);
92     private static final Generic BB_IOX = TypeDefinition.Sort.describe(IOException.class);
93
94     private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
95     private static final int PUB_CONST = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
96             | Opcodes.ACC_SYNTHETIC;
97
98     private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataObjectStreamer.class)
99             .modifiers(PUB_FINAL);
100
101     private static final StackManipulation REG = MethodVariableAccess.REFERENCE.loadFrom(1);
102     private static final StackManipulation OBJ = MethodVariableAccess.REFERENCE.loadFrom(2);
103     private static final StackManipulation STREAM = MethodVariableAccess.REFERENCE.loadFrom(3);
104     private static final StackManipulation UNKNOWN_SIZE = IntegerConstant.forValue(
105         BindingStreamEventWriter.UNKNOWN_SIZE);
106
107     private static final StackManipulation START_AUGMENTATION_NODE = invokeMethod(BindingStreamEventWriter.class,
108         "startAugmentationNode", Class.class);
109     private static final StackManipulation START_CASE = invokeMethod(BindingStreamEventWriter.class,
110         "startCase", Class.class, int.class);
111     private static final StackManipulation START_CONTAINER_NODE = invokeMethod(BindingStreamEventWriter.class,
112         "startContainerNode", Class.class, int.class);
113     private static final StackManipulation END_NODE = invokeMethod(BindingStreamEventWriter.class,
114         "endNode");
115
116     // startMapEntryNode(obj.key(), UNKNOWN_SIZE)
117     private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
118         OBJ,
119         invokeMethod(Identifiable.class, "key"),
120         UNKNOWN_SIZE,
121         invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Identifier.class, int.class));
122
123     // startUnkeyedListItem(UNKNOWN_SIZE)
124     private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
125         UNKNOWN_SIZE,
126         invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
127
128     private static final StackManipulation STREAM_ANYDATA = invokeMethod(DataObjectStreamer.class,
129         "streamAnydata", BindingStreamEventWriter.class, String.class, Object.class);
130     private static final StackManipulation STREAM_ANYXML = invokeMethod(DataObjectStreamer.class,
131         "streamAnyxml", BindingStreamEventWriter.class, String.class, Object.class);
132     private static final StackManipulation STREAM_CHOICE = invokeMethod(DataObjectStreamer.class,
133         "streamChoice", Class.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
134         DataContainer.class);
135     private static final StackManipulation STREAM_CONTAINER = invokeMethod(DataObjectStreamer.class,
136         "streamContainer", DataObjectStreamer.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
137         DataObject.class);
138     private static final StackManipulation STREAM_LEAF = invokeMethod(DataObjectStreamer.class,
139         "streamLeaf", BindingStreamEventWriter.class, String.class, Object.class);
140     private static final StackManipulation STREAM_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
141         "streamLeafList",
142         BindingStreamEventWriter.class, String.class, Set.class);
143     private static final StackManipulation STREAM_ORDERED_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
144         "streamOrderedLeafList", BindingStreamEventWriter.class, String.class, List.class);
145     private static final StackManipulation STREAM_LIST = invokeMethod(DataObjectStreamer.class,
146         "streamList", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
147         BindingStreamEventWriter.class, List.class);
148     private static final StackManipulation STREAM_MAP = invokeMethod(DataObjectStreamer.class,
149         "streamMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
150         BindingStreamEventWriter.class, Map.class);
151     private static final StackManipulation STREAM_ORDERED_MAP = invokeMethod(DataObjectStreamer.class,
152         "streamOrderedMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
153         BindingStreamEventWriter.class, List.class);
154
155     // streamAugmentations(reg, stream, obj)
156     private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
157         REG,
158         STREAM,
159         OBJ,
160         invokeMethod(DataObjectStreamer.class, "streamAugmentations", DataObjectSerializerRegistry.class,
161             BindingStreamEventWriter.class, Augmentable.class));
162
163     private final CodecContextFactory registry;
164     private final StackManipulation startEvent;
165     private final DataNodeContainer schema;
166     private final Class<?> type;
167     private final GeneratedType genType;
168
169     private DataObjectStreamerGenerator(final CodecContextFactory registry, final GeneratedType genType,
170             final DataNodeContainer schema, final Class<?> type, final StackManipulation startEvent) {
171         this.registry = requireNonNull(registry);
172         this.genType = requireNonNull(genType);
173         this.schema = requireNonNull(schema);
174         this.type = requireNonNull(type);
175         this.startEvent = requireNonNull(startEvent);
176     }
177
178     static Class<? extends DataObjectStreamer<?>> generateStreamer(final CodecClassLoader loader,
179             final CodecContextFactory registry, final Class<?> type) {
180
181         final Entry<GeneratedType, WithStatus> typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
182         final WithStatus schema = typeAndSchema.getValue();
183
184         final StackManipulation startEvent;
185         if (schema instanceof ContainerLike || schema instanceof NotificationDefinition) {
186             startEvent = classUnknownSizeMethod(START_CONTAINER_NODE, type);
187         } else if (schema instanceof ListSchemaNode) {
188             startEvent = ((ListSchemaNode) schema).getKeyDefinition().isEmpty() ? START_UNKEYED_LIST_ITEM
189                     : START_MAP_ENTRY_NODE;
190         } else if (schema instanceof AugmentationSchemaNode) {
191             // startAugmentationNode(Foo.class)
192             startEvent = new StackManipulation.Compound(
193                 ClassConstant.of(Sort.describe(type).asErasure()),
194                 START_AUGMENTATION_NODE);
195         } else if (schema instanceof CaseSchemaNode) {
196             startEvent = classUnknownSizeMethod(START_CASE, type);
197         } else {
198             throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
199         }
200
201         return loader.generateClass(type, "streamer",
202             new DataObjectStreamerGenerator<>(registry, typeAndSchema.getKey(), (DataNodeContainer) schema, type,
203                     startEvent));
204     }
205
206     @Override
207     public GeneratorResult<T> generateClass(final CodecClassLoader loader, final String fqcn,
208             final Class<?> bindingInterface) {
209         LOG.trace("Definining streamer {}", fqcn);
210
211         @SuppressWarnings("unchecked")
212         Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
213
214         final ImmutableMap<String, Type> props = collectAllProperties(genType);
215         final List<ChildStream> children = new ArrayList<>(props.size());
216         for (final DataSchemaNode schemaChild : schema.getChildNodes()) {
217             if (!schemaChild.isAugmenting()) {
218                 final String getterName = BindingSchemaMapping.getGetterMethodName(schemaChild);
219                 final Method getter;
220                 try {
221                     getter = type.getMethod(getterName);
222                 } catch (NoSuchMethodException e) {
223                     throw new IllegalStateException("Failed to find getter " + getterName, e);
224                 }
225
226                 final ChildStream child = createStream(loader, props, schemaChild, getter);
227                 if (child != null) {
228                     children.add(child);
229                 }
230             }
231         }
232
233         final ImmutableList.Builder<Class<?>> depBuilder = ImmutableList.builder();
234         for (ChildStream child : children) {
235             final Class<?> dependency = child.getDependency();
236             if (dependency != null) {
237                 depBuilder.add(dependency);
238             }
239         }
240
241         final GeneratorResult<T> result = GeneratorResult.of(builder
242             .defineMethod("serialize", BB_VOID, PUB_FINAL)
243                 .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
244                 .throwing(BB_IOX)
245             .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
246
247         LOG.trace("Definition of {} done", fqcn);
248         return result;
249     }
250
251     private ChildStream createStream(final CodecClassLoader loader, final ImmutableMap<String, Type> props,
252             final DataSchemaNode childSchema, final Method getter) {
253         if (childSchema instanceof LeafSchemaNode) {
254             return qnameChildStream(STREAM_LEAF, getter, childSchema);
255         }
256         if (childSchema instanceof ContainerSchemaNode) {
257             return containerChildStream(getter);
258         }
259         if (childSchema instanceof ListSchemaNode) {
260             final String getterName = getter.getName();
261             final Type childType = props.get(getterName);
262             verify(childType instanceof ParameterizedType, "Unexpected type %s for %s", childType, getterName);
263             final Type[] params = ((ParameterizedType) childType).getActualTypeArguments();
264             final ListSchemaNode listSchema = (ListSchemaNode) childSchema;
265             final Class<?> valueClass;
266             if (!listSchema.isUserOrdered() && !listSchema.getKeyDefinition().isEmpty()) {
267                 loadTypeClass(loader, params[0]);
268                 valueClass = loadTypeClass(loader, params[1]);
269             } else {
270                 valueClass = loadTypeClass(loader, params[0]);
271             }
272
273             return listChildStream(getter, valueClass.asSubclass(DataObject.class), listSchema);
274         }
275         if (childSchema instanceof ChoiceSchemaNode) {
276             return choiceChildStream(getter);
277         }
278         if (childSchema instanceof AnydataSchemaNode) {
279             return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
280         }
281         if (childSchema instanceof AnyxmlSchemaNode) {
282             return qnameChildStream(STREAM_ANYXML, getter, childSchema);
283         }
284         if (childSchema instanceof LeafListSchemaNode) {
285             return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
286                     : STREAM_LEAF_LIST, getter, childSchema);
287         }
288
289         LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
290         return null;
291     }
292
293     private static ChildStream choiceChildStream(final Method getter) {
294         // streamChoice(Foo.class, reg, stream, obj.getFoo())
295         return new ChildStream(
296             ClassConstant.of(Sort.describe(getter.getReturnType()).asErasure()),
297             REG,
298             STREAM,
299             OBJ,
300             invokeMethod(getter),
301             STREAM_CHOICE);
302     }
303
304     private ChildStream containerChildStream(final Method getter) {
305         final Class<? extends DataObject> itemClass = getter.getReturnType().asSubclass(DataObject.class);
306         final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
307
308         // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo())
309         return new ChildStream(streamer,
310             streamerInstance(streamer),
311             REG,
312             STREAM,
313             OBJ,
314             invokeMethod(getter),
315             STREAM_CONTAINER);
316     }
317
318     private ChildStream listChildStream(final Method getter, final Class<? extends DataObject> itemClass,
319             final ListSchemaNode childSchema) {
320         final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
321         final StackManipulation method;
322         if (childSchema.getKeyDefinition().isEmpty()) {
323             method = STREAM_LIST;
324         } else {
325             method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
326         }
327
328         // <METHOD>(Foo.class, FooStreamer.INSTACE, reg, stream, obj.getFoo())
329         return new ChildStream(streamer,
330             ClassConstant.of(Sort.describe(itemClass).asErasure()),
331             streamerInstance(streamer),
332             REG,
333             STREAM,
334             OBJ,
335             invokeMethod(getter),
336             method);
337     }
338
339     private static ChildStream qnameChildStream(final StackManipulation method, final Method getter,
340             final DataSchemaNode schema) {
341         // <METHOD>(stream, "foo", obj.getFoo())
342         return new ChildStream(
343             STREAM,
344             new TextConstant(schema.getQName().getLocalName()),
345             OBJ,
346             invokeMethod(getter),
347             method);
348     }
349
350     private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
351         try {
352             return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
353         } catch (NoSuchFieldException e) {
354             throw new IllegalStateException(e);
355         }
356     }
357
358     private static StackManipulation classUnknownSizeMethod(final StackManipulation method, final Class<?> type) {
359         // <METHOD>(Foo.class, UNKNOWN_SIZE)
360         return new StackManipulation.Compound(
361                 ClassConstant.of(Sort.describe(type).asErasure()),
362                 UNKNOWN_SIZE,
363                 method);
364     }
365
366     private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
367         final Map<String, Type> props = new HashMap<>();
368         collectAllProperties(type, props);
369         return ImmutableMap.copyOf(props);
370     }
371
372     private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
373         for (final MethodSignature definition : type.getMethodDefinitions()) {
374             hashMap.put(definition.getName(), definition.getReturnType());
375         }
376         for (final Type parent : type.getImplements()) {
377             if (parent instanceof GeneratedType) {
378                 collectAllProperties((GeneratedType) parent, hashMap);
379             }
380         }
381     }
382
383     private static Class<?> loadTypeClass(final CodecClassLoader loader, final Type type) {
384         try {
385             return loader.loadClass(type.getFullyQualifiedName());
386         } catch (ClassNotFoundException e) {
387             throw new LinkageError("Failed to load " + type, e);
388         }
389     }
390
391     private static final class SerializeImplementation implements Implementation {
392         private final List<ChildStream> children;
393         private final StackManipulation startEvent;
394         private final Class<?> bindingInterface;
395
396         SerializeImplementation(final Class<?> bindingInterface, final StackManipulation startEvent,
397                 final List<ChildStream> children) {
398             this.bindingInterface = requireNonNull(bindingInterface);
399             this.startEvent = requireNonNull(startEvent);
400             this.children = requireNonNull(children);
401         }
402
403         @Override
404         public InstrumentedType prepare(final InstrumentedType instrumentedType) {
405             return instrumentedType
406                     // private static final This INSTANCE = new This()
407                     .withField(new FieldDescription.Token(INSTANCE_FIELD, PUB_CONST, instrumentedType.asGenericType()))
408                     .withInitializer(InitializeInstanceField.INSTANCE);
409         }
410
411         @Override
412         public ByteCodeAppender appender(final Target implementationTarget) {
413             final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
414
415             // stream.<START_EVENT>(...)
416             manipulations.add(STREAM);
417             manipulations.add(startEvent);
418
419             // ... emit children ...
420             manipulations.addAll(children);
421
422             if (Augmentable.class.isAssignableFrom(bindingInterface)) {
423                 // streamAugmentations(reg, stream, obj)
424                 manipulations.add(STREAM_AUGMENTATIONS);
425             }
426
427             // stream.endNode()
428             manipulations.add(STREAM);
429             manipulations.add(END_NODE);
430             // return
431             manipulations.add(MethodReturn.VOID);
432
433             return new ByteCodeAppender.Simple(manipulations);
434         }
435     }
436
437     private static final class ChildStream extends StackManipulation.Compound {
438         private final @Nullable Class<?> dependency;
439
440         ChildStream(final StackManipulation... stackManipulation) {
441             super(stackManipulation);
442             dependency = null;
443         }
444
445         ChildStream(final DataObjectStreamer<?> streamer, final StackManipulation... stackManipulation) {
446             super(stackManipulation);
447             dependency = streamer.getClass();
448         }
449
450         @Nullable Class<?> getDependency() {
451             return dependency;
452         }
453     }
454
455     private enum InitializeInstanceField implements ByteCodeAppender {
456         INSTANCE;
457
458         // TODO: eliminate this constant when ElementMatchers.isDefaultConstructor() returns a singleton
459         private static final ElementMatcher<MethodDescription> IS_DEFAULT_CONSTRUCTOR =
460             ElementMatchers.isDefaultConstructor();
461
462         @Override
463         public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
464                 final MethodDescription instrumentedMethod) {
465             final TypeDescription instrumentedType = implementationContext.getInstrumentedType();
466             StackManipulation.Size operandStackSize = new StackManipulation.Compound(
467                 TypeCreation.of(instrumentedType),
468                 Duplication.SINGLE,
469                 MethodInvocation.invoke(instrumentedType.getDeclaredMethods()
470                     .filter(IS_DEFAULT_CONSTRUCTOR).getOnly().asDefined()),
471                 putField(instrumentedType, INSTANCE_FIELD))
472                     .apply(methodVisitor, implementationContext);
473             return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());
474         }
475     }
476 }