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