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