<artifactId>mdsal-binding-dom-codec</artifactId>
<packaging>bundle</packaging>
+ <properties>
+ <shade.source>net.bytebuddy</shade.source>
+ <shade.target>org.opendaylight.mdsal.binding.dom.codec.jar.bytebuddy</shade.target>
+ </properties>
+
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
</dependency>
+ <dependency>
+ <!-- We are going to shade this -->
+ <groupId>net.bytebuddy</groupId>
+ <artifactId>byte-buddy</artifactId>
+ <version>1.9.12</version>
+ </dependency>
<dependency>
<groupId>org.opendaylight.mdsal</groupId>
<artifactId>mdsal-binding-generator-impl</artifactId>
<Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
<Export-Package>
org.opendaylight.mdsal.binding.dom.codec.*,
- org.opendaylight.mdsal.binding.dom.codec.gen.impl.*,
- org.opendaylight.mdsal.binding.dom.codec.impl.*,
;-split-package:=error
</Export-Package>
+ <Private-Package>
+ org.opendaylight.mdsal.binding.dom.codec.loader,
+ </Private-Package>
+ <Import-Package>
+ !net.bytebuddy.*,
+ *
+ </Import-Package>
</instructions>
</configuration>
</plugin>
+
+ <!-- Shade Byte-Buddy -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <shadedArtifactAttached>false</shadedArtifactAttached>
+ <createDependencyReducedPom>true</createDependencyReducedPom>
+ <createSourcesJar>true</createSourcesJar>
+ <shadeSourcesContent>true</shadeSourcesContent>
+ <minimizeJar>true</minimizeJar>
+ <relocations>
+ <relocation>
+ <pattern>${shade.source}</pattern>
+ <shadedPattern>${shade.target}</shadedPattern>
+ </relocation>
+ </relocations>
+ <artifactSet>
+ <includes>
+ <include>net.bytebuddy:byte-buddy</include>
+ </includes>
+ </artifactSet>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
ImmutableClassToInstanceMap.class, "cachedAugmentations");
private volatile ImmutableClassToInstanceMap<Augmentation<T>> cachedAugmentations;
- public AugmentableCodecDataObject(final DataObjectCodecContext<T, ?> context,
+ protected AugmentableCodecDataObject(final DataObjectCodecContext<T, ?> context,
final NormalizedNodeContainer<?, ?, ?> data) {
super(data);
this.context = requireNonNull(context, "Context must not be null");
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
-import javassist.CannotCompileException;
-import javassist.NotFoundException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingCodecTree;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode;
import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
-import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
import org.opendaylight.mdsal.binding.dom.codec.util.BindingSchemaMapping;
import org.opendaylight.mdsal.binding.generator.util.BindingRuntimeContext;
import org.opendaylight.mdsal.binding.model.api.GeneratedType;
private final LoadingCache<Class<?>, DataObjectStreamer<?>> streamers = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, DataObjectStreamer<?>>() {
@Override
- public DataObjectStreamer<?> load(final Class<?> key) throws CannotCompileException, IOException,
- NotFoundException, ReflectiveOperationException {
- final Class<?> streamer = loader.generateSubclass(DataObjectStreamerCustomizer.CT_DOS, key, "streamer",
- DataObjectStreamerCustomizer.create(BindingCodecContext.this, key));
-
- final Field instance = streamer.getDeclaredField(DataObjectStreamerCustomizer.INSTANCE_FIELD);
+ public DataObjectStreamer<?> load(final Class<?> key) throws ReflectiveOperationException {
+ final Class<?> streamer = DataObjectStreamerGenerator.generateStreamer(loader, BindingCodecContext.this,
+ key);
+ final Field instance = streamer.getDeclaredField(DataObjectStreamerGenerator.INSTANCE_FIELD);
return (DataObjectStreamer<?>) instance.get(null);
}
});
}
});
- private final @NonNull CodecClassLoader loader = StaticClassPool.createLoader();
+ private final @NonNull CodecClassLoader loader = CodecClassLoader.create();
private final InstanceIdentifierCodec instanceIdentifierCodec;
private final IdentityCodec identityCodec;
private final BindingNormalizedNodeCodecRegistry registry;
--- /dev/null
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.dom.codec.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import net.bytebuddy.asm.AsmVisitorWrapper;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.field.FieldDescription.ForLoadedField;
+import net.bytebuddy.description.field.FieldList;
+import net.bytebuddy.description.method.MethodDescription.ForLoadedMethod;
+import net.bytebuddy.description.method.MethodList;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess;
+import net.bytebuddy.implementation.bytecode.member.FieldAccess.Defined;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.jar.asm.ClassVisitor;
+import net.bytebuddy.jar.asm.ClassWriter;
+import net.bytebuddy.jar.asm.Label;
+import net.bytebuddy.jar.asm.MethodVisitor;
+import net.bytebuddy.jar.asm.Opcodes;
+import net.bytebuddy.matcher.ElementMatchers;
+import net.bytebuddy.pool.TypePool;
+
+final class ByteBuddyUtils {
+ static final StackManipulation THIS = MethodVariableAccess.loadThis();
+
+ private ByteBuddyUtils() {
+
+ }
+
+ static StackManipulation invokeMethod(final Method method) {
+ return MethodInvocation.invoke(describe(method));
+ }
+
+ static StackManipulation invokeMethod(final Class<?> clazz, final String name, final Class<?>... args) {
+ return MethodInvocation.invoke(describe(clazz, name, args));
+ }
+
+ static AsmVisitorWrapper computeFrames() {
+ return ComputeFrames.INSTANCE;
+ }
+
+ static StackManipulation ifEq(final Label label) {
+ return new IfEq(label);
+ }
+
+ static StackManipulation markLabel(final Label label) {
+ return new Mark(label);
+ }
+
+ static StackManipulation loadThis() {
+ return THIS;
+ }
+
+ static StackManipulation getField(final Field field) {
+ return FieldAccess.forField(new ForLoadedField(field).asDefined()).read();
+ }
+
+ static StackManipulation getField(final TypeDescription instrumentedType, final String fieldName) {
+ return fieldAccess(instrumentedType, fieldName).read();
+ }
+
+ static StackManipulation putField(final TypeDescription instrumentedType, final String fieldName) {
+ return fieldAccess(instrumentedType, fieldName).write();
+ }
+
+ private static ForLoadedMethod describe(final Method method) {
+ return new ForLoadedMethod(method);
+ }
+
+ private static ForLoadedMethod describe(final Class<?> clazz, final String name, final Class<?>... args) {
+ return describe(getMethod(clazz, name, args));
+ }
+
+ private static Defined fieldAccess(final TypeDescription instrumentedType, final String fieldName) {
+ return FieldAccess.forField(instrumentedType.getDeclaredFields().filter(ElementMatchers.named(fieldName))
+ .getOnly());
+ }
+
+ /**
+ * Utility wrapper to force ASM to compute frames.
+ */
+ private enum ComputeFrames implements AsmVisitorWrapper {
+ INSTANCE;
+
+ @Override
+ public int mergeWriter(final int flags) {
+ return flags | ClassWriter.COMPUTE_FRAMES;
+ }
+
+ @Override
+ public int mergeReader(final int flags) {
+ return flags | ClassWriter.COMPUTE_FRAMES;
+ }
+
+ @Override
+ public ClassVisitor wrap(final TypeDescription td, final ClassVisitor cv, final Implementation.Context ctx,
+ final TypePool tp, final FieldList<FieldDescription.InDefinedShape> fields, final MethodList<?> methods,
+ final int wflags, final int rflags) {
+ return cv;
+ }
+ }
+
+ /**
+ * IFEQ opcode invocation, jumping to a particular label.
+ */
+ private static final class IfEq implements StackManipulation {
+ private static final StackManipulation.Size SIZE = new StackManipulation.Size(-1, 0);
+
+ private final Label label;
+
+ IfEq(final Label label) {
+ this.label = requireNonNull(label);
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public StackManipulation.Size apply(final MethodVisitor mv, final Implementation.Context ctx) {
+ mv.visitJumpInsn(Opcodes.IFEQ, label);
+ return SIZE;
+ }
+ }
+
+ /**
+ * A label definition, marking the spot where IfEq should jump.
+ */
+ private static final class Mark implements StackManipulation {
+ private static final StackManipulation.Size SIZE = new StackManipulation.Size(0, 0);
+
+ private final Label label;
+
+ Mark(final Label label) {
+ this.label = requireNonNull(label);
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public StackManipulation.Size apply(final MethodVisitor mv, final Implementation.Context ctx) {
+ mv.visitLabel(label);
+ return SIZE;
+ }
+ }
+
+ private static Method getMethod(final Class<?> clazz, final String name, final Class<?>... args) {
+ try {
+ return clazz.getDeclaredMethod(name, args);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
private volatile Integer cachedHashcode = null;
- public CodecDataObject(final NormalizedNodeContainer<?, ?, ?> data) {
+ protected CodecDataObject(final NormalizedNodeContainer<?, ?, ?> data) {
this.data = requireNonNull(data, "Data must not be null");
}
*/
@Beta
public final class CodecDataObjectBridge {
- private static final ThreadLocal<CodecDataObjectCustomizer> CURRENT_CUSTOMIZER = new ThreadLocal<>();
+ private static final ThreadLocal<CodecDataObjectGenerator> CURRENT_CUSTOMIZER = new ThreadLocal<>();
private CodecDataObjectBridge() {
return current().resolveKey(methodName);
}
- static @Nullable CodecDataObjectCustomizer setup(final @NonNull CodecDataObjectCustomizer next) {
- final CodecDataObjectCustomizer prev = CURRENT_CUSTOMIZER.get();
+ static @Nullable CodecDataObjectGenerator setup(final @NonNull CodecDataObjectGenerator next) {
+ final CodecDataObjectGenerator prev = CURRENT_CUSTOMIZER.get();
CURRENT_CUSTOMIZER.set(verifyNotNull(next));
return prev;
}
- static void tearDown(final @Nullable CodecDataObjectCustomizer prev) {
+ static void tearDown(final @Nullable CodecDataObjectGenerator prev) {
if (prev == null) {
CURRENT_CUSTOMIZER.remove();
} else {
}
}
- private static @NonNull CodecDataObjectCustomizer current() {
+ private static @NonNull CodecDataObjectGenerator current() {
return verifyNotNull(CURRENT_CUSTOMIZER.get(), "No customizer attached");
}
}
+++ /dev/null
-/*
- * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.mdsal.binding.dom.codec.impl;
-
-import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.base.MoreObjects.ToStringHelper;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import javassist.CannotCompileException;
-import javassist.CtClass;
-import javassist.CtField;
-import javassist.CtMethod;
-import javassist.NotFoundException;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
-import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
-import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Private support for generating AbstractDataObject specializations.
- */
-final class CodecDataObjectCustomizer implements Customizer {
- private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectCustomizer.class);
- private static final CtClass CT_ARFU = StaticClassPool.findClass(AtomicReferenceFieldUpdater.class);
- private static final CtClass CT_BOOLEAN = StaticClassPool.findClass(boolean.class);
- private static final CtClass CT_DATAOBJECT = StaticClassPool.findClass(DataObject.class);
- private static final CtClass CT_HELPER = StaticClassPool.findClass(ToStringHelper.class);
- private static final CtClass CT_IIC = StaticClassPool.findClass(IdentifiableItemCodec.class);
- private static final CtClass CT_INT = StaticClassPool.findClass(int.class);
- private static final CtClass CT_NCS = StaticClassPool.findClass(NodeContextSupplier.class);
- private static final CtClass CT_OBJECT = StaticClassPool.findClass(Object.class);
- private static final CtClass[] EMPTY_ARGS = new CtClass[0];
- private static final CtClass[] EQUALS_ARGS = new CtClass[] { CT_DATAOBJECT };
- private static final CtClass[] TOSTRING_ARGS = new CtClass[] { CT_HELPER };
-
- private final ImmutableMap<Method, NodeContextSupplier> properties;
- private final Entry<Method, IdentifiableItemCodec> keyMethod;
-
- CodecDataObjectCustomizer(final ImmutableMap<Method, NodeContextSupplier> properties,
- final @Nullable Entry<Method, IdentifiableItemCodec> keyMethod) {
- this.properties = requireNonNull(properties);
- this.keyMethod = keyMethod;
- }
-
- @Override
- public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
- throws NotFoundException, CannotCompileException {
- // Generate members for all methods ...
- LOG.trace("Generating class {}", generated.getName());
- generated.addInterface(bindingClass);
-
- for (Method method : properties.keySet()) {
- generateMethod(loader, generated, method, CT_NCS, "resolve");
- }
- if (keyMethod != null) {
- generateMethod(loader, generated, keyMethod.getKey(), CT_IIC, "resolveKey");
- }
-
- // Final bits: codecHashCode() ...
- final CtMethod codecHashCode = new CtMethod(CT_INT, "codecHashCode", EMPTY_ARGS, generated);
- codecHashCode.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
- codecHashCode.setBody(codecHashCodeBody());
- generated.addMethod(codecHashCode);
-
- // ... equals
- final CtMethod codecEquals = new CtMethod(CT_BOOLEAN, "codecEquals", EQUALS_ARGS, generated);
- codecEquals.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
- codecEquals.setBody(codecEqualsBody(bindingClass.getName()));
- generated.addMethod(codecEquals);
-
- // ... and codecFillToString()
- final CtMethod codecFillToString = new CtMethod(CT_HELPER, "codecFillToString", TOSTRING_ARGS, generated);
- codecFillToString.setModifiers(Modifier.PROTECTED | Modifier.FINAL);
- codecFillToString.setBody(codecFillToStringBody());
- generated.addMethod(codecFillToString);
-
- generated.setModifiers(Modifier.FINAL | Modifier.PUBLIC);
- return ImmutableList.of();
- }
-
- @Override
- public Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
- final CodecDataObjectCustomizer prev = CodecDataObjectBridge.setup(this);
- try {
- final Class<?> result = loader.get();
-
- /*
- * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while
- * we have the information needed to find them -- that information is being held in this instance and we
- * leak it to a thread-local variable held by CodecDataObjectBridge.
- *
- * By default the JVM will defer class initialization to first use, which unfortunately is too late for
- * us, and hence we need to force class to initialize.
- */
- try {
- Class.forName(result.getName(), true, result.getClassLoader());
- } catch (ClassNotFoundException e) {
- throw new LinkageError("Failed to find newly-defined " + result, e);
- }
-
- return result;
- } finally {
- CodecDataObjectBridge.tearDown(prev);
- }
- }
-
-
- @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
- final Optional<Entry<Method, NodeContextSupplier>> found = properties.entrySet().stream()
- .filter(entry -> methodName.equals(entry.getKey().getName())).findAny();
- verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this);
- return verifyNotNull(found.get().getValue());
- }
-
- @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
- return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
- .getValue());
- }
-
- private String codecHashCodeBody() {
- final StringBuilder sb = new StringBuilder()
- .append("{\n")
- .append("final int prime = 31;\n")
- .append("int result = 1;\n");
-
- for (Method method : properties.keySet()) {
- sb.append("result = prime * result + java.util.").append(utilClass(method)).append(".hashCode(")
- .append(method.getName()).append("());\n");
- }
-
- return sb.append("return result;\n")
- .append('}').toString();
- }
-
- private String codecEqualsBody(final String ifaceName) {
- final StringBuilder sb = new StringBuilder()
- .append("{\n")
- .append("final ").append(ifaceName).append(" other = $1;")
- .append("return true");
-
- for (Method method : properties.keySet()) {
- final String methodName = method.getName();
- sb.append("\n&& java.util.").append(utilClass(method)).append(".equals(").append(methodName)
- .append("(), other.").append(methodName).append("())");
- }
-
- return sb.append(";\n")
- .append('}').toString();
- }
-
- private String codecFillToStringBody() {
- final StringBuilder sb = new StringBuilder()
- .append("{\n")
- .append("return $1");
- for (Method method : properties.keySet()) {
- final String methodName = method.getName();
- sb.append("\n.add(\"").append(methodName).append("\", ").append(methodName).append("())");
- }
-
- return sb.append(";\n")
- .append('}').toString();
- }
-
- private static void generateMethod(final CodecClassLoader loader, final CtClass generated, final Method method,
- final CtClass contextType, final String resolveMethod) throws CannotCompileException, NotFoundException {
- LOG.trace("Generating for method {}", method);
- final String methodName = method.getName();
- final String methodArfu = methodName + "$$$ARFU";
- final String methodNcs = methodName + "$$$NCS";
-
- // NodeContextSupplier ...
- final CtField ncsField = new CtField(contextType, methodNcs, generated);
- ncsField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
- generated.addField(ncsField, new StringBuilder().append(CodecDataObjectBridge.class.getName())
- .append('.').append(resolveMethod).append("(\"").append(methodName).append("\")").toString());
-
- // ... AtomicReferenceFieldUpdater ...
- final CtField arfuField = new CtField(CT_ARFU, methodArfu, generated);
- arfuField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
- generated.addField(arfuField, new StringBuilder().append(CT_ARFU.getName()).append(".newUpdater(")
- .append(generated.getName()).append(".class, java.lang.Object.class, \"").append(methodName)
- .append("\")").toString());
-
- // ... corresponding volatile field ...
- final CtField field = new CtField(CT_OBJECT, methodName, generated);
- field.setModifiers(Modifier.PRIVATE | Modifier.VOLATILE);
- generated.addField(field);
-
- // ... and the getter
- final Class<?> retType = method.getReturnType();
- final CtMethod getter = new CtMethod(loader.findClass(retType), methodName, EMPTY_ARGS, generated);
- final String retName = retType.isArray() ? retType.getSimpleName() : retType.getName();
-
- getter.setBody(new StringBuilder()
- .append("{\n")
- .append("return (").append(retName).append(") codecMember(").append(methodArfu).append(", ")
- .append(methodNcs).append(");\n")
- .append('}').toString());
- getter.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
- generated.addMethod(getter);
- }
-
- private static String utilClass(final Method method) {
- // We can either have objects or byte[], we cannot have Object[]
- return method.getReturnType().isArray() ? "Arrays" : "Objects";
- }
-}
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.dom.codec.impl;
+
+import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.THIS;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.getField;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.invokeMethod;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.putField;
+
+import com.google.common.base.MoreObjects.ToStringHelper;
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeDescription.Generic;
+import net.bytebuddy.dynamic.DynamicType.Builder;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.Addition;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Multiplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.assign.TypeCasting;
+import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.jar.asm.Label;
+import net.bytebuddy.jar.asm.MethodVisitor;
+import net.bytebuddy.jar.asm.Opcodes;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Private support for generating {@link CodecDataObject} and {@link AugmentableCodecDataObject} specializations.
+ *
+ * <p>
+ * Code generation here is probably more involved than usual mainly due to the fact we *really* want to express the
+ * strong connection between a generated class and BindingCodecContext in terms of a true constant, which boils down to
+ * {@code private static final NodeContextSupplier NCS}. Having such constants provides significant boost to JITs
+ * ability to optimize code -- especially with inlining and constant propagation.
+ *
+ * <p>
+ * The accessor mapping performance is critical due to users typically not taking care of storing the results acquired
+ * by an invocation, assuming the accessors are backed by a normal field -- which of course is not true, as the results
+ * are lazily computed.
+ *
+ * <p>
+ * The design is such that for a particular structure like:
+ * <pre>
+ * container foo {
+ * leaf bar {
+ * type string;
+ * }
+ * }
+ * </pre>
+ * we end up generating a class with the following layout:
+ * <pre>
+ * public final class Foo$$$codecImpl extends CodecDataObject implements Foo {
+ * private static final AtomicRefereceFieldUpdater<Foo$$$codecImpl, Object> getBar$$$A;
+ * private static final NodeContextSupplier getBar$$$C;
+ * private volatile Object getBar;
+ *
+ * public Foo$$$codecImpl(NormalizedNodeContainer data) {
+ * super(data);
+ * }
+ *
+ * public Bar getBar() {
+ * return (Bar) codecMember(getBar$$$A, getBar$$$C);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>
+ * This strategy minimizes the bytecode footprint and follows the generally good idea of keeping common logic in a
+ * single place in a maintainable form. The glue code is extremely light (~6 instructions), which is beneficial on both
+ * sides of invocation:
+ * - generated method can readily be inlined into the caller
+ * - it forms a call site into which codeMember() can be inlined with both AtomicReferenceFieldUpdater and
+ * NodeContextSupplier being constant
+ *
+ * <p>
+ * The second point is important here, as it allows the invocation logic around AtomicRefereceFieldUpdater to completely
+ * disappear, becoming synonymous with operations of a volatile field. NodeContextSupplier being constant also means
+ * it will resolve to one of its two implementations, allowing NodeContextSupplier.get() to be resolved to a constant
+ * (pointing to the supplier itself) or to a simple volatile read (which will be non-null after first access).
+ *
+ * <p>
+ * The sticky point here is the NodeContextSupplier, as it is a heap object which cannot normally be looked up from the
+ * static context in which the static class initializer operates -- so we need perform some sort of a trick here.
+ *
+ * <p>
+ * Eventhough ByteBuddy provides facilities for bridging references to type fields, those facilities operate on volatile
+ * fields -- hence they do not quite work for us.
+ *
+ * <p>
+ * Another alternative, which we used in Javassist-generated DataObjectSerializers, is to muck with the static field
+ * using reflection -- which works, but requires redefinition of Field.modifiers, which is something Java 9 complains
+ * about quite noisily.
+ *
+ * <p>
+ * We take a different approach here, which takes advantage of the fact we are in control of both code generation (here)
+ * and class loading (in {@link CodecClassLoader}). The process is performed in four steps:
+ * <ul>
+ * <li>During code generation, the context fields are pointed towards {@link CodecDataObjectBridge#resolve(String)} and
+ * {@link CodecDataObjectBridge#resolveKey(String)} methods, which are public and static, hence perfectly usable
+ * in the context of a class initializer.</li>
+ * <li>During class loading of generated byte code, the original instance of the generator is called to wrap the actual
+ * class loading operation. At this point the generator installs itself as the current generator for this thread via
+ * {@link CodecDataObjectBridge#setup(CodecDataObjectGenerator)} and allows the class to be loaded.
+ * <li>After the class has been loaded, but before the call returns, we will force the class to initialize, at which
+ * point the static invocations will be redirect to {@link #resolve(String)} and {@link #resolveKey(String)}
+ * methods, thus initializing the fields to the intended constants.</li>
+ * <li>Before returning from the class loading call, the generator will detach itself via
+ * {@link CodecDataObjectBridge#tearDown(CodecDataObjectGenerator)}.</li>
+ * </ul>
+ *
+ * <p>
+ * This strategy works due to close cooperation with the target ClassLoader, as the entire code generation and loading
+ * block runs with the class loading lock for this FQCN and the reference is not leaked until the process completes.
+ */
+final class CodecDataObjectGenerator<T extends CodecDataObject<?>> implements ClassGenerator<T> {
+ private static final Logger LOG = LoggerFactory.getLogger(CodecDataObjectGenerator.class);
+ private static final Generic BB_BOOLEAN = TypeDefinition.Sort.describe(boolean.class);
+ private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class);
+ private static final Generic BB_HELPER = TypeDefinition.Sort.describe(ToStringHelper.class);
+ private static final Generic BB_INT = TypeDefinition.Sort.describe(int.class);
+ private static final Generic BB_IIC = TypeDefinition.Sort.describe(IdentifiableItemCodec.class);
+ private static final Generic BB_NCS = TypeDefinition.Sort.describe(NodeContextSupplier.class);
+
+ private static final StackManipulation BRIDGE_RESOLVE = invokeMethod(CodecDataObjectBridge.class,
+ "resolve", String.class);
+ private static final StackManipulation BRIDGE_RESOLVE_KEY = invokeMethod(CodecDataObjectBridge.class,
+ "resolveKey", String.class);
+ private static final StackManipulation CODEC_MEMBER = invokeMethod(CodecDataObject.class,
+ "codecMember", AtomicReferenceFieldUpdater.class, NodeContextSupplier.class);
+ private static final StackManipulation CODEC_MEMBER_KEY = invokeMethod(CodecDataObject.class,
+ "codecMember", AtomicReferenceFieldUpdater.class, IdentifiableItemCodec.class);
+
+ private static final StackManipulation ARRAYS_EQUALS = invokeMethod(Arrays.class, "equals",
+ byte[].class, byte[].class);
+ private static final StackManipulation OBJECTS_EQUALS = invokeMethod(Objects.class, "equals",
+ Object.class, Object.class);
+ private static final StackManipulation HELPER_ADD = invokeMethod(ToStringHelper.class, "add",
+ String.class, Object.class);
+
+ private static final StackManipulation FIRST_ARG_REF = MethodVariableAccess.REFERENCE.loadFrom(1);
+
+ private static final int PROT_FINAL = Opcodes.ACC_PROTECTED | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
+ private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
+
+ private static final Builder<?> CDO;
+ private static final Builder<?> ACDO;
+
+ static {
+ final ByteBuddy bb = new ByteBuddy();
+ CDO = bb.subclass(CodecDataObject.class).visit(ByteBuddyUtils.computeFrames()).modifiers(PUB_FINAL);
+ ACDO = bb.subclass(AugmentableCodecDataObject.class).visit(ByteBuddyUtils.computeFrames()).modifiers(PUB_FINAL);
+ }
+
+ private final ImmutableMap<Method, NodeContextSupplier> properties;
+ private final Entry<Method, IdentifiableItemCodec> keyMethod;
+ private final Builder<?> template;
+
+ private CodecDataObjectGenerator(final Builder<?> template,
+ final ImmutableMap<Method, NodeContextSupplier> properties,
+ final @Nullable Entry<Method, IdentifiableItemCodec> keyMethod) {
+ this.template = requireNonNull(template);
+ this.properties = requireNonNull(properties);
+ this.keyMethod = keyMethod;
+ }
+
+ static <D extends DataObject, T extends CodecDataObject<T>> Class<T> generate(final CodecClassLoader loader,
+ final Class<D> bindingInterface, final ImmutableMap<Method, NodeContextSupplier> properties,
+ final Entry<Method, IdentifiableItemCodec> keyMethod) {
+ return loader.generateClass(bindingInterface, "codecImpl",
+ new CodecDataObjectGenerator<>(CDO, properties, keyMethod));
+ }
+
+ static <D extends DataObject, T extends CodecDataObject<T>> Class<T> generateAugmentable(
+ final CodecClassLoader loader, final Class<D> bindingInterface,
+ final ImmutableMap<Method, NodeContextSupplier> properties,
+ final Entry<Method, IdentifiableItemCodec> keyMethod) {
+ return loader.generateClass(bindingInterface, "codecImpl",
+ new CodecDataObjectGenerator<>(ACDO, properties, keyMethod));
+ }
+
+ @Override
+ public GeneratorResult<T> generateClass(final CodecClassLoader loeader, final String fqcn,
+ final Class<?> bindingInterface) {
+ LOG.trace("Generating class {}", fqcn);
+
+ @SuppressWarnings("unchecked")
+ Builder<T> builder = (Builder<T>) template.name(fqcn).implement(bindingInterface);
+
+ for (Method method : properties.keySet()) {
+ LOG.trace("Generating for method {}", method);
+ final String methodName = method.getName();
+ final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType());
+ builder = builder.defineMethod(methodName, retType, PUB_FINAL)
+ .intercept(new MethodImplementation(BB_NCS, BRIDGE_RESOLVE, CODEC_MEMBER, methodName, retType));
+ }
+
+ if (keyMethod != null) {
+ LOG.trace("Generating for key {}", keyMethod);
+ final Method method = keyMethod.getKey();
+ final String methodName = method.getName();
+ final TypeDescription retType = TypeDescription.ForLoadedType.of(method.getReturnType());
+ builder = builder.defineMethod(methodName, retType, PUB_FINAL)
+ .intercept(new MethodImplementation(BB_IIC, BRIDGE_RESOLVE_KEY, CODEC_MEMBER_KEY, methodName,
+ retType));
+ }
+
+ // Index all property methods, turning them into "getFoo()" invocations, retaining order. We will be using
+ // those invocations in each of the three methods. Note that we do not glue the invocations to 'this', as we
+ // will be invoking them on 'other' in codecEquals()
+ final ImmutableMap<StackManipulation, Method> methods = Maps.uniqueIndex(properties.keySet(),
+ ByteBuddyUtils::invokeMethod);
+
+ // Final bits:
+ return GeneratorResult.of(builder
+ // codecHashCode() ...
+ .defineMethod("codecHashCode", BB_INT, PROT_FINAL)
+ .intercept(new Implementation.Simple(new CodecHashCode(methods)))
+ // ... codecEquals() ...
+ .defineMethod("codecEquals", BB_BOOLEAN, PROT_FINAL).withParameter(BB_DATAOBJECT)
+ .intercept(codecEquals(methods))
+ // ... and codecFillToString() ...
+ .defineMethod("codecFillToString", BB_HELPER, PROT_FINAL).withParameter(BB_HELPER)
+ .intercept(codecFillToString(methods))
+ // ... and build it
+ .make());
+ }
+
+ @Override
+ public Class<T> customizeLoading(final @NonNull Supplier<Class<T>> loader) {
+ final CodecDataObjectGenerator<?> prev = CodecDataObjectBridge.setup(this);
+ try {
+ final Class<T> result = loader.get();
+
+ /*
+ * This a bit of magic to support NodeContextSupplier constants. These constants need to be resolved while
+ * we have the information needed to find them -- that information is being held in this instance and we
+ * leak it to a thread-local variable held by CodecDataObjectBridge.
+ *
+ * By default the JVM will defer class initialization to first use, which unfortunately is too late for
+ * us, and hence we need to force class to initialize.
+ */
+ try {
+ Class.forName(result.getName(), true, result.getClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new LinkageError("Failed to find newly-defined " + result, e);
+ }
+
+ return result;
+ } finally {
+ CodecDataObjectBridge.tearDown(prev);
+ }
+ }
+
+ @NonNull NodeContextSupplier resolve(final @NonNull String methodName) {
+ final Optional<Entry<Method, NodeContextSupplier>> found = properties.entrySet().stream()
+ .filter(entry -> methodName.equals(entry.getKey().getName())).findAny();
+ verify(found.isPresent(), "Failed to find property for %s in %s", methodName, this);
+ return verifyNotNull(found.get().getValue());
+ }
+
+ @NonNull IdentifiableItemCodec resolveKey(final @NonNull String methodName) {
+ return verifyNotNull(verifyNotNull(keyMethod, "No key method attached for %s in %s", methodName, this)
+ .getValue());
+ }
+
+ private static Implementation codecEquals(final ImmutableMap<StackManipulation, Method> properties) {
+ // Label for 'return false;'
+ final Label falseLabel = new Label();
+ // Condition for 'if (!...)'
+ final StackManipulation ifFalse = ByteBuddyUtils.ifEq(falseLabel);
+
+ final List<StackManipulation> manipulations = new ArrayList<>(properties.size() * 6 + 5);
+ for (Entry<StackManipulation, Method> entry : properties.entrySet()) {
+ // if (!java.util.(Objects|Arrays).equals(getFoo(), other.getFoo())) {
+ // return false;
+ // }
+ manipulations.add(THIS);
+ manipulations.add(entry.getKey());
+ manipulations.add(FIRST_ARG_REF);
+ manipulations.add(entry.getKey());
+ manipulations.add(entry.getValue().getReturnType().isArray() ? ARRAYS_EQUALS : OBJECTS_EQUALS);
+ manipulations.add(ifFalse);
+ }
+
+ // return true;
+ manipulations.add(IntegerConstant.ONE);
+ manipulations.add(MethodReturn.INTEGER);
+ // L0: return false;
+ manipulations.add(ByteBuddyUtils.markLabel(falseLabel));
+ manipulations.add(IntegerConstant.ZERO);
+ manipulations.add(MethodReturn.INTEGER);
+
+ return new Implementation.Simple(manipulations.toArray(new StackManipulation[0]));
+ }
+
+ private static Implementation codecFillToString(final ImmutableMap<StackManipulation, Method> properties) {
+ final List<StackManipulation> manipulations = new ArrayList<>(properties.size() * 4 + 2);
+ // push 'return helper' to stack...
+ manipulations.add(FIRST_ARG_REF);
+ for (Entry<StackManipulation, Method> entry : properties.entrySet()) {
+ // .add("getFoo", getFoo())
+ manipulations.add(new TextConstant(entry.getValue().getName()));
+ manipulations.add(THIS);
+ manipulations.add(entry.getKey());
+ manipulations.add(HELPER_ADD);
+ }
+ // ... execute 'return helper'
+ manipulations.add(MethodReturn.REFERENCE);
+
+ return new Implementation.Simple(manipulations.toArray(new StackManipulation[0]));
+ }
+
+ private static final class MethodImplementation implements Implementation {
+ private static final Generic BB_ARFU = TypeDefinition.Sort.describe(AtomicReferenceFieldUpdater.class);
+ private static final Generic BB_OBJECT = TypeDefinition.Sort.describe(Object.class);
+ private static final StackManipulation OBJECT_CLASS = ClassConstant.of(TypeDescription.OBJECT);
+ private static final StackManipulation ARFU_NEWUPDATER = invokeMethod(AtomicReferenceFieldUpdater.class,
+ "newUpdater", Class.class, Class.class, String.class);
+
+ private static final int PRIV_CONST = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
+ | Opcodes.ACC_SYNTHETIC;
+ private static final int PRIV_VOLATILE = Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE | Opcodes.ACC_SYNTHETIC;
+
+ private final Generic contextType;
+ private final StackManipulation resolveMethod;
+ private final StackManipulation codecMember;
+ private final TypeDescription retType;
+
+ // getFoo
+ private final String methodName;
+ // getFoo$$$A
+ private final String arfuName;
+ // getFoo$$$C
+ private final String contextName;
+
+ MethodImplementation(final Generic contextType, final StackManipulation resolveMethod,
+ final StackManipulation codecMember, final String methodName, final TypeDescription retType) {
+ this.contextType = requireNonNull(contextType);
+ this.resolveMethod = requireNonNull(resolveMethod);
+ this.codecMember = requireNonNull(codecMember);
+ this.methodName = requireNonNull(methodName);
+ this.retType = requireNonNull(retType);
+ this.arfuName = methodName + "$$$A";
+ this.contextName = methodName + "$$$C";
+ }
+
+ @Override
+ public InstrumentedType prepare(final InstrumentedType instrumentedType) {
+ final InstrumentedType tmp = instrumentedType
+ // private static final AtomicReferenceFieldUpdater<This, Object> getFoo$$$A;
+ .withField(new FieldDescription.Token(arfuName, PRIV_CONST, BB_ARFU))
+ // private static final <CONTEXT_TYPE> getFoo$$$C;
+ .withField(new FieldDescription.Token(contextName, PRIV_CONST, contextType))
+ // private volatile Object getFoo;
+ .withField(new FieldDescription.Token(methodName, PRIV_VOLATILE, BB_OBJECT));
+
+ // "getFoo"
+ final TextConstant methodNameText = new TextConstant(methodName);
+
+ return tmp
+ .withInitializer(new ByteCodeAppender.Simple(
+ // getFoo$$$A = AtomicReferenceFieldUpdater.newUpdater(This.class, Object.class, "getFoo");
+ ClassConstant.of(tmp),
+ OBJECT_CLASS,
+ methodNameText,
+ ARFU_NEWUPDATER,
+ putField(tmp, arfuName),
+ // getFoo$$$C = CodecDataObjectBridge.<RESOLVE_METHOD>("getFoo");
+ methodNameText,
+ resolveMethod,
+ putField(tmp, contextName)));
+ }
+
+ @Override
+ public ByteCodeAppender appender(final Target implementationTarget) {
+ final TypeDescription instrumentedType = implementationTarget.getInstrumentedType();
+ return new ByteCodeAppender.Simple(
+ // return (FooType) codecMember(getFoo$$$A, getFoo$$$C);
+ THIS,
+ getField(instrumentedType, arfuName),
+ getField(instrumentedType, contextName),
+ codecMember,
+ TypeCasting.to(retType),
+ MethodReturn.REFERENCE);
+ }
+ }
+
+ private static final class CodecHashCode implements ByteCodeAppender {
+ private static final StackManipulation THIRTY_ONE = IntegerConstant.forValue(31);
+ private static final StackManipulation LOAD_RESULT = MethodVariableAccess.INTEGER.loadFrom(1);
+ private static final StackManipulation STORE_RESULT = MethodVariableAccess.INTEGER.storeAt(1);
+ private static final StackManipulation ARRAYS_HASHCODE = invokeMethod(Arrays.class, "hashCode", byte[].class);
+ private static final StackManipulation OBJECTS_HASHCODE = invokeMethod(Objects.class, "hashCode", Object.class);
+
+ private final ImmutableMap<StackManipulation, Method> properties;
+
+ CodecHashCode(final ImmutableMap<StackManipulation, Method> properties) {
+ this.properties = requireNonNull(properties);
+ }
+
+ @Override
+ public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
+ final MethodDescription instrumentedMethod) {
+ final List<StackManipulation> manipulations = new ArrayList<>(properties.size() * 8 + 4);
+ // int result = 1;
+ manipulations.add(IntegerConstant.ONE);
+ manipulations.add(STORE_RESULT);
+
+ for (Entry<StackManipulation, Method> entry : properties.entrySet()) {
+ // result = 31 * result + java.util.(Objects,Arrays).hashCode(getFoo());
+ manipulations.add(THIRTY_ONE);
+ manipulations.add(LOAD_RESULT);
+ manipulations.add(Multiplication.INTEGER);
+ manipulations.add(THIS);
+ manipulations.add(entry.getKey());
+ manipulations.add(entry.getValue().getReturnType().isArray() ? ARRAYS_HASHCODE : OBJECTS_HASHCODE);
+ manipulations.add(Addition.INTEGER);
+ manipulations.add(STORE_RESULT);
+ }
+ // return result;
+ manipulations.add(LOAD_RESULT);
+ manipulations.add(MethodReturn.INTEGER);
+
+ StackManipulation.Size operandStackSize = new StackManipulation.Compound(manipulations)
+ .apply(methodVisitor, implementationContext);
+ return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize() + 1);
+ }
+ }
+}
import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
+import com.google.common.annotations.Beta;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
-import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-import javassist.CannotCompileException;
-import javassist.CtClass;
-import javassist.NotFoundException;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
import org.opendaylight.mdsal.binding.generator.api.ClassLoadingStrategy;
import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
import org.opendaylight.mdsal.binding.model.api.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeContainer & WithStatus>
+/**
+ * This class is an implementation detail. It is public only due to technical reasons and may change at any time.
+ */
+@Beta
+public abstract class DataObjectCodecContext<D extends DataObject, T extends DataNodeContainer & WithStatus>
extends DataContainerCodecContext<D, T> {
private static final class Augmentations implements Immutable {
final ImmutableMap<YangInstanceIdentifier.PathArgument, DataContainerCodecPrototype<?>> byYang;
DataObjectCodecContext.class, NormalizedNodeContainer.class);
private static final Comparator<Method> METHOD_BY_ALPHABET = Comparator.comparing(Method::getName);
private static final Augmentations EMPTY_AUGMENTATIONS = new Augmentations(ImmutableMap.of(), ImmutableMap.of());
- private static final CtClass SUPERCLASS = StaticClassPool.findClass(CodecDataObject.class);
- private static final CtClass AUGMENTABLE_SUPERCLASS = StaticClassPool.findClass(
- AugmentableCodecDataObject.class);
private final ImmutableMap<String, ValueNodeCodecContext> leafChild;
private final ImmutableMap<YangInstanceIdentifier.PathArgument, NodeContextSupplier> byYang;
byBindingArgClassBuilder.putAll(byStreamClass);
this.byBindingArgClass = ImmutableMap.copyOf(byBindingArgClassBuilder);
- final CtClass superClass;
+ final Class<? extends CodecDataObject<?>> generatedClass;
final MethodType ctorType;
if (Augmentable.class.isAssignableFrom(bindingClass)) {
this.possibleAugmentations = factory().getRuntimeContext().getAvailableAugmentationTypes(getSchema());
- superClass = AUGMENTABLE_SUPERCLASS;
+ generatedClass = CodecDataObjectGenerator.generateAugmentable(prototype.getFactory().getLoader(),
+ bindingClass, propBuilder.build(), keyMethod);
ctorType = AUGMENTABLE_CONSTRUCTOR_TYPE;
} else {
this.possibleAugmentations = ImmutableMap.of();
- superClass = SUPERCLASS;
+ generatedClass = CodecDataObjectGenerator.generate(prototype.getFactory().getLoader(), bindingClass,
+ propBuilder.build(), keyMethod);
ctorType = CONSTRUCTOR_TYPE;
}
reloadAllAugmentations();
- final Class<?> generatedClass;
- try {
- generatedClass = prototype.getFactory().getLoader().generateSubclass(superClass, bindingClass, "codecImpl",
- new CodecDataObjectCustomizer(propBuilder.build(), keyMethod));
- } catch (CannotCompileException | IOException | NotFoundException e) {
- throw new LinkageError("Failed to generated class for " + bindingClass, e);
- }
-
final MethodHandle ctor;
try {
ctor = MethodHandles.publicLookup().findConstructor(generatedClass, ctorType);
writer.endNode();
}
-
private static void commonStreamLeafset(final BindingStreamEventWriter writer, final List<?> value)
throws IOException {
for (Object entry : value) {
+++ /dev/null
-/*
- * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.mdsal.binding.dom.codec.impl;
-
-import static com.google.common.base.Verify.verifyNotNull;
-
-import com.google.common.annotations.Beta;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-
-/**
- * Bridge for initializing {@link DataObjectStreamer} instance constants during class loading time. This class is public
- * only due to implementation restrictions and can change at any time.
- */
-// FIXME: this bridge is only necessary to work around Javassist compiler resolution of dependencies. If we switch to
-// a more bytecode-oriented framework, this bridge becomes superfluous.
-//
-@Beta
-public final class DataObjectStreamerBridge {
- private static final ThreadLocal<DataObjectStreamerCustomizer> CURRENT_CUSTOMIZER = new ThreadLocal<>();
-
- private DataObjectStreamerBridge() {
-
- }
-
- public static @NonNull DataObjectStreamer<?> resolve(final @NonNull String methodName) {
- return verifyNotNull(CURRENT_CUSTOMIZER.get(), "No customizer attached").resolve(methodName);
- }
-
- static @Nullable DataObjectStreamerCustomizer setup(final @NonNull DataObjectStreamerCustomizer next) {
- final DataObjectStreamerCustomizer prev = CURRENT_CUSTOMIZER.get();
- CURRENT_CUSTOMIZER.set(verifyNotNull(next));
- return prev;
- }
-
- static void tearDown(final @Nullable DataObjectStreamerCustomizer prev) {
- if (prev == null) {
- CURRENT_CUSTOMIZER.remove();
- } else {
- CURRENT_CUSTOMIZER.set(prev);
- }
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.mdsal.binding.dom.codec.impl;
-
-import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.annotations.Beta;
-import com.google.common.base.Supplier;
-import com.google.common.collect.ImmutableMap;
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import javassist.CannotCompileException;
-import javassist.CtClass;
-import javassist.CtField;
-import javassist.CtMethod;
-import javassist.Modifier;
-import javassist.NotFoundException;
-import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
-import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
-import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
-import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.Customizer;
-import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
-import org.opendaylight.mdsal.binding.dom.codec.util.BindingSchemaMapping;
-import org.opendaylight.mdsal.binding.model.api.GeneratedType;
-import org.opendaylight.mdsal.binding.model.api.MethodSignature;
-import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
-import org.opendaylight.mdsal.binding.model.api.Type;
-import org.opendaylight.mdsal.binding.spec.naming.BindingMapping;
-import org.opendaylight.yangtools.yang.binding.Augmentable;
-import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
-import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
-import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
-import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
-import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Beta
-public final class DataObjectStreamerCustomizer implements Customizer {
- static final CtClass CT_DOS = StaticClassPool.findClass(DataObjectStreamer.class);
- static final String INSTANCE_FIELD = "INSTANCE";
-
- private static final Logger LOG = LoggerFactory.getLogger(DataObjectStreamerCustomizer.class);
- private static final String UNKNOWN_SIZE = BindingStreamEventWriter.class.getName() + ".UNKNOWN_SIZE";
-
- private static final CtClass CT_VOID = StaticClassPool.findClass(void.class);
- private static final CtClass[] SERIALIZE_ARGS = new CtClass[] {
- StaticClassPool.findClass(DataObjectSerializerRegistry.class),
- StaticClassPool.findClass(DataObject.class),
- StaticClassPool.findClass(BindingStreamEventWriter.class)
- };
-
- private final Map<String, Class<? extends DataObject>> constants = new HashMap<>();
- private final ImmutableMap<String, Type> props;
- private final CodecContextFactory registry;
- private final DataNodeContainer schema;
- private final String startEvent;
- private final Class<?> type;
-
- DataObjectStreamerCustomizer(final CodecContextFactory registry, final GeneratedType genType,
- final DataNodeContainer schema, final Class<?> type, final String startEvent) {
- this.registry = requireNonNull(registry);
- this.schema = requireNonNull(schema);
- this.type = requireNonNull(type);
- this.startEvent = requireNonNull(startEvent);
- props = collectAllProperties(genType);
- }
-
- public static DataObjectStreamerCustomizer create(final CodecContextFactory registry, final Class<?> type) {
- final Entry<GeneratedType, WithStatus> typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
- final WithStatus schema = typeAndSchema.getValue();
-
- final String startEvent;
- if (schema instanceof ContainerSchemaNode || schema instanceof NotificationDefinition) {
- startEvent = "startContainerNode(" + type.getName() + ".class," + UNKNOWN_SIZE;
- } else if (schema instanceof ListSchemaNode) {
- final ListSchemaNode casted = (ListSchemaNode) schema;
- if (!casted.getKeyDefinition().isEmpty()) {
- startEvent = "startMapEntryNode(obj." + BindingMapping.IDENTIFIABLE_KEY_NAME + "(), " + UNKNOWN_SIZE;
- } else {
- startEvent = "startUnkeyedListItem(" + UNKNOWN_SIZE;
- }
- } else if (schema instanceof AugmentationSchemaNode) {
- startEvent = "startAugmentationNode(" + type.getName() + ".class";
- } else if (schema instanceof CaseSchemaNode) {
- startEvent = "startCase(" + type.getName() + ".class, " + UNKNOWN_SIZE;
- } else {
- throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
- }
-
- return new DataObjectStreamerCustomizer(registry, typeAndSchema.getKey(), (DataNodeContainer) schema, type,
- startEvent);
- }
-
- @Override
- public List<Class<?>> customize(final CodecClassLoader loader, final CtClass bindingClass, final CtClass generated)
- throws CannotCompileException, NotFoundException, IOException {
- LOG.trace("Definining streamer {}", generated.getName());
-
- final CtField instanceField = new CtField(generated, INSTANCE_FIELD, generated);
- instanceField.setModifiers(Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
- generated.addField(instanceField, "new " + generated.getName() + "()");
-
- // This results in a body
- final String objType = bindingClass.getName();
- final StringBuilder sb = new StringBuilder()
- .append("{\n")
- .append("final ").append(objType).append(" obj = (").append(objType).append(") $2;\n")
- .append("$3.").append(startEvent).append(");\n");
-
- final List<Class<?>> dependencies = emitChildren(sb, loader, generated);
- if (Augmentable.class.isAssignableFrom(type)) {
- sb.append("streamAugmentations($1, $3, obj);\n");
- }
-
- sb.append("$3.endNode();\n")
- .append('}');
-
- final CtMethod serialize = new CtMethod(CT_VOID, "serialize", SERIALIZE_ARGS, generated);
- serialize.setModifiers(Modifier.PUBLIC);
- serialize.setBody(sb.toString());
- generated.addMethod(serialize);
-
- generated.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
- LOG.trace("Definition of {} done", generated.getName());
-
- return dependencies;
- }
-
- @Override
- public Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
- if (constants.isEmpty()) {
- return loader.get();
- }
-
- final DataObjectStreamerCustomizer prev = DataObjectStreamerBridge.setup(this);
- try {
- final Class<?> result = loader.get();
-
- /*
- * This a bit of magic to support DataObjectStreamer constants. These constants need to be resolved while
- * we have the information needed to find them -- that information is being held in this instance and we
- * leak it to a thread-local variable held by DataObjectStreamerBridge.
- *
- * By default the JVM will defer class initialization to first use, which unfortunately is too late for
- * us, and hence we need to force class to initialize.
- */
- try {
- Class.forName(result.getName(), true, result.getClassLoader());
- } catch (ClassNotFoundException e) {
- throw new LinkageError("Failed to find newly-defined " + result, e);
- }
-
- return result;
- } finally {
- DataObjectStreamerBridge.tearDown(prev);
- }
- }
-
- @NonNull DataObjectStreamer<?> resolve(final @NonNull String methodName) {
- final Class<? extends DataObject> target = verifyNotNull(constants.get(methodName), "Cannot resolve type of %s",
- methodName);
- return verifyNotNull(registry.getDataObjectSerializer(target), "Cannot find serializer for %s", target);
- }
-
- private List<Class<?>> emitChildren(final StringBuilder sb, final CodecClassLoader loader,
- final CtClass generated) throws CannotCompileException {
- final List<Class<?>> dependencies = new ArrayList<>();
-
- for (final DataSchemaNode schemaChild : schema.getChildNodes()) {
- if (!schemaChild.isAugmenting()) {
- final String getterName = BindingSchemaMapping.getGetterMethodName(schemaChild);
- final Method getter;
- try {
- getter = type.getMethod(getterName);
- } catch (NoSuchMethodException e) {
- throw new IllegalStateException("Failed to find getter " + getterName, e);
- }
-
- final Class<?> dependency = emitChild(sb, loader, generated, getterName, getter.getReturnType(),
- schemaChild);
- if (dependency != null) {
- LOG.trace("Require dependency {}", dependency);
- dependencies.add(dependency);
- }
- }
- }
-
- return dependencies;
- }
-
- private @Nullable Class<?> emitChild(final StringBuilder sb, final CodecClassLoader loader, final CtClass generated,
- final String getterName, final Class<?> returnType, final DataSchemaNode child)
- throws CannotCompileException {
- if (child instanceof LeafSchemaNode) {
- sb.append("streamLeaf($3, \"").append(child.getQName().getLocalName()).append("\", obj.")
- .append(getterName).append("());\n");
- return null;
- }
- if (child instanceof ContainerSchemaNode) {
- final Class<? extends DataObject> itemClass = returnType.asSubclass(DataObject.class);
- final String constField = declareDependency(generated, getterName, itemClass);
-
- sb.append("streamContainer(").append(constField).append(", $1, $3, obj.").append(getterName)
- .append("());\n");
- return registry.getDataObjectSerializer(itemClass).getClass();
- }
- if (child instanceof ListSchemaNode) {
- final Type childType = props.get(getterName);
- verify(childType instanceof ParameterizedType, "Unexpected type %s for %s", childType, getterName);
- final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
- final Class<?> valueClass;
- try {
- valueClass = loader.loadClass(valueType.getFullyQualifiedName());
- } catch (ClassNotFoundException e) {
- throw new LinkageError("Failed to load " + valueType, e);
- }
-
- verify(DataObject.class.isAssignableFrom(valueClass), "Value type %s of %s is not a DataObject", valueClass,
- returnType);
- final Class<? extends DataObject> itemClass = valueClass.asSubclass(DataObject.class);
- final ListSchemaNode casted = (ListSchemaNode) child;
-
- sb.append("stream");
- if (casted.getKeyDefinition().isEmpty()) {
- sb.append("List");
- } else {
- if (casted.isUserOrdered()) {
- sb.append("Ordered");
- }
- sb.append("Map");
- }
-
- final String constField = declareDependency(generated, getterName, itemClass);
- sb.append('(').append(valueClass.getName()).append(".class, ").append(constField).append(", $1, $3, obj.")
- .append(getterName).append("());\n");
- return registry.getDataObjectSerializer(itemClass).getClass();
- }
- if (child instanceof AnyXmlSchemaNode) {
- sb.append("streamAnyxml($3, \"").append(child.getQName().getLocalName()).append("\", obj.")
- .append(getterName).append("());\n");
- return null;
- }
- if (child instanceof LeafListSchemaNode) {
- sb.append("stream");
- if (((LeafListSchemaNode) child).isUserOrdered()) {
- sb.append("Ordered");
- }
- sb.append("LeafList($3, \"").append(child.getQName().getLocalName()).append("\", obj.")
- .append(getterName).append("());\n");
- return null;
- }
- if (child instanceof ChoiceSchemaNode) {
- sb.append("streamChoice(").append(returnType.getName()).append(".class, $1, $3, obj.").append(getterName)
- .append("());\n");
- return null;
- }
-
- LOG.debug("Ignoring {} due to unhandled schema {}", getterName, child);
- return null;
- }
-
- /*
- * Javassist not quite helpful in our environment. We really want to output plain bytecode so that it links
- * using normal ClassLoader mechanics (supported via CodecClassLoader), but Javassist's compiler really gets in
- * the way of keeping things simple by requiring CtClass references to dependencies at the the time we set the
- * implementation body. In order to side-step those requirements, we rely on defining references to our dependencies
- * as constants and fill them up via customizeLoading().
- *
- * This method defines the constants for later use. Should we migrate to a more byte-code oriented framework
- * (like ByteBuddy), we will pay some cost in assembling the method bodies, we can ditch the constants, as we
- * provide INSTANCE_FIELD which can readily be reused and CodecClassLoader will resolve the dependencies without
- * any problems.
- */
- private String declareDependency(final CtClass generated, final String getterName,
- final Class<? extends DataObject> bindingClass) throws CannotCompileException {
- final String fieldName = getterName + "_STREAMER";
-
- final CtField instanceField = new CtField(CT_DOS, fieldName, generated);
- instanceField.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL);
- generated.addField(instanceField,
- DataObjectStreamerBridge.class.getName() + ".resolve(\"" + getterName + "\")");
-
- verify(constants.put(getterName, bindingClass) == null, "Duplicate dependency for %s", getterName);
- return fieldName;
- }
-
- private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
- final Map<String, Type> props = new HashMap<>();
- collectAllProperties(type, props);
- return ImmutableMap.copyOf(props);
- }
-
- private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
- for (final MethodSignature definition : type.getMethodDefinitions()) {
- hashMap.put(definition.getName(), definition.getReturnType());
- }
- for (final Type parent : type.getImplements()) {
- if (parent instanceof GeneratedType) {
- collectAllProperties((GeneratedType) parent, hashMap);
- }
- }
- }
-}
--- /dev/null
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.binding.dom.codec.impl;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.getField;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.invokeMethod;
+import static org.opendaylight.mdsal.binding.dom.codec.impl.ByteBuddyUtils.putField;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.field.FieldDescription;
+import net.bytebuddy.description.method.MethodDescription;
+import net.bytebuddy.description.type.TypeDefinition;
+import net.bytebuddy.description.type.TypeDefinition.Sort;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.description.type.TypeDescription.Generic;
+import net.bytebuddy.dynamic.DynamicType.Builder;
+import net.bytebuddy.dynamic.scaffold.InstrumentedType;
+import net.bytebuddy.implementation.Implementation;
+import net.bytebuddy.implementation.Implementation.Context;
+import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
+import net.bytebuddy.implementation.bytecode.Duplication;
+import net.bytebuddy.implementation.bytecode.StackManipulation;
+import net.bytebuddy.implementation.bytecode.TypeCreation;
+import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
+import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
+import net.bytebuddy.implementation.bytecode.constant.TextConstant;
+import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
+import net.bytebuddy.implementation.bytecode.member.MethodReturn;
+import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
+import net.bytebuddy.jar.asm.MethodVisitor;
+import net.bytebuddy.jar.asm.Opcodes;
+import net.bytebuddy.matcher.ElementMatchers;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.ClassGenerator;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
+import org.opendaylight.mdsal.binding.dom.codec.util.BindingSchemaMapping;
+import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.model.api.MethodSignature;
+import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
+import org.opendaylight.mdsal.binding.model.api.Type;
+import org.opendaylight.yangtools.yang.binding.Augmentable;
+import org.opendaylight.yangtools.yang.binding.BindingStreamEventWriter;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
+import org.opendaylight.yangtools.yang.binding.DataObject;
+import org.opendaylight.yangtools.yang.binding.DataObjectSerializerRegistry;
+import org.opendaylight.yangtools.yang.binding.Identifiable;
+import org.opendaylight.yangtools.yang.binding.Identifier;
+import org.opendaylight.yangtools.yang.model.api.AnyXmlSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
+import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
+import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class DataObjectStreamerGenerator<T extends DataObjectStreamer<?>> implements ClassGenerator<T> {
+ static final String INSTANCE_FIELD = "INSTANCE";
+
+ private static final Logger LOG = LoggerFactory.getLogger(DataObjectStreamerGenerator.class);
+ private static final Generic BB_VOID = TypeDefinition.Sort.describe(void.class);
+ private static final Generic BB_DATAOBJECT = TypeDefinition.Sort.describe(DataObject.class);
+ private static final Generic BB_DOSR = TypeDefinition.Sort.describe(DataObjectSerializerRegistry.class);
+ private static final Generic BB_BESV = TypeDefinition.Sort.describe(BindingStreamEventWriter.class);
+ private static final Generic BB_IOX = TypeDefinition.Sort.describe(IOException.class);
+
+ private static final int PUB_FINAL = Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC;
+ private static final int PUB_CONST = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL
+ | Opcodes.ACC_SYNTHETIC;
+
+ private static final Builder<?> TEMPLATE = new ByteBuddy().subclass(DataObjectStreamer.class)
+ .modifiers(PUB_FINAL);
+
+ private static final StackManipulation REG = MethodVariableAccess.REFERENCE.loadFrom(1);
+ private static final StackManipulation OBJ = MethodVariableAccess.REFERENCE.loadFrom(2);
+ private static final StackManipulation STREAM = MethodVariableAccess.REFERENCE.loadFrom(3);
+ private static final StackManipulation UNKNOWN_SIZE = IntegerConstant.forValue(
+ BindingStreamEventWriter.UNKNOWN_SIZE);
+
+ private static final StackManipulation START_AUGMENTATION_NODE = invokeMethod(BindingStreamEventWriter.class,
+ "startAugmentationNode", Class.class);
+ private static final StackManipulation START_CASE = invokeMethod(BindingStreamEventWriter.class,
+ "startCase", Class.class, int.class);
+ private static final StackManipulation START_CONTAINER_NODE = invokeMethod(BindingStreamEventWriter.class,
+ "startContainerNode", Class.class, int.class);
+ private static final StackManipulation END_NODE = invokeMethod(BindingStreamEventWriter.class,
+ "endNode");
+
+ // startMapEntryNode(obj.key(), UNKNOWN_SIZE);
+ private static final StackManipulation START_MAP_ENTRY_NODE = new StackManipulation.Compound(
+ OBJ,
+ invokeMethod(Identifiable.class, "key"),
+ UNKNOWN_SIZE,
+ invokeMethod(BindingStreamEventWriter.class, "startMapEntryNode", Identifier.class, int.class));
+
+ // startUnkeyedListItem(UNKNOWN_SIZE);
+ private static final StackManipulation START_UNKEYED_LIST_ITEM = new StackManipulation.Compound(
+ UNKNOWN_SIZE,
+ invokeMethod(BindingStreamEventWriter.class, "startUnkeyedListItem", int.class));
+
+ private static final StackManipulation STREAM_ANYXML = invokeMethod(DataObjectStreamer.class,
+ "streamAnyxml", BindingStreamEventWriter.class, String.class, Object.class);
+ private static final StackManipulation STREAM_CHOICE = invokeMethod(DataObjectStreamer.class,
+ "streamChoice", Class.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
+ DataContainer.class);
+ private static final StackManipulation STREAM_CONTAINER = invokeMethod(DataObjectStreamer.class,
+ "streamContainer", DataObjectStreamer.class, DataObjectSerializerRegistry.class, BindingStreamEventWriter.class,
+ DataObject.class);
+ private static final StackManipulation STREAM_LEAF = invokeMethod(DataObjectStreamer.class,
+ "streamLeaf", BindingStreamEventWriter.class, String.class, Object.class);
+ private static final StackManipulation STREAM_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
+ "streamLeafList",
+ BindingStreamEventWriter.class, String.class, List.class);
+ private static final StackManipulation STREAM_ORDERED_LEAF_LIST = invokeMethod(DataObjectStreamer.class,
+ "streamOrderedLeafList", BindingStreamEventWriter.class, String.class, List.class);
+ private static final StackManipulation STREAM_LIST = invokeMethod(DataObjectStreamer.class,
+ "streamList", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
+ BindingStreamEventWriter.class, List.class);
+ private static final StackManipulation STREAM_MAP = invokeMethod(DataObjectStreamer.class,
+ "streamMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
+ BindingStreamEventWriter.class, List.class);
+ private static final StackManipulation STREAM_ORDERED_MAP = invokeMethod(DataObjectStreamer.class,
+ "streamOrderedMap", Class.class, DataObjectStreamer.class, DataObjectSerializerRegistry.class,
+ BindingStreamEventWriter.class, List.class);
+
+ // streamAugmentations(reg, stream, obj);
+ private static final StackManipulation STREAM_AUGMENTATIONS = new StackManipulation.Compound(
+ REG,
+ STREAM,
+ OBJ,
+ invokeMethod(DataObjectStreamer.class, "streamAugmentations", DataObjectSerializerRegistry.class,
+ BindingStreamEventWriter.class, Augmentable.class));
+
+ private final CodecContextFactory registry;
+ private final StackManipulation startEvent;
+ private final DataNodeContainer schema;
+ private final Class<?> type;
+ private final GeneratedType genType;
+
+ private DataObjectStreamerGenerator(final CodecContextFactory registry, final GeneratedType genType,
+ final DataNodeContainer schema, final Class<?> type, final StackManipulation startEvent) {
+ this.registry = requireNonNull(registry);
+ this.genType = requireNonNull(genType);
+ this.schema = requireNonNull(schema);
+ this.type = requireNonNull(type);
+ this.startEvent = requireNonNull(startEvent);
+ }
+
+ static Class<? extends DataObjectStreamer<?>> generateStreamer(final CodecClassLoader loader,
+ final CodecContextFactory registry, final Class<?> type) {
+
+ final Entry<GeneratedType, WithStatus> typeAndSchema = registry.getRuntimeContext().getTypeWithSchema(type);
+ final WithStatus schema = typeAndSchema.getValue();
+
+ final StackManipulation startEvent;
+ if (schema instanceof ContainerSchemaNode || schema instanceof NotificationDefinition) {
+ startEvent = classUnknownSizeMethod(START_CONTAINER_NODE, type);
+ } else if (schema instanceof ListSchemaNode) {
+ startEvent = ((ListSchemaNode) schema).getKeyDefinition().isEmpty() ? START_UNKEYED_LIST_ITEM
+ : START_MAP_ENTRY_NODE;
+ } else if (schema instanceof AugmentationSchemaNode) {
+ // startAugmentationNode(Foo.class);
+ startEvent = new StackManipulation.Compound(
+ ClassConstant.of(Sort.describe(type).asErasure()),
+ START_AUGMENTATION_NODE);
+ } else if (schema instanceof CaseSchemaNode) {
+ startEvent = classUnknownSizeMethod(START_CASE, type);
+ } else {
+ throw new UnsupportedOperationException("Schema type " + schema.getClass() + " is not supported");
+ }
+
+ return loader.generateClass(type, "streamer",
+ new DataObjectStreamerGenerator<>(registry, typeAndSchema.getKey(), (DataNodeContainer) schema, type,
+ startEvent));
+ }
+
+ @Override
+ public GeneratorResult<T> generateClass(final CodecClassLoader loader, final String fqcn,
+ final Class<?> bindingInterface) {
+ LOG.trace("Definining streamer {}", fqcn);
+
+ @SuppressWarnings("unchecked")
+ Builder<T> builder = (Builder<T>) TEMPLATE.name(fqcn);
+
+ final ImmutableMap<String, Type> props = collectAllProperties(genType);
+ final List<ChildStream> children = new ArrayList<>(props.size());
+ for (final DataSchemaNode schemaChild : schema.getChildNodes()) {
+ if (!schemaChild.isAugmenting()) {
+ final String getterName = BindingSchemaMapping.getGetterMethodName(schemaChild);
+ final Method getter;
+ try {
+ getter = type.getMethod(getterName);
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException("Failed to find getter " + getterName, e);
+ }
+
+ final ChildStream child = createStream(loader, props, schemaChild, getter);
+ if (child != null) {
+ children.add(child);
+ }
+ }
+ }
+
+ final ImmutableList.Builder<Class<?>> depBuilder = ImmutableList.builder();
+ for (ChildStream child : children) {
+ final Class<?> dependency = child.getDependency();
+ if (dependency != null) {
+ depBuilder.add(dependency);
+ }
+ }
+
+ final GeneratorResult<T> result = GeneratorResult.of(builder
+ .defineMethod("serialize", BB_VOID, PUB_FINAL)
+ .withParameters(BB_DOSR, BB_DATAOBJECT, BB_BESV)
+ .throwing(BB_IOX)
+ .intercept(new SerializeImplementation(bindingInterface, startEvent, children)).make(), depBuilder.build());
+
+ LOG.trace("Definition of {} done", fqcn);
+ return result;
+ }
+
+ private ChildStream createStream(final CodecClassLoader loader, final ImmutableMap<String, Type> props,
+ final DataSchemaNode childSchema, final Method getter) {
+ if (childSchema instanceof LeafSchemaNode) {
+ return qnameChildStream(STREAM_LEAF, getter, childSchema);
+ }
+ if (childSchema instanceof ContainerSchemaNode) {
+ return containerChildStream(getter);
+ }
+ if (childSchema instanceof ListSchemaNode) {
+ final String getterName = getter.getName();
+ final Type childType = props.get(getterName);
+ verify(childType instanceof ParameterizedType, "Unexpected type %s for %s", childType, getterName);
+ final Type valueType = ((ParameterizedType) childType).getActualTypeArguments()[0];
+ final Class<?> valueClass;
+ try {
+ valueClass = loader.loadClass(valueType.getFullyQualifiedName());
+ } catch (ClassNotFoundException e) {
+ throw new LinkageError("Failed to load " + valueType, e);
+ }
+ return listChildStream(getter, valueClass.asSubclass(DataObject.class), (ListSchemaNode) childSchema);
+ }
+ if (childSchema instanceof ChoiceSchemaNode) {
+ return choiceChildStream(getter);
+ }
+ if (childSchema instanceof AnyXmlSchemaNode) {
+ return qnameChildStream(STREAM_ANYXML, getter, childSchema);
+ }
+ if (childSchema instanceof LeafListSchemaNode) {
+ return qnameChildStream(((LeafListSchemaNode) childSchema).isUserOrdered() ? STREAM_ORDERED_LEAF_LIST
+ : STREAM_LEAF_LIST, getter, childSchema);
+ }
+
+ LOG.debug("Ignoring {} due to unhandled schema {}", getter, childSchema);
+ return null;
+ }
+
+ private static ChildStream choiceChildStream(final Method getter) {
+ // streamChoice(Foo.class, reg, stream, obj.getFoo());
+ return new ChildStream(
+ ClassConstant.of(Sort.describe(getter.getReturnType()).asErasure()),
+ REG,
+ STREAM,
+ OBJ,
+ invokeMethod(getter),
+ STREAM_CHOICE);
+ }
+
+ private ChildStream containerChildStream(final Method getter) {
+ final Class<? extends DataObject> itemClass = getter.getReturnType().asSubclass(DataObject.class);
+ final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
+
+ // streamContainer(FooStreamer.INSTANCE, reg, stream, obj.getFoo());
+ return new ChildStream(streamer,
+ streamerInstance(streamer),
+ REG,
+ STREAM,
+ OBJ,
+ invokeMethod(getter),
+ STREAM_CONTAINER);
+ }
+
+ private ChildStream listChildStream(final Method getter, final Class<? extends DataObject> itemClass,
+ final ListSchemaNode childSchema) {
+ final DataObjectStreamer<?> streamer = registry.getDataObjectSerializer(itemClass);
+ final StackManipulation method;
+ if (childSchema.getKeyDefinition().isEmpty()) {
+ method = STREAM_LIST;
+ } else {
+ method = childSchema.isUserOrdered() ? STREAM_ORDERED_MAP : STREAM_MAP;
+ }
+
+ // <METHOD>(Foo.class, FooStreamer.INSTACE, reg, stream, obj.getFoo());
+ return new ChildStream(streamer,
+ ClassConstant.of(Sort.describe(itemClass).asErasure()),
+ streamerInstance(streamer),
+ REG,
+ STREAM,
+ OBJ,
+ invokeMethod(getter),
+ method);
+ }
+
+ private static ChildStream qnameChildStream(final StackManipulation method, final Method getter,
+ final DataSchemaNode schema) {
+ // <METHOD>(stream, "foo", obj.getFoo());
+ return new ChildStream(
+ STREAM,
+ new TextConstant(schema.getQName().getLocalName()),
+ OBJ,
+ invokeMethod(getter),
+ method);
+ }
+
+ private static StackManipulation streamerInstance(final DataObjectStreamer<?> streamer) {
+ try {
+ return getField(streamer.getClass().getDeclaredField(INSTANCE_FIELD));
+ } catch (NoSuchFieldException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static StackManipulation classUnknownSizeMethod(final StackManipulation method, final Class<?> type) {
+ // <METHOD>(Foo.class, UNKNOWN_SIZE);
+ return new StackManipulation.Compound(
+ ClassConstant.of(Sort.describe(type).asErasure()),
+ UNKNOWN_SIZE,
+ method);
+ }
+
+ private static ImmutableMap<String, Type> collectAllProperties(final GeneratedType type) {
+ final Map<String, Type> props = new HashMap<>();
+ collectAllProperties(type, props);
+ return ImmutableMap.copyOf(props);
+ }
+
+ private static void collectAllProperties(final GeneratedType type, final Map<String, Type> hashMap) {
+ for (final MethodSignature definition : type.getMethodDefinitions()) {
+ hashMap.put(definition.getName(), definition.getReturnType());
+ }
+ for (final Type parent : type.getImplements()) {
+ if (parent instanceof GeneratedType) {
+ collectAllProperties((GeneratedType) parent, hashMap);
+ }
+ }
+ }
+
+ private static final class SerializeImplementation implements Implementation {
+ private final List<ChildStream> children;
+ private final StackManipulation startEvent;
+ private final Class<?> bindingInterface;
+
+ SerializeImplementation(final Class<?> bindingInterface, final StackManipulation startEvent,
+ final List<ChildStream> children) {
+ this.bindingInterface = requireNonNull(bindingInterface);
+ this.startEvent = requireNonNull(startEvent);
+ this.children = requireNonNull(children);
+ }
+
+ @Override
+ public InstrumentedType prepare(final InstrumentedType instrumentedType) {
+ return instrumentedType
+ // private static final This INSTANCE = new This();
+ .withField(new FieldDescription.Token(INSTANCE_FIELD, PUB_CONST, instrumentedType.asGenericType()))
+ .withInitializer(InitializeInstanceField.INSTANCE);
+ }
+
+ @Override
+ public ByteCodeAppender appender(final Target implementationTarget) {
+ final List<StackManipulation> manipulations = new ArrayList<>(children.size() + 6);
+
+ // stream.<START_EVENT>(...);
+ manipulations.add(STREAM);
+ manipulations.add(startEvent);
+
+ // ... emit children ...
+ manipulations.addAll(children);
+
+ if (Augmentable.class.isAssignableFrom(bindingInterface)) {
+ // streamAugmentations(reg, stream, obj);
+ manipulations.add(STREAM_AUGMENTATIONS);
+ }
+
+ // stream.endNode();
+ manipulations.add(STREAM);
+ manipulations.add(END_NODE);
+ // return;
+ manipulations.add(MethodReturn.VOID);
+
+ return new ByteCodeAppender.Simple(manipulations);
+ }
+ }
+
+ private static final class ChildStream extends StackManipulation.Compound {
+ private final @Nullable Class<?> dependency;
+
+ ChildStream(final StackManipulation... stackManipulation) {
+ super(stackManipulation);
+ dependency = null;
+ }
+
+ ChildStream(final DataObjectStreamer<?> streamer, final StackManipulation... stackManipulation) {
+ super(stackManipulation);
+ dependency = streamer.getClass();
+ }
+
+ @Nullable Class<?> getDependency() {
+ return dependency;
+ }
+ }
+
+ private enum InitializeInstanceField implements ByteCodeAppender {
+ INSTANCE;
+
+ @Override
+ public Size apply(final MethodVisitor methodVisitor, final Context implementationContext,
+ final MethodDescription instrumentedMethod) {
+ final TypeDescription instrumentedType = implementationContext.getInstrumentedType();
+ StackManipulation.Size operandStackSize = new StackManipulation.Compound(
+ TypeCreation.of(instrumentedType),
+ Duplication.SINGLE,
+ MethodInvocation.invoke(instrumentedType.getDeclaredMethods()
+ .filter(ElementMatchers.isDefaultConstructor()).getOnly().asDefined()),
+ putField(instrumentedType, INSTANCE_FIELD))
+ .apply(methodVisitor, implementationContext);
+ return new Size(operandStackSize.getMaximalSize(), instrumentedMethod.getStackSize());
+ }
+ }
+}
import static java.util.Objects.requireNonNull;
import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import java.io.IOException;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
-import javassist.CannotCompileException;
-import javassist.CtClass;
-import javassist.Modifier;
-import javassist.NotFoundException;
import javax.xml.transform.dom.DOMSource;
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.dynamic.DynamicType.Builder;
+import net.bytebuddy.jar.asm.Opcodes;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingOpaqueObjectCodecTreeNode;
import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader;
-import org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool;
+import org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader.GeneratorResult;
import org.opendaylight.yangtools.concepts.Codec;
import org.opendaylight.yangtools.yang.binding.OpaqueData;
import org.opendaylight.yangtools.yang.binding.OpaqueObject;
}
}
- private static final CtClass SUPERCLASS = StaticClassPool.findClass(CodecOpaqueObject.class);
private static final MethodType CONSTRUCTOR_TYPE = MethodType.methodType(OpaqueObject.class,
OpaqueData.class);
+ @SuppressWarnings("rawtypes")
+ private static final Builder<CodecOpaqueObject> TEMPLATE = new ByteBuddy().subclass(CodecOpaqueObject.class)
+ .modifiers(Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SYNTHETIC);
private final Codec<Object, Object> valueCodec = new Codec<Object, Object>() {
@Override
}
private static MethodHandle createImpl(final CodecClassLoader rootLoader, final Class<?> bindingClass) {
- final Class<?> proxyClass;
- try {
- proxyClass = rootLoader.generateSubclass(SUPERCLASS, bindingClass, "codecImpl",
- (pool, binding, generated) -> {
- generated.addInterface(binding);
- generated.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
- return ImmutableList.of();
- });
- } catch (CannotCompileException | IOException | NotFoundException e) {
- throw new LinkageError("Failed to instantiate prototype for " + bindingClass, e);
- }
+ final Class<?> proxyClass = rootLoader.generateClass(bindingClass, "codecImpl",
+ (loader, fqcn, bindingInterface) -> GeneratorResult.of(TEMPLATE
+ .name(fqcn)
+ .implement(bindingInterface)
+ .make()));
Constructor<?> ctor;
try {
*/
package org.opendaylight.mdsal.binding.dom.codec.loader;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.Beta;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import java.io.File;
import java.io.IOException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collection;
import java.util.HashSet;
-import java.util.List;
+import java.util.Map.Entry;
import java.util.Set;
-import javassist.CannotCompileException;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.LoaderClassPath;
-import javassist.NotFoundException;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.DynamicType.Unloaded;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* <p>In single-classloader environments, obviously, the root loader can load all binding classes, and hence no leaf
* loader is created.
*
- * <p>
- * Each {@link CodecClassLoader} has a {@link ClassPool} attached to it and can perform operations on it. Leaf loaders
- * specify the root loader's ClassPool as their parent, but are configured to lookup classes first in themselves.
- *
* @author Robert Varga
*/
@Beta
public abstract class CodecClassLoader extends ClassLoader {
- /**
- * A customizer allowing a generated class to be modified before it is loader.
- */
- @FunctionalInterface
- public interface Customizer {
+ public interface ClassGenerator<T> {
/**
- * Customize a generated class before it is instantiated in the loader.
+ * Generate a class.
*
- * @param loader CodecClassLoader which will hold the class. It can be used to lookup/instantiate other classes
- * @param bindingClass Binding class for which the customized class is being generated
- * @param generated The class being generated
- * @return A set of generated classes the generated class depends on
- * @throws CannotCompileException if the customizer cannot perform partial compilation
- * @throws NotFoundException if the customizer cannot find a required class
- * @throws IOException if the customizer cannot perform partial loading
+ * @param fqcn Generated class Fully-qualified class name
+ * @param bindingInterface Binding interface for which the class is being generated
+ * @return A result.
*/
- @NonNull List<Class<?>> customize(@NonNull CodecClassLoader loader, @NonNull CtClass bindingClass,
- @NonNull CtClass generated) throws CannotCompileException, NotFoundException, IOException;
+ GeneratorResult<T> generateClass(CodecClassLoader loader, String fqcn, Class<?> bindingInterface);
/**
* Run the specified loader in a customized environment. The environment customizations must be cleaned up by
* @param loader Class loader to execute
* @return Class returned by the loader
*/
- default Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
+ default Class<T> customizeLoading(final @NonNull Supplier<Class<T>> loader) {
return loader.get();
}
}
+ public static final class GeneratorResult<T> {
+ private final @NonNull ImmutableSet<Class<?>> dependecies;
+ private final @NonNull Unloaded<T> result;
+
+ GeneratorResult(final Unloaded<T> result, final ImmutableSet<Class<?>> dependecies) {
+ this.result = requireNonNull(result);
+ this.dependecies = requireNonNull(dependecies);
+ }
+
+ public static <T> @NonNull GeneratorResult<T> of(final Unloaded<T> result) {
+ return new GeneratorResult<>(result, ImmutableSet.of());
+ }
+
+ public static <T> @NonNull GeneratorResult<T> of(final Unloaded<T> result,
+ final Collection<Class<?>> dependencies) {
+ return dependencies.isEmpty() ? of(result) : new GeneratorResult<>(result,
+ ImmutableSet.copyOf(dependencies));
+ }
+
+ @NonNull Unloaded<T> getResult() {
+ return result;
+ }
+
+ @NonNull ImmutableSet<Class<?>> getDependencies() {
+ return dependecies;
+ }
+ }
+
+ private static final ClassLoadingStrategy<CodecClassLoader> STRATEGY = (classLoader, types) -> {
+ verify(types.size() == 1, "Unexpected multiple types", types);
+ final Entry<TypeDescription, byte[]> entry = types.entrySet().iterator().next();
+ return ImmutableMap.of(entry.getKey(), classLoader.loadClass(entry.getKey().getName(), entry.getValue()));
+ };
+
static {
verify(ClassLoader.registerAsParallelCapable());
}
private static final Logger LOG = LoggerFactory.getLogger(CodecClassLoader.class);
+ private static final File BYTECODE_DIRECTORY;
- private final ClassPool classPool;
-
- private CodecClassLoader(final ClassLoader parentLoader, final ClassPool parentPool) {
- super(parentLoader);
- this.classPool = new ClassPool(parentPool);
- this.classPool.childFirstLookup = true;
- this.classPool.appendClassPath(new LoaderClassPath(this));
- }
-
- CodecClassLoader() {
- this(StaticClassPool.LOADER, StaticClassPool.POOL);
+ static {
+ final String dir = System.getProperty("org.opendaylight.mdsal.binding.dom.codec.loader.bytecodeDumpDirectory");
+ BYTECODE_DIRECTORY = Strings.isNullOrEmpty(dir) ? null : new File(dir);
}
- CodecClassLoader(final CodecClassLoader parent) {
- this(parent, parent.classPool);
+ CodecClassLoader(final ClassLoader parentLoader) {
+ super(parentLoader);
}
/**
- * Turn a Class instance into a CtClass for referencing it in code generation. This method supports both
- * generated- and non-generated classes.
+ * Instantiate a new CodecClassLoader, which serves as the root of generated code loading.
*
- * @param clazz Class to be looked up.
- * @return A CtClass instance
- * @throws NotFoundException if the class cannot be found
- * @throws NullPointerException if {@code clazz} is null
+ * @return A new CodecClassLoader.
*/
- public final @NonNull CtClass findClass(final @NonNull Class<?> clazz) throws NotFoundException {
- return BindingReflections.isBindingClass(clazz) ? findClassLoader(clazz).getLocalFrozen(clazz.getName())
- : StaticClassPool.findClass(clazz);
+ public static @NonNull CodecClassLoader create() {
+ return AccessController.doPrivileged((PrivilegedAction<CodecClassLoader>)() -> new RootCodecClassLoader());
}
/**
- * Create a new class by subclassing specified class and running a customizer on it. The name of the target class
- * is formed through concatenation of the name of a {@code bindingInterface} and specified {@code suffix}
+ * The name of the target class is formed through concatenation of the name of a {@code bindingInterface} and
+ * specified {@code suffix}.
*
- * @param superClass Superclass from which to derive
* @param bindingInterface Binding compile-time-generated interface
* @param suffix Suffix to use
- * @param customizer Customizer to use to process the class
+ * @param generator Code generator to run
* @return A generated class object
- * @throws CannotCompileException if the resulting generated class cannot be compiled or customized
- * @throws NotFoundException if the binding interface cannot be found or the generated class cannot be customized
- * @throws IOException if the generated class cannot be turned into bytecode or the generator fails with IOException
* @throws NullPointerException if any argument is null
*/
- public final Class<?> generateSubclass(final CtClass superClass, final Class<?> bindingInterface,
- final String suffix, final Customizer customizer) throws CannotCompileException, IOException,
- NotFoundException {
- return findClassLoader(requireNonNull(bindingInterface))
- .doGenerateSubclass(superClass, bindingInterface, suffix, customizer);
- }
-
- final @NonNull CtClass getLocalFrozen(final String name) throws NotFoundException {
- synchronized (getClassLoadingLock(name)) {
- final CtClass result = classPool.get(name);
- result.freeze();
- return result;
- }
+ public final <T> Class<T> generateClass(final Class<?> bindingInterface,
+ final String suffix, final ClassGenerator<T> generator) {
+ return findClassLoader(requireNonNull(bindingInterface)).doGenerateClass(bindingInterface, suffix, generator);
}
/**
*/
abstract @NonNull CodecClassLoader findClassLoader(@NonNull Class<?> bindingClass);
- private Class<?> doGenerateSubclass(final CtClass superClass, final Class<?> bindingInterface, final String suffix,
- final Customizer customizer) throws CannotCompileException, IOException, NotFoundException {
- checkArgument(!superClass.isInterface(), "%s must not be an interface", superClass);
- checkArgument(bindingInterface.isInterface(), "%s is not an interface", bindingInterface);
- checkArgument(!Strings.isNullOrEmpty(suffix));
+ private <T> Class<T> doGenerateClass(final Class<?> bindingInterface, final String suffix,
+ final ClassGenerator<T> generator) {
+ final String fqcn = bindingInterface.getName() + "$$$" + suffix;
- final String bindingName = bindingInterface.getName();
- final String fqn = bindingName + "$$$" + suffix;
- synchronized (getClassLoadingLock(fqn)) {
+ synchronized (getClassLoadingLock(fqcn)) {
// Attempt to find a loaded class
- final Class<?> loaded = findLoadedClass(fqn);
- if (loaded != null) {
- return loaded;
+ final Class<?> existing = findLoadedClass(fqcn);
+ if (existing != null) {
+ return (Class<T>) existing;
}
- // Get the interface
- final CtClass bindingCt = getLocalFrozen(bindingName);
- try {
- final byte[] byteCode;
- final CtClass generated = verifyNotNull(classPool.makeClass(fqn, superClass));
- try {
- final List<Class<?>> deps = customizer.customize(this, bindingCt, generated);
- final String ctName = generated.getName();
- verify(fqn.equals(ctName), "Target class is %s returned result is %s", fqn, ctName);
- processDependencies(deps);
-
- byteCode = generated.toBytecode();
- } finally {
- // Always detach the result, as we will never use it again
- generated.detach();
- }
-
- return customizer.customizeLoading(() -> {
- final Class<?> newClass = defineClass(fqn, byteCode, 0, byteCode.length);
- resolveClass(newClass);
- return newClass;
- });
- } finally {
- // Binding interfaces are used only a few times, hence it does not make sense to cache them in the class
- // pool.
- // TODO: this hinders caching, hence we should re-think this
- bindingCt.detach();
- }
+ final GeneratorResult<T> result = generator.generateClass(this, fqcn, bindingInterface);
+ final Unloaded<T> unloaded = result.getResult();
+ verify(fqcn.equals(unloaded.getTypeDescription().getName()), "Unexpected class in %s", unloaded);
+ verify(unloaded.getAuxiliaryTypes().isEmpty(), "Auxiliary types present in %s", unloaded);
+ dumpBytecode(unloaded);
+
+ processDependencies(result.getDependencies());
+ return generator.customizeLoading(() -> (Class<T>) unloaded.load(this, STRATEGY).getLoaded());
}
}
- private void processDependencies(final List<Class<?>> deps) {
+ final Class<?> loadClass(final String fqcn, final byte[] byteCode) {
+ synchronized (getClassLoadingLock(fqcn)) {
+ final Class<?> existing = findLoadedClass(fqcn);
+ verify(existing == null, "Attempted to load existing %s", existing);
+ return defineClass(fqcn, byteCode, 0, byteCode.length);
+ }
+ }
+
+ private void processDependencies(final Collection<Class<?>> deps) {
final Set<LeafCodecClassLoader> depLoaders = new HashSet<>();
for (Class<?> dep : deps) {
final ClassLoader depLoader = dep.getClassLoader();
appendLoaders(depLoaders);
}
}
+
+ private static void dumpBytecode(final Unloaded<?> unloaded) {
+ if (BYTECODE_DIRECTORY != null) {
+ try {
+ unloaded.saveIn(BYTECODE_DIRECTORY);
+ } catch (IOException | IllegalArgumentException e) {
+ LOG.info("Failed to save {}", unloaded.getTypeDescription().getName(), e);
+ }
+ }
+ }
}
} catch (ClassNotFoundException e) {
LOG.trace("Class {} not found in target, looking through dependencies", name);
for (LeafCodecClassLoader loader : dependencies) {
- final Class<?> loaded = loader.findLoadedClass(name);
- if (loaded != null) {
- LOG.trace("Class {} found in dependency {}", name, loader);
- return loaded;
+ // Careful: a loading operation may be underway, make sure that process has completed
+ synchronized (loader.getClassLoadingLock(name)) {
+ final Class<?> loaded = loader.findLoadedClass(name);
+ if (loaded != null) {
+ LOG.trace("Class {} found in dependency {}", name, loader);
+ return loaded;
+ }
}
}
package org.opendaylight.mdsal.binding.dom.codec.loader;
import static com.google.common.base.Verify.verify;
+import static com.google.common.base.Verify.verifyNotNull;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import java.security.PrivilegedAction;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.opendaylight.yangtools.yang.binding.DataContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// A root codec classloader, binding only whatever is available StaticClassPool
final class RootCodecClassLoader extends CodecClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(RootCodecClassLoader.class);
+ private static final ClassLoader LOADER = verifyNotNull(RootCodecClassLoader.class.getClassLoader());
static {
verify(registerAsParallelCapable());
private volatile ImmutableMap<ClassLoader, CodecClassLoader> loaders = ImmutableMap.of();
RootCodecClassLoader() {
- super();
+ super(LOADER);
}
@Override
// ourselves) or we need to create a new Leaf.
final CodecClassLoader found;
if (!isOurClass(bindingClass)) {
- StaticClassPool.verifyStaticLinkage(target);
+ verifyStaticLinkage(target);
found = AccessController.doPrivileged(
(PrivilegedAction<CodecClassLoader>)() -> new LeafCodecClassLoader(this, target));
} else {
}
return bindingClass.equals(ourClass);
}
+
+ // Sanity check: target has to resolve yang-binding contents to the same class, otherwise we are in a pickle
+ private static void verifyStaticLinkage(final ClassLoader candidate) {
+ final Class<?> targetClazz;
+ try {
+ targetClazz = candidate.loadClass(DataContainer.class.getName());
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException("ClassLoader " + candidate + " cannot load " + DataContainer.class, e);
+ }
+ verify(DataContainer.class.equals(targetClazz),
+ "Class mismatch on DataContainer. Ours is from %s, target %s has %s from %s",
+ DataContainer.class.getClassLoader(), candidate, targetClazz, targetClazz.getClassLoader());
+ }
}
+++ /dev/null
-/*
- * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others. All rights reserved.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License v1.0 which accompanies this distribution,
- * and is available at http://www.eclipse.org/legal/epl-v10.html
- */
-package org.opendaylight.mdsal.binding.dom.codec.loader;
-
-import static com.google.common.base.Verify.verify;
-import static com.google.common.base.Verify.verifyNotNull;
-
-import com.google.common.annotations.Beta;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import javassist.ClassPool;
-import javassist.CtClass;
-import javassist.LoaderClassPath;
-import javassist.NotFoundException;
-import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.yang.binding.DataContainer;
-
-/**
- * Static class pool, bound to the class loader of binding-dom-codec. It can be used to acquire CtClass instances that
- * reside within the binding-dom-codec artifact or any of its direct mandatory dependencies. It can also instantiate
- * {@link CodecClassLoader} instances for use with code generation.
- *
- * @author Robert Varga
- */
-@Beta
-public final class StaticClassPool {
- static final ClassLoader LOADER = verifyNotNull(StaticClassPool.class.getClassLoader());
- static final ClassPool POOL;
-
- static {
- final ClassPool pool = new ClassPool();
- pool.appendClassPath(new LoaderClassPath(LOADER));
- POOL = pool;
- }
-
- private StaticClassPool() {
- // Utility class
- }
-
- /**
- * Instantiate a new CodecClassLoader.
- *
- * @return A new CodecClassLoader.
- */
- public static @NonNull CodecClassLoader createLoader() {
- return AccessController.doPrivileged((PrivilegedAction<CodecClassLoader>)() -> new RootCodecClassLoader());
- }
-
- /**
- * Resolve a binding-dom-codec class to its {@link CtClass} counterpart.
- *
- * @param clazz Class to resolve
- * @return A CtClass instance
- * @throws IllegalStateException if the class cannot be resolved
- * @throws NullPointerException if {@code clazz} is null
- */
- public static synchronized @NonNull CtClass findClass(final @NonNull Class<?> clazz) {
- final CtClass ret;
- try {
- ret = POOL.get(clazz.getName());
- } catch (NotFoundException e) {
- throw new IllegalStateException("Failed to find " + clazz, e);
- }
- ret.freeze();
- return ret;
- }
-
- // Sanity check: target has to resolve yang-binding contents to the same class, otherwise we are in a pickle
- static void verifyStaticLinkage(final ClassLoader candidate) {
- final Class<?> targetClazz;
- try {
- targetClazz = candidate.loadClass(DataContainer.class.getName());
- } catch (ClassNotFoundException e) {
- throw new IllegalStateException("ClassLoader " + candidate + " cannot load " + DataContainer.class, e);
- }
- verify(DataContainer.class.equals(targetClazz),
- "Class mismatch on DataContainer. Ours is from %s, target %s has %s from %s",
- DataContainer.class.getClassLoader(), candidate, targetClazz, targetClazz.getClassLoader());
- }
-}
* and is available at http://www.eclipse.org/legal/epl-v10.html
*/
/**
- * {@link java.lang.ClassLoader} support for Binding/DOM codec translation code generators. This package provides two
- * core classes:
- * <ul>
- * <li>{@link org.opendaylight.mdsal.binding.dom.codec.loader.StaticClassPool}, which is allows lookup of classes within
- * Binding/DOM codec for the purpose of referencing them within code generators.</li>
- * <li>{@link org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader}, which allows lookup of
- * compile-time-generated Binding classes for the purpose of referencing them within code generators and which
- * serves as the ClassLoader holding runtime-generated codecs.
- * </li>
- * </ul>
+ * {@link java.lang.ClassLoader} support for Binding/DOM codec translation code generators. This package provides one
+ * core class, {@link org.opendaylight.mdsal.binding.dom.codec.loader.CodecClassLoader}, which allows lookup of
+ * compile-time-generated Binding classes for the purpose of referencing them within code generators and which
+ * serves as the ClassLoader holding runtime-generated codecs.
*
* <p>
* While the interfaces and classes in this package may be publicly accessible, they are an implementation detail and