2 * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.dom.codec.impl;
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;
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;
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.yangtools.binding.model.api.GeneratedType;
57 import org.opendaylight.yangtools.binding.model.api.ParameterizedType;
58 import org.opendaylight.yangtools.binding.model.api.Type;
59 import org.opendaylight.yangtools.binding.lib.Augmentable;
60 import org.opendaylight.yangtools.binding.lib.DataContainer;
61 import org.opendaylight.yangtools.binding.lib.DataObject;
62 import org.opendaylight.yangtools.binding.lib.Key;
63 import org.opendaylight.yangtools.binding.lib.KeyAware;
64 import org.opendaylight.yangtools.binding.lib.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;
81 final class DataContainerStreamerGenerator<T extends DataContainerStreamer<?>> implements ClassGenerator<T> {
82 static final String INSTANCE_FIELD = "INSTANCE";
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);
91 private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataContainerStreamer.class)
92 .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC);
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);
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,
109 // startMapEntryNode(obj.key(), UNKNOWN_SIZE)
110 private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
112 invokeMethod(KeyAware.class, Naming.KEY_AWARE_KEY_NAME),
114 invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Key.class, int.class));
116 // startUnkeyedListItem(UNKNOWN_SIZE)
117 private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
119 invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
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,
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);
148 // streamAugmentations(reg, stream, obj)
149 private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
153 invokeMethod(DataContainerStreamer.class, "streamAugmentations", DataContainerSerializerRegistry.class,
154 BindingStreamEventWriter.class, Augmentable.class));
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;
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);
171 static Class<? extends DataContainerStreamer<?>> generateStreamer(final BindingClassLoader loader,
172 final CodecContextFactory registry, final Class<?> type) {
174 final var typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
175 final var schema = typeAndSchema.statement();
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);
190 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
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));
200 public GeneratorResult<T> generateClass(final BindingClassLoader loader, final String fqcn,
201 final Class<?> bindingInterface) {
202 LOG.trace("Definining streamer {}", fqcn);
204 @SuppressWarnings("unchecked")
205 Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
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);
214 getter = type.getMethod(getterName);
215 } catch (NoSuchMethodException e) {
216 throw new IllegalStateException("Failed to find getter " + getterName, e);
219 final ChildStream child = createStream(loader, props, schemaChild, getter);
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);
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)
238 .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
240 LOG.trace("Definition of {} done", fqcn);
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);
249 if (childSchema instanceof ContainerSchemaNode) {
250 return containerChildStream(getter);
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]);
262 valueClass = loadTypeClass(loader, params[0]);
265 return listChildStream(getter, valueClass.asSubclass(DataObject.class), listSchema);
267 if (childSchema instanceof ChoiceSchemaNode) {
268 return choiceChildStream(getter);
270 if (childSchema instanceof AnydataSchemaNode) {
271 return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
273 if (childSchema instanceof AnyxmlSchemaNode) {
274 return qnameChildStream(STREAM_ANYXML, getter, childSchema);
276 if (childSchema instanceof LeafListSchemaNode leafListSchema) {
277 return qnameChildStream(leafListSchema.isUserOrdered() ? STREAM_ORDERED_LEAF_LIST : STREAM_LEAF_LIST,
278 getter, childSchema);
281 LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
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()),
292 invokeMethod(getter),
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);
300 // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo())
301 return new ChildStream(streamer,
302 streamerInstance(streamer),
306 invokeMethod(getter),
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;
317 method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
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),
327 invokeMethod(getter),
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(
336 new TextConstant(schema.getQName().getLocalName()),
338 invokeMethod(getter),
342 private static StackManipulation streamerInstance(final DataContainerStreamer<?> streamer) {
344 return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
345 } catch (NoSuchFieldException e) {
346 throw new IllegalStateException(e);
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()),
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);
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());
368 for (var parent : type.getImplements()) {
369 if (parent instanceof GeneratedType generated) {
370 collectAllProperties(generated, hashMap);
375 private static Class<?> loadTypeClass(final BindingClassLoader loader, final Type type) {
377 return loader.loadClass(type.getFullyQualifiedName());
378 } catch (ClassNotFoundException e) {
379 throw new LinkageError("Failed to load " + type, e);
383 private static final class SerializeImplementation implements Implementation {
384 private final List<ChildStream> children;
385 private final StackManipulation startEvent;
386 private final Class<?> bindingInterface;
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);
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);
406 public ByteCodeAppender appender(final Target implementationTarget) {
407 final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
409 // stream.<START_EVENT>(...)
410 manipulations.add(STREAM);
411 manipulations.add(startEvent);
413 // ... emit children ...
414 manipulations.addAll(children);
416 if (Augmentable.class.isAssignableFrom(bindingInterface)) {
417 // streamAugmentations(reg, stream, obj)
418 manipulations.add(STREAM_AUGMENTATIONS);
422 manipulations.add(STREAM);
423 manipulations.add(END_NODE);
425 manipulations.add(MethodReturn.VOID);
427 return new ByteCodeAppender.Simple(manipulations);
431 private static final class ChildStream extends StackManipulation.Compound {
432 private final @Nullable Class<?> dependency;
434 ChildStream(final StackManipulation... stackManipulation) {
435 super(stackManipulation);
439 ChildStream(final DataContainerStreamer<?> streamer, final StackManipulation... stackManipulation) {
440 super(stackManipulation);
441 dependency = streamer.getClass();
444 @Nullable Class<?> getDependency() {
449 private enum InitializeInstanceField implements ByteCodeAppender {
452 // TODO: eliminate this constant when ElementMatchers.isDefaultConstructor() returns a singleton
453 private static final ElementMatcher<MethodDescription> IS_DEFAULT_CONSTRUCTOR =
454 ElementMatchers.isDefaultConstructor();
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),
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());