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;
24 import java.util.Map.Entry;
26 import net.bytebuddy.ByteBuddy;
27 import net.bytebuddy.description.field.FieldDescription;
28 import net.bytebuddy.description.method.MethodDescription;
29 import net.bytebuddy.description.type.TypeDefinition;
30 import net.bytebuddy.description.type.TypeDefinition.Sort;
31 import net.bytebuddy.description.type.TypeDescription;
32 import net.bytebuddy.description.type.TypeDescription.Generic;
33 import net.bytebuddy.dynamic.DynamicType.Builder;
34 import net.bytebuddy.dynamic.scaffold.InstrumentedType;
35 import net.bytebuddy.implementation.Implementation;
36 import net.bytebuddy.implementation.Implementation.Context;
37 import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
38 import net.bytebuddy.implementation.bytecode.Duplication;
39 import net.bytebuddy.implementation.bytecode.StackManipulation;
40 import net.bytebuddy.implementation.bytecode.TypeCreation;
41 import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
42 import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
43 import net.bytebuddy.implementation.bytecode.constant.TextConstant;
44 import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
45 import net.bytebuddy.implementation.bytecode.member.MethodReturn;
46 import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
47 import net.bytebuddy.jar.asm.MethodVisitor;
48 import net.bytebuddy.jar.asm.Opcodes;
49 import net.bytebuddy.matcher.ElementMatcher;
50 import net.bytebuddy.matcher.ElementMatchers;
51 import org.eclipse.jdt.annotation.Nullable;
52 import org.opendaylight.mdsal.binding.dom.codec.api.BindingStreamEventWriter;
53 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
54 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
55 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator;
56 import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
57 import org.opendaylight.mdsal.binding.dom.codec.spi.BindingSchemaMapping;
58 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
59 import org.opendaylight.mdsal.binding.model.api.MethodSignature;
60 import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
61 import org.opendaylight.mdsal.binding.model.api.Type;
62 import org.opendaylight.yangtools.yang.binding.Augmentable;
63 import org.opendaylight.yangtools.yang.binding.DataContainer;
64 import org.opendaylight.yangtools.yang.binding.DataObject;
65 import org.opendaylight.yangtools.yang.binding.Identifiable;
66 import org.opendaylight.yangtools.yang.binding.Identifier;
67 import org.opendaylight.yangtools.yang.model.api.AnydataSchemaNode;
68 import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
69 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
70 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
71 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
72 import org.opendaylight.yangtools.yang.model.api.ContainerLike;
73 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
74 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
75 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
76 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
77 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
78 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
79 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
80 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
81 import org.slf4j.Logger;
82 import org.slf4j.LoggerFactory;
84 final class DataObjectStreamerGenerator<T extends DataObjectStreamer<?>> implements ClassGenerator<T> {
85 static final String INSTANCE_FIELD = "INSTANCE";
87 private static final Logger LOG = LoggerFactory.getLogger(DataObjectStreamerGenerator.class);
88 private static final Generic BB_VOID = TypeDefinition.Sort.describe(void.class);
89 private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class);
90 private static final Generic BB_DOSR = TypeDefinition.Sort.describe(DataObjectSerializerRegistry.class);
91 private static final Generic BB_BESV = TypeDefinition.Sort.describe(BindingStreamEventWriter.class);
92 private static final Generic BB_IOX = TypeDefinition.Sort.describe(IOException.class);
94 private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
95 private static final int PUB_CONST = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
96 | Opcodes.ACC_SYNTHETIC;
98 private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataObjectStreamer.class)
99 .modifiers(PUB_FINAL);
101 private static final StackManipulation REG = MethodVariableAccess.REFERENCE.loadFrom(1);
102 private static final StackManipulation OBJ = MethodVariableAccess.REFERENCE.loadFrom(2);
103 private static final StackManipulation STREAM = MethodVariableAccess.REFERENCE.loadFrom(3);
104 private static final StackManipulation UNKNOWN_SIZE = IntegerConstant.forValue(
105 BindingStreamEventWriter.UNKNOWN_SIZE);
107 private static final StackManipulation START_AUGMENTATION_NODE = invokeMethod(BindingStreamEventWriter.class,
108 "startAugmentationNode", Class.class);
109 private static final StackManipulation START_CASE = invokeMethod(BindingStreamEventWriter.class,
110 "startCase", Class.class, int.class);
111 private static final StackManipulation START_CONTAINER_NODE = invokeMethod(BindingStreamEventWriter.class,
112 "startContainerNode", Class.class, int.class);
113 private static final StackManipulation END_NODE = invokeMethod(BindingStreamEventWriter.class,
116 // startMapEntryNode(obj.key(), UNKNOWN_SIZE)
117 private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
119 invokeMethod(Identifiable.class, "key"),
121 invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Identifier.class, int.class));
123 // startUnkeyedListItem(UNKNOWN_SIZE)
124 private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
126 invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
128 private static final StackManipulation STREAM_ANYDATA = invokeMethod(DataObjectStreamer.class,
129 "streamAnydata", BindingStreamEventWriter.class, String.class, Object.class);
130 private static final StackManipulation STREAM_ANYXML = invokeMethod(DataObjectStreamer.class,
131 "streamAnyxml", BindingStreamEventWriter.class, String.class, Object.class);
132 private static final StackManipulation STREAM_CHOICE = invokeMethod(DataObjectStreamer.class,
133 "streamChoice", Class.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
134 DataContainer.class);
135 private static final StackManipulation STREAM_CONTAINER = invokeMethod(DataObjectStreamer.class,
136 "streamContainer", DataObjectStreamer.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
138 private static final StackManipulation STREAM_LEAF = invokeMethod(DataObjectStreamer.class,
139 "streamLeaf", BindingStreamEventWriter.class, String.class, Object.class);
140 private static final StackManipulation STREAM_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
142 BindingStreamEventWriter.class, String.class, Set.class);
143 private static final StackManipulation STREAM_ORDERED_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
144 "streamOrderedLeafList", BindingStreamEventWriter.class, String.class, List.class);
145 private static final StackManipulation STREAM_LIST = invokeMethod(DataObjectStreamer.class,
146 "streamList", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
147 BindingStreamEventWriter.class, List.class);
148 private static final StackManipulation STREAM_MAP = invokeMethod(DataObjectStreamer.class,
149 "streamMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
150 BindingStreamEventWriter.class, Map.class);
151 private static final StackManipulation STREAM_ORDERED_MAP = invokeMethod(DataObjectStreamer.class,
152 "streamOrderedMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
153 BindingStreamEventWriter.class, List.class);
155 // streamAugmentations(reg, stream, obj)
156 private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
160 invokeMethod(DataObjectStreamer.class, "streamAugmentations", DataObjectSerializerRegistry.class,
161 BindingStreamEventWriter.class, Augmentable.class));
163 private final CodecContextFactory registry;
164 private final StackManipulation startEvent;
165 private final DataNodeContainer schema;
166 private final Class<?> type;
167 private final GeneratedType genType;
169 private DataObjectStreamerGenerator(final CodecContextFactory registry, final GeneratedType genType,
170 final DataNodeContainer schema, final Class<?> type, final StackManipulation startEvent) {
171 this.registry = requireNonNull(registry);
172 this.genType = requireNonNull(genType);
173 this.schema = requireNonNull(schema);
174 this.type = requireNonNull(type);
175 this.startEvent = requireNonNull(startEvent);
178 static Class<? extends DataObjectStreamer<?>> generateStreamer(final CodecClassLoader loader,
179 final CodecContextFactory registry, final Class<?> type) {
181 final Entry<GeneratedType, WithStatus> typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
182 final WithStatus schema = typeAndSchema.getValue();
184 final StackManipulation startEvent;
185 if (schema instanceof ContainerLike || schema instanceof NotificationDefinition) {
186 startEvent = classUnknownSizeMethod(START_CONTAINER_NODE, type);
187 } else if (schema instanceof ListSchemaNode) {
188 startEvent = ((ListSchemaNode) schema).getKeyDefinition().isEmpty() ? START_UNKEYED_LIST_ITEM
189 : START_MAP_ENTRY_NODE;
190 } else if (schema instanceof AugmentationSchemaNode) {
191 // startAugmentationNode(Foo.class)
192 startEvent = new StackManipulation.Compound(
193 ClassConstant.of(Sort.describe(type).asErasure()),
194 START_AUGMENTATION_NODE);
195 } else if (schema instanceof CaseSchemaNode) {
196 startEvent = classUnknownSizeMethod(START_CASE, type);
198 throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
201 return loader.generateClass(type, "streamer",
202 new DataObjectStreamerGenerator<>(registry, typeAndSchema.getKey(), (DataNodeContainer) schema, type,
207 public GeneratorResult<T> generateClass(final CodecClassLoader loader, final String fqcn,
208 final Class<?> bindingInterface) {
209 LOG.trace("Definining streamer {}", fqcn);
211 @SuppressWarnings("unchecked")
212 Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
214 final ImmutableMap<String, Type> props = collectAllProperties(genType);
215 final List<ChildStream> children = new ArrayList<>(props.size());
216 for (final DataSchemaNode schemaChild : schema.getChildNodes()) {
217 if (!schemaChild.isAugmenting()) {
218 final String getterName = BindingSchemaMapping.getGetterMethodName(schemaChild);
221 getter = type.getMethod(getterName);
222 } catch (NoSuchMethodException e) {
223 throw new IllegalStateException("Failed to find getter " + getterName, e);
226 final ChildStream child = createStream(loader, props, schemaChild, getter);
233 final ImmutableList.Builder<Class<?>> depBuilder = ImmutableList.builder();
234 for (ChildStream child : children) {
235 final Class<?> dependency = child.getDependency();
236 if (dependency != null) {
237 depBuilder.add(dependency);
241 final GeneratorResult<T> result = GeneratorResult.of(builder
242 .defineMethod("serialize", BB_VOID, PUB_FINAL)
243 .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
245 .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
247 LOG.trace("Definition of {} done", fqcn);
251 private ChildStream createStream(final CodecClassLoader loader, final ImmutableMap<String, Type> props,
252 final DataSchemaNode childSchema, final Method getter) {
253 if (childSchema instanceof LeafSchemaNode) {
254 return qnameChildStream(STREAM_LEAF, getter, childSchema);
256 if (childSchema instanceof ContainerSchemaNode) {
257 return containerChildStream(getter);
259 if (childSchema instanceof ListSchemaNode) {
260 final String getterName = getter.getName();
261 final Type childType = props.get(getterName);
262 verify(childType instanceof ParameterizedType, "Unexpected type %s for %s", childType, getterName);
263 final Type[] params = ((ParameterizedType) childType).getActualTypeArguments();
264 final ListSchemaNode listSchema = (ListSchemaNode) childSchema;
265 final Class<?> valueClass;
266 if (!listSchema.isUserOrdered() && !listSchema.getKeyDefinition().isEmpty()) {
267 loadTypeClass(loader, params[0]);
268 valueClass = loadTypeClass(loader, params[1]);
270 valueClass = loadTypeClass(loader, params[0]);
273 return listChildStream(getter, valueClass.asSubclass(DataObject.class), listSchema);
275 if (childSchema instanceof ChoiceSchemaNode) {
276 return choiceChildStream(getter);
278 if (childSchema instanceof AnydataSchemaNode) {
279 return qnameChildStream(STREAM_ANYDATA, getter, childSchema);
281 if (childSchema instanceof AnyxmlSchemaNode) {
282 return qnameChildStream(STREAM_ANYXML, getter, childSchema);
284 if (childSchema instanceof LeafListSchemaNode) {
285 return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
286 : STREAM_LEAF_LIST, getter, childSchema);
289 LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
293 private static ChildStream choiceChildStream(final Method getter) {
294 // streamChoice(Foo.class, reg, stream, obj.getFoo())
295 return new ChildStream(
296 ClassConstant.of(Sort.describe(getter.getReturnType()).asErasure()),
300 invokeMethod(getter),
304 private ChildStream containerChildStream(final Method getter) {
305 final Class<? extends DataObject> itemClass = getter.getReturnType().asSubclass(DataObject.class);
306 final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
308 // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo())
309 return new ChildStream(streamer,
310 streamerInstance(streamer),
314 invokeMethod(getter),
318 private ChildStream listChildStream(final Method getter, final Class<? extends DataObject> itemClass,
319 final ListSchemaNode childSchema) {
320 final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
321 final StackManipulation method;
322 if (childSchema.getKeyDefinition().isEmpty()) {
323 method = STREAM_LIST;
325 method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
328 // <METHOD>(Foo.class, FooStreamer.INSTACE, reg, stream, obj.getFoo())
329 return new ChildStream(streamer,
330 ClassConstant.of(Sort.describe(itemClass).asErasure()),
331 streamerInstance(streamer),
335 invokeMethod(getter),
339 private static ChildStream qnameChildStream(final StackManipulation method, final Method getter,
340 final DataSchemaNode schema) {
341 // <METHOD>(stream, "foo", obj.getFoo())
342 return new ChildStream(
344 new TextConstant(schema.getQName().getLocalName()),
346 invokeMethod(getter),
350 private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
352 return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
353 } catch (NoSuchFieldException e) {
354 throw new IllegalStateException(e);
358 private static StackManipulation classUnknownSizeMethod(final StackManipulation method, final Class<?> type) {
359 // <METHOD>(Foo.class, UNKNOWN_SIZE)
360 return new StackManipulation.Compound(
361 ClassConstant.of(Sort.describe(type).asErasure()),
366 private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
367 final Map<String, Type> props = new HashMap<>();
368 collectAllProperties(type, props);
369 return ImmutableMap.copyOf(props);
372 private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
373 for (final MethodSignature definition : type.getMethodDefinitions()) {
374 hashMap.put(definition.getName(), definition.getReturnType());
376 for (final Type parent : type.getImplements()) {
377 if (parent instanceof GeneratedType) {
378 collectAllProperties((GeneratedType) parent, hashMap);
383 private static Class<?> loadTypeClass(final CodecClassLoader loader, final Type type) {
385 return loader.loadClass(type.getFullyQualifiedName());
386 } catch (ClassNotFoundException e) {
387 throw new LinkageError("Failed to load " + type, e);
391 private static final class SerializeImplementation implements Implementation {
392 private final List<ChildStream> children;
393 private final StackManipulation startEvent;
394 private final Class<?> bindingInterface;
396 SerializeImplementation(final Class<?> bindingInterface, final StackManipulation startEvent,
397 final List<ChildStream> children) {
398 this.bindingInterface = requireNonNull(bindingInterface);
399 this.startEvent = requireNonNull(startEvent);
400 this.children = requireNonNull(children);
404 public InstrumentedType prepare(final InstrumentedType instrumentedType) {
405 return instrumentedType
406 // private static final This INSTANCE = new This()
407 .withField(new FieldDescription.Token(INSTANCE_FIELD, PUB_CONST, instrumentedType.asGenericType()))
408 .withInitializer(InitializeInstanceField.INSTANCE);
412 public ByteCodeAppender appender(final Target implementationTarget) {
413 final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
415 // stream.<START_EVENT>(...)
416 manipulations.add(STREAM);
417 manipulations.add(startEvent);
419 // ... emit children ...
420 manipulations.addAll(children);
422 if (Augmentable.class.isAssignableFrom(bindingInterface)) {
423 // streamAugmentations(reg, stream, obj)
424 manipulations.add(STREAM_AUGMENTATIONS);
428 manipulations.add(STREAM);
429 manipulations.add(END_NODE);
431 manipulations.add(MethodReturn.VOID);
433 return new ByteCodeAppender.Simple(manipulations);
437 private static final class ChildStream extends StackManipulation.Compound {
438 private final @Nullable Class<?> dependency;
440 ChildStream(final StackManipulation... stackManipulation) {
441 super(stackManipulation);
445 ChildStream(final DataObjectStreamer<?> streamer, final StackManipulation... stackManipulation) {
446 super(stackManipulation);
447 dependency = streamer.getClass();
450 @Nullable Class<?> getDependency() {
455 private enum InitializeInstanceField implements ByteCodeAppender {
458 // TODO: eliminate this constant when ElementMatchers.isDefaultConstructor() returns a singleton
459 private static final ElementMatcher<MethodDescription> IS_DEFAULT_CONSTRUCTOR =
460 ElementMatchers.isDefaultConstructor();
463 public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
464 final MethodDescription instrumentedMethod) {
465 final TypeDescription instrumentedType = implementationContext.getInstrumentedType();
466 StackManipulation.Size operandStackSize = new StackManipulation.Compound(
467 TypeCreation.of(instrumentedType),
469 MethodInvocation.invoke(instrumentedType.getDeclaredMethods()
470 .filter(IS_DEFAULT_CONSTRUCTOR).getOnly().asDefined()),
471 putField(instrumentedType, INSTANCE_FIELD))
472 .apply(methodVisitor, implementationContext);
473 return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());