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