Split out CodecContextFactory
[yangtools.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.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.ContainerLike;
71 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
72 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
73 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
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, Set.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 BindingClassLoader loader,
176             final CodecContextFactory registry, final Class<?> type) {
177
178         final var typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
179         final var schema = typeAndSchema.statement();
180
181         final StackManipulation startEvent;
182         if (schema instanceof ContainerLike || 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 CodecPackage.STREAMER.generateClass(loader, type,
199             // FIXME: cast to GeneratedType: we really should adjust getTypeWithSchema()
200             new DataObjectStreamerGenerator<>(registry, (GeneratedType) typeAndSchema.javaType(),
201                 (DataNodeContainer) schema, type, startEvent));
202     }
203
204     @Override
205     public GeneratorResult<T> generateClass(final BindingClassLoader loader, final String fqcn,
206             final Class<?> bindingInterface) {
207         LOG.trace("Definining streamer {}", fqcn);
208
209         @SuppressWarnings("unchecked")
210         Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
211
212         final ImmutableMap<String, Type> props = collectAllProperties(genType);
213         final List<ChildStream> children = new ArrayList<>(props.size());
214         for (final DataSchemaNode schemaChild : schema.getChildNodes()) {
215             if (!schemaChild.isAugmenting()) {
216                 final String getterName = BindingSchemaMapping.getGetterMethodName(schemaChild);
217                 final Method getter;
218                 try {
219                     getter = type.getMethod(getterName);
220                 } catch (NoSuchMethodException e) {
221                     throw new IllegalStateException("Failed to find getter " + getterName, e);
222                 }
223
224                 final ChildStream child = createStream(loader, props, schemaChild, getter);
225                 if (child != null) {
226                     children.add(child);
227                 }
228             }
229         }
230
231         final ImmutableList.Builder<Class<?>> depBuilder = ImmutableList.builder();
232         for (ChildStream child : children) {
233             final Class<?> dependency = child.getDependency();
234             if (dependency != null) {
235                 depBuilder.add(dependency);
236             }
237         }
238
239         final GeneratorResult<T> result = GeneratorResult.of(builder
240             .defineMethod("serialize", BB_VOID, PUB_FINAL)
241                 .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
242                 .throwing(BB_IOX)
243             .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
244
245         LOG.trace("Definition of {} done", fqcn);
246         return result;
247     }
248
249     private ChildStream createStream(final BindingClassLoader loader, final ImmutableMap<String, Type> props,
250             final DataSchemaNode childSchema, final Method getter) {
251         if (childSchema instanceof LeafSchemaNode) {
252             return qnameChildStream(STREAM_LEAF, getter, childSchema);
253         }
254         if (childSchema instanceof ContainerSchemaNode) {
255             return containerChildStream(getter);
256         }
257         if (childSchema instanceof ListSchemaNode) {
258             final String getterName = getter.getName();
259             final Type childType = props.get(getterName);
260             verify(childType instanceof ParameterizedType, "Unexpected type %s for %s", childType, getterName);
261             final Type[] params = ((ParameterizedType) childType).getActualTypeArguments();
262             final ListSchemaNode listSchema = (ListSchemaNode) childSchema;
263             final Class<?> valueClass;
264             if (!listSchema.isUserOrdered() && !listSchema.getKeyDefinition().isEmpty()) {
265                 loadTypeClass(loader, params[0]);
266                 valueClass = loadTypeClass(loader, params[1]);
267             } else {
268                 valueClass = loadTypeClass(loader, params[0]);
269             }
270
271             return listChildStream(getter, valueClass.asSubclass(DataObject.class), listSchema);
272         }
273         if (childSchema instanceof ChoiceSchemaNode) {
274             return choiceChildStream(getter);
275         }
276         if (childSchema instanceof AnydataSchemaNode) {
277             return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
278         }
279         if (childSchema instanceof AnyxmlSchemaNode) {
280             return qnameChildStream(STREAM_ANYXML, getter, childSchema);
281         }
282         if (childSchema instanceof LeafListSchemaNode) {
283             return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
284                     : STREAM_LEAF_LIST, getter, childSchema);
285         }
286
287         LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
288         return null;
289     }
290
291     private static ChildStream choiceChildStream(final Method getter) {
292         // streamChoice(Foo.class, reg, stream, obj.getFoo())
293         return new ChildStream(
294             ClassConstant.of(Sort.describe(getter.getReturnType()).asErasure()),
295             REG,
296             STREAM,
297             OBJ,
298             invokeMethod(getter),
299             STREAM_CHOICE);
300     }
301
302     private ChildStream containerChildStream(final Method getter) {
303         final Class<? extends DataObject> itemClass = getter.getReturnType().asSubclass(DataObject.class);
304         final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
305
306         // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo())
307         return new ChildStream(streamer,
308             streamerInstance(streamer),
309             REG,
310             STREAM,
311             OBJ,
312             invokeMethod(getter),
313             STREAM_CONTAINER);
314     }
315
316     private ChildStream listChildStream(final Method getter, final Class<? extends DataObject> itemClass,
317             final ListSchemaNode childSchema) {
318         final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
319         final StackManipulation method;
320         if (childSchema.getKeyDefinition().isEmpty()) {
321             method = STREAM_LIST;
322         } else {
323             method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
324         }
325
326         // <METHOD>(Foo.class, FooStreamer.INSTACE, reg, stream, obj.getFoo())
327         return new ChildStream(streamer,
328             ClassConstant.of(Sort.describe(itemClass).asErasure()),
329             streamerInstance(streamer),
330             REG,
331             STREAM,
332             OBJ,
333             invokeMethod(getter),
334             method);
335     }
336
337     private static ChildStream qnameChildStream(final StackManipulation method, final Method getter,
338             final DataSchemaNode schema) {
339         // <METHOD>(stream, "foo", obj.getFoo())
340         return new ChildStream(
341             STREAM,
342             new TextConstant(schema.getQName().getLocalName()),
343             OBJ,
344             invokeMethod(getter),
345             method);
346     }
347
348     private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
349         try {
350             return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
351         } catch (NoSuchFieldException e) {
352             throw new IllegalStateException(e);
353         }
354     }
355
356     private static StackManipulation classUnknownSizeMethod(final StackManipulation method, final Class<?> type) {
357         // <METHOD>(Foo.class, UNKNOWN_SIZE)
358         return new StackManipulation.Compound(
359                 ClassConstant.of(Sort.describe(type).asErasure()),
360                 UNKNOWN_SIZE,
361                 method);
362     }
363
364     private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
365         final Map<String, Type> props = new HashMap<>();
366         collectAllProperties(type, props);
367         return ImmutableMap.copyOf(props);
368     }
369
370     private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
371         for (final MethodSignature definition : type.getMethodDefinitions()) {
372             hashMap.put(definition.getName(), definition.getReturnType());
373         }
374         for (final Type parent : type.getImplements()) {
375             if (parent instanceof GeneratedType) {
376                 collectAllProperties((GeneratedType) parent, hashMap);
377             }
378         }
379     }
380
381     private static Class<?> loadTypeClass(final BindingClassLoader loader, final Type type) {
382         try {
383             return loader.loadClass(type.getFullyQualifiedName());
384         } catch (ClassNotFoundException e) {
385             throw new LinkageError("Failed to load " + type, e);
386         }
387     }
388
389     private static final class SerializeImplementation implements Implementation {
390         private final List<ChildStream> children;
391         private final StackManipulation startEvent;
392         private final Class<?> bindingInterface;
393
394         SerializeImplementation(final Class<?> bindingInterface, final StackManipulation startEvent,
395                 final List<ChildStream> children) {
396             this.bindingInterface = requireNonNull(bindingInterface);
397             this.startEvent = requireNonNull(startEvent);
398             this.children = requireNonNull(children);
399         }
400
401         @Override
402         public InstrumentedType prepare(final InstrumentedType instrumentedType) {
403             return instrumentedType
404                     // private static final This INSTANCE = new This()
405                     .withField(new FieldDescription.Token(INSTANCE_FIELD, PUB_CONST, instrumentedType.asGenericType()))
406                     .withInitializer(InitializeInstanceField.INSTANCE);
407         }
408
409         @Override
410         public ByteCodeAppender appender(final Target implementationTarget) {
411             final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
412
413             // stream.<START_EVENT>(...)
414             manipulations.add(STREAM);
415             manipulations.add(startEvent);
416
417             // ... emit children ...
418             manipulations.addAll(children);
419
420             if (Augmentable.class.isAssignableFrom(bindingInterface)) {
421                 // streamAugmentations(reg, stream, obj)
422                 manipulations.add(STREAM_AUGMENTATIONS);
423             }
424
425             // stream.endNode()
426             manipulations.add(STREAM);
427             manipulations.add(END_NODE);
428             // return
429             manipulations.add(MethodReturn.VOID);
430
431             return new ByteCodeAppender.Simple(manipulations);
432         }
433     }
434
435     private static final class ChildStream extends StackManipulation.Compound {
436         private final @Nullable Class<?> dependency;
437
438         ChildStream(final StackManipulation... stackManipulation) {
439             super(stackManipulation);
440             dependency = null;
441         }
442
443         ChildStream(final DataObjectStreamer<?> streamer, final StackManipulation... stackManipulation) {
444             super(stackManipulation);
445             dependency = streamer.getClass();
446         }
447
448         @Nullable Class<?> getDependency() {
449             return dependency;
450         }
451     }
452
453     private enum InitializeInstanceField implements ByteCodeAppender {
454         INSTANCE;
455
456         // TODO: eliminate this constant when ElementMatchers.isDefaultConstructor() returns a singleton
457         private static final ElementMatcher<MethodDescription> IS_DEFAULT_CONSTRUCTOR =
458             ElementMatchers.isDefaultConstructor();
459
460         @Override
461         public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
462                 final MethodDescription instrumentedMethod) {
463             final TypeDescription instrumentedType = implementationContext.getInstrumentedType();
464             StackManipulation.Size operandStackSize = new StackManipulation.Compound(
465                 TypeCreation.of(instrumentedType),
466                 Duplication.SINGLE,
467                 MethodInvocation.invoke(instrumentedType.getDeclaredMethods()
468                     .filter(IS_DEFAULT_CONSTRUCTOR).getOnly().asDefined()),
469                 putField(instrumentedType, INSTANCE_FIELD))
470                     .apply(methodVisitor, implementationContext);
471             return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());
472         }
473     }
474 }