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.impl.NodeCodecContext.CodecContextFactory;
53 import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping;
54 import org.opendaylight.mdsal.binding.loader.BindingClassLoader;
55 import org.opendaylight.mdsal.binding.loader.BindingClassLoader.ClassGenerator;
56 import org.opendaylight.mdsal.binding.loader.BindingClassLoader.GeneratorResult;
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;
82 final class DataObjectStreamerGenerator<T extends DataObjectStreamer<?>> implements ClassGenerator<T> {
83 static final String INSTANCE_FIELD = "INSTANCE";
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);
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;
96 private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataObjectStreamer.class)
97 .modifiers(PUB_FINAL);
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);
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,
114 // startMapEntryNode(obj.key(), UNKNOWN_SIZE)
115 private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
117 invokeMethod(Identifiable.class, "key"),
119 invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Identifier.class, int.class));
121 // startUnkeyedListItem(UNKNOWN_SIZE)
122 private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
124 invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
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,
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,
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);
153 // streamAugmentations(reg, stream, obj)
154 private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
158 invokeMethod(DataObjectStreamer.class, "streamAugmentations", DataObjectSerializerRegistry.class,
159 BindingStreamEventWriter.class, Augmentable.class));
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;
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);
176 static Class<? extends DataObjectStreamer<?>> generateStreamer(final BindingClassLoader loader,
177 final CodecContextFactory registry, final Class<?> type) {
179 final var typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
180 final var schema = typeAndSchema.statement();
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);
196 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
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));
206 public GeneratorResult<T> generateClass(final BindingClassLoader loader, final String fqcn,
207 final Class<?> bindingInterface) {
208 LOG.trace("Definining streamer {}", fqcn);
210 @SuppressWarnings("unchecked")
211 Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
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);
220 getter = type.getMethod(getterName);
221 } catch (NoSuchMethodException e) {
222 throw new IllegalStateException("Failed to find getter " + getterName, e);
225 final ChildStream child = createStream(loader, props, schemaChild, getter);
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);
240 final GeneratorResult<T> result = GeneratorResult.of(builder
241 .defineMethod("serialize", BB_VOID, PUB_FINAL)
242 .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
244 .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
246 LOG.trace("Definition of {} done", fqcn);
250 private ChildStream createStream(final BindingClassLoader 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);
255 if (childSchema instanceof ContainerSchemaNode) {
256 return containerChildStream(getter);
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]);
269 valueClass = loadTypeClass(loader, params[0]);
272 return listChildStream(getter, valueClass.asSubclass(DataObject.class), listSchema);
274 if (childSchema instanceof ChoiceSchemaNode) {
275 return choiceChildStream(getter);
277 if (childSchema instanceof AnydataSchemaNode) {
278 return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
280 if (childSchema instanceof AnyxmlSchemaNode) {
281 return qnameChildStream(STREAM_ANYXML, getter, childSchema);
283 if (childSchema instanceof LeafListSchemaNode) {
284 return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
285 : STREAM_LEAF_LIST, getter, childSchema);
288 LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
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()),
299 invokeMethod(getter),
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);
307 // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo())
308 return new ChildStream(streamer,
309 streamerInstance(streamer),
313 invokeMethod(getter),
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;
324 method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
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),
334 invokeMethod(getter),
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(
343 new TextConstant(schema.getQName().getLocalName()),
345 invokeMethod(getter),
349 private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
351 return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
352 } catch (NoSuchFieldException e) {
353 throw new IllegalStateException(e);
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()),
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);
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());
375 for (final Type parent : type.getImplements()) {
376 if (parent instanceof GeneratedType) {
377 collectAllProperties((GeneratedType) parent, hashMap);
382 private static Class<?> loadTypeClass(final BindingClassLoader loader, final Type type) {
384 return loader.loadClass(type.getFullyQualifiedName());
385 } catch (ClassNotFoundException e) {
386 throw new LinkageError("Failed to load " + type, e);
390 private static final class SerializeImplementation implements Implementation {
391 private final List<ChildStream> children;
392 private final StackManipulation startEvent;
393 private final Class<?> bindingInterface;
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);
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);
411 public ByteCodeAppender appender(final Target implementationTarget) {
412 final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
414 // stream.<START_EVENT>(...)
415 manipulations.add(STREAM);
416 manipulations.add(startEvent);
418 // ... emit children ...
419 manipulations.addAll(children);
421 if (Augmentable.class.isAssignableFrom(bindingInterface)) {
422 // streamAugmentations(reg, stream, obj)
423 manipulations.add(STREAM_AUGMENTATIONS);
427 manipulations.add(STREAM);
428 manipulations.add(END_NODE);
430 manipulations.add(MethodReturn.VOID);
432 return new ByteCodeAppender.Simple(manipulations);
436 private static final class ChildStream extends StackManipulation.Compound {
437 private final @Nullable Class<?> dependency;
439 ChildStream(final StackManipulation... stackManipulation) {
440 super(stackManipulation);
444 ChildStream(final DataObjectStreamer<?> streamer, final StackManipulation... stackManipulation) {
445 super(stackManipulation);
446 dependency = streamer.getClass();
449 @Nullable Class<?> getDependency() {
454 private enum InitializeInstanceField implements ByteCodeAppender {
457 // TODO: eliminate this constant when ElementMatchers.isDefaultConstructor() returns a singleton
458 private static final ElementMatcher<MethodDescription> IS_DEFAULT_CONSTRUCTOR =
459 ElementMatchers.isDefaultConstructor();
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),
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());