Separate out mdsal-binding-dom-codec-spi
[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 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.spi.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;
80
81 final class DataObjectStreamerGenerator<T extends DataObjectStreamer<?>> implements ClassGenerator<T> {
82     static final String INSTANCE_FIELD = "INSTANCE";
83
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);
90
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;
94
95     private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataObjectStreamer.class)
96             .modifiers(PUB_FINAL);
97
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);
103
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,
111         "endNode");
112
113     // startMapEntryNode(obj.key(), UNKNOWN_SIZE);
114     private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
115         OBJ,
116         invokeMethod(Identifiable.class, "key"),
117         UNKNOWN_SIZE,
118         invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Identifier.class, int.class));
119
120     // startUnkeyedListItem(UNKNOWN_SIZE);
121     private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
122         UNKNOWN_SIZE,
123         invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
124
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,
134         DataObject.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,
138         "streamLeafList",
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, Map.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);
151
152     // streamAugmentations(reg, stream, obj);
153     private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
154         REG,
155         STREAM,
156         OBJ,
157         invokeMethod(DataObjectStreamer.class, "streamAugmentations", DataObjectSerializerRegistry.class,
158             BindingStreamEventWriter.class, Augmentable.class));
159
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;
165
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);
173     }
174
175     static Class<? extends DataObjectStreamer<?>> generateStreamer(final CodecClassLoader loader,
176             final CodecContextFactory registry, final Class<?> type) {
177
178         final Entry<GeneratedType, WithStatus> typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
179         final WithStatus schema = typeAndSchema.getValue();
180
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);
194         } else {
195             throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
196         }
197
198         return loader.generateClass(type, "streamer",
199             new DataObjectStreamerGenerator<>(registry, typeAndSchema.getKey(), (DataNodeContainer) schema, type,
200                     startEvent));
201     }
202
203     @Override
204     public GeneratorResult<T> generateClass(final CodecClassLoader loader, final String fqcn,
205             final Class<?> bindingInterface) {
206         LOG.trace("Definining streamer {}", fqcn);
207
208         @SuppressWarnings("unchecked")
209         Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
210
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);
216                 final Method getter;
217                 try {
218                     getter = type.getMethod(getterName);
219                 } catch (NoSuchMethodException e) {
220                     throw new IllegalStateException("Failed to find getter " + getterName, e);
221                 }
222
223                 final ChildStream child = createStream(loader, props, schemaChild, getter);
224                 if (child != null) {
225                     children.add(child);
226                 }
227             }
228         }
229
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);
235             }
236         }
237
238         final GeneratorResult<T> result = GeneratorResult.of(builder
239             .defineMethod("serialize", BB_VOID, PUB_FINAL)
240                 .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
241                 .throwing(BB_IOX)
242             .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
243
244         LOG.trace("Definition of {} done", fqcn);
245         return result;
246     }
247
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);
252         }
253         if (childSchema instanceof ContainerSchemaNode) {
254             return containerChildStream(getter);
255         }
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[] params = ((ParameterizedType) childType).getActualTypeArguments();
261             final ListSchemaNode listSchema = (ListSchemaNode) childSchema;
262             final Class<?> valueClass;
263             if (!listSchema.isUserOrdered() && !listSchema.getKeyDefinition().isEmpty()) {
264                 loadTypeClass(loader, params[0]);
265                 valueClass = loadTypeClass(loader, params[1]);
266             } else {
267                 valueClass = loadTypeClass(loader, params[0]);
268             }
269
270             return listChildStream(getter, valueClass.asSubclass(DataObject.class), listSchema);
271         }
272         if (childSchema instanceof ChoiceSchemaNode) {
273             return choiceChildStream(getter);
274         }
275         if (childSchema instanceof AnydataSchemaNode) {
276             return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
277         }
278         if (childSchema instanceof AnyxmlSchemaNode) {
279             return qnameChildStream(STREAM_ANYXML, getter, childSchema);
280         }
281         if (childSchema instanceof LeafListSchemaNode) {
282             return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
283                     : STREAM_LEAF_LIST, getter, childSchema);
284         }
285
286         LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
287         return null;
288     }
289
290     private static ChildStream choiceChildStream(final Method getter) {
291         // streamChoice(Foo.class, reg, stream, obj.getFoo());
292         return new ChildStream(
293             ClassConstant.of(Sort.describe(getter.getReturnType()).asErasure()),
294             REG,
295             STREAM,
296             OBJ,
297             invokeMethod(getter),
298             STREAM_CHOICE);
299     }
300
301     private ChildStream containerChildStream(final Method getter) {
302         final Class<? extends DataObject> itemClass = getter.getReturnType().asSubclass(DataObject.class);
303         final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
304
305         // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo());
306         return new ChildStream(streamer,
307             streamerInstance(streamer),
308             REG,
309             STREAM,
310             OBJ,
311             invokeMethod(getter),
312             STREAM_CONTAINER);
313     }
314
315     private ChildStream listChildStream(final Method getter, final Class<? extends DataObject> itemClass,
316             final ListSchemaNode childSchema) {
317         final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
318         final StackManipulation method;
319         if (childSchema.getKeyDefinition().isEmpty()) {
320             method = STREAM_LIST;
321         } else {
322             method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
323         }
324
325         // <METHOD>(Foo.class, FooStreamer.INSTACE, reg, stream, obj.getFoo());
326         return new ChildStream(streamer,
327             ClassConstant.of(Sort.describe(itemClass).asErasure()),
328             streamerInstance(streamer),
329             REG,
330             STREAM,
331             OBJ,
332             invokeMethod(getter),
333             method);
334     }
335
336     private static ChildStream qnameChildStream(final StackManipulation method, final Method getter,
337             final DataSchemaNode schema) {
338         // <METHOD>(stream, "foo", obj.getFoo());
339         return new ChildStream(
340             STREAM,
341             new TextConstant(schema.getQName().getLocalName()),
342             OBJ,
343             invokeMethod(getter),
344             method);
345     }
346
347     private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
348         try {
349             return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
350         } catch (NoSuchFieldException e) {
351             throw new IllegalStateException(e);
352         }
353     }
354
355     private static StackManipulation classUnknownSizeMethod(final StackManipulation method, final Class<?> type) {
356         // <METHOD>(Foo.class, UNKNOWN_SIZE);
357         return new StackManipulation.Compound(
358                 ClassConstant.of(Sort.describe(type).asErasure()),
359                 UNKNOWN_SIZE,
360                 method);
361     }
362
363     private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
364         final Map<String, Type> props = new HashMap<>();
365         collectAllProperties(type, props);
366         return ImmutableMap.copyOf(props);
367     }
368
369     private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
370         for (final MethodSignature definition : type.getMethodDefinitions()) {
371             hashMap.put(definition.getName(), definition.getReturnType());
372         }
373         for (final Type parent : type.getImplements()) {
374             if (parent instanceof GeneratedType) {
375                 collectAllProperties((GeneratedType) parent, hashMap);
376             }
377         }
378     }
379
380     private static Class<?> loadTypeClass(final CodecClassLoader loader, final Type type) {
381         try {
382             return loader.loadClass(type.getFullyQualifiedName());
383         } catch (ClassNotFoundException e) {
384             throw new LinkageError("Failed to load " + type, e);
385         }
386     }
387
388     private static final class SerializeImplementation implements Implementation {
389         private final List<ChildStream> children;
390         private final StackManipulation startEvent;
391         private final Class<?> bindingInterface;
392
393         SerializeImplementation(final Class<?> bindingInterface, final StackManipulation startEvent,
394                 final List<ChildStream> children) {
395             this.bindingInterface = requireNonNull(bindingInterface);
396             this.startEvent = requireNonNull(startEvent);
397             this.children = requireNonNull(children);
398         }
399
400         @Override
401         public InstrumentedType prepare(final InstrumentedType instrumentedType) {
402             return instrumentedType
403                     // private static final This INSTANCE = new This();
404                     .withField(new FieldDescription.Token(INSTANCE_FIELD, PUB_CONST, 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         @Override
456         public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
457                 final MethodDescription instrumentedMethod) {
458             final TypeDescription instrumentedType = implementationContext.getInstrumentedType();
459             StackManipulation.Size operandStackSize = new StackManipulation.Compound(
460                 TypeCreation.of(instrumentedType),
461                 Duplication.SINGLE,
462                 MethodInvocation.invoke(instrumentedType.getDeclaredMethods()
463                     .filter(ElementMatchers.isDefaultConstructor()).getOnly().asDefined()),
464                 putField(instrumentedType, INSTANCE_FIELD))
465                     .apply(methodVisitor, implementationContext);
466             return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());
467         }
468     }
469 }