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 java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.eclipse.jdt.annotation.NonNull;
import org.opendaylight.mdsal.binding.spec.reflect.BindingReflections;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* A ClassLoader hosting types generated for a particular type. A root instance is attached to a
* @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
*/
- void customize(@NonNull CodecClassLoader loader, @NonNull CtClass bindingClass, @NonNull CtClass generated)
- throws CannotCompileException, NotFoundException, IOException;
+ @NonNull List<Class<?>> customize(@NonNull CodecClassLoader loader, @NonNull CtClass bindingClass,
+ @NonNull CtClass generated) throws CannotCompileException, NotFoundException, IOException;
+
+ /**
+ * Run the specified loader in a customized environment. The environment customizations must be cleaned up by
+ * the time this method returns. The default implementation performs no customization.
+ *
+ * @param loader Class loader to execute
+ * @return Class returned by the loader
+ */
+ default Class<?> customizeLoading(final @NonNull Supplier<Class<?>> loader) {
+ return loader.get();
+ }
}
static {
verify(ClassLoader.registerAsParallelCapable());
}
+ private static final Logger LOG = LoggerFactory.getLogger(CodecClassLoader.class);
+
private final ClassPool classPool;
private CodecClassLoader(final ClassLoader parentLoader, final ClassPool parentPool) {
public final Class<?> generateSubclass(final CtClass superClass, final Class<?> bindingInterface,
final String suffix, final Customizer customizer) throws CannotCompileException, IOException,
NotFoundException {
- return findClassLoader(bindingInterface).doGenerateSubclass(superClass, bindingInterface, suffix, customizer);
+ return findClassLoader(requireNonNull(bindingInterface))
+ .doGenerateSubclass(superClass, bindingInterface, suffix, customizer);
}
final @NonNull CtClass getLocalFrozen(final String name) throws NotFoundException {
}
}
- abstract @NonNull CodecClassLoader findClassLoader(Class<?> bindingClass);
+ /**
+ * Append specified loaders to this class loader for the purposes of looking up generated classes. Note that the
+ * loaders are expected to have required classes already loaded. This is required to support generation of
+ * inter-dependent structures, such as those used for streaming binding interfaces.
+ *
+ * @param newLoaders Loaders to append
+ * @throws NullPointerException if {@code loaders} is null
+ */
+ abstract void appendLoaders(@NonNull Set<LeafCodecClassLoader> newLoaders);
+
+ /**
+ * Find the loader responsible for holding classes related to a binding class.
+ *
+ * @param bindingClass Class to locate
+ * @return a Loader instance
+ * @throws NullPointerException if {@code bindingClass} is null
+ */
+ 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 {
final byte[] byteCode;
final CtClass generated = verifyNotNull(classPool.makeClass(fqn, superClass));
try {
- customizer.customize(this, bindingCt, generated);
+ 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();
}
- final Class<?> newClass = defineClass(fqn, byteCode, 0, byteCode.length);
- resolveClass(newClass);
- return newClass;
+ 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.
}
}
}
+
+ private void processDependencies(final List<Class<?>> deps) {
+ final Set<LeafCodecClassLoader> depLoaders = new HashSet<>();
+ for (Class<?> dep : deps) {
+ final ClassLoader depLoader = dep.getClassLoader();
+ verify(depLoader instanceof CodecClassLoader, "Dependency %s is not a generated class", dep);
+ if (this.equals(depLoader)) {
+ // Same loader, skip
+ continue;
+ }
+
+ try {
+ loadClass(dep.getName());
+ } catch (ClassNotFoundException e) {
+ LOG.debug("Cannot find {} in local loader, attempting to compensate", dep, e);
+ // Root loader is always visible from a leaf, hence the dependency can only be a leaf
+ verify(depLoader instanceof LeafCodecClassLoader, "Dependency loader %s is not a leaf", depLoader);
+ depLoaders.add((LeafCodecClassLoader) depLoader);
+ }
+ }
+
+ if (!depLoaders.isEmpty()) {
+ appendLoaders(depLoaders);
+ }
+ }
}
import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;
+import com.google.common.collect.ImmutableSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.eclipse.jdt.annotation.NonNull;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
// A leaf class loader, binding together a root class loader and some other class loader
final class LeafCodecClassLoader extends CodecClassLoader {
verify(registerAsParallelCapable());
}
- private final @NonNull ClassLoader target;
+ private static final Logger LOG = LoggerFactory.getLogger(LeafCodecClassLoader.class);
+
private final @NonNull RootCodecClassLoader root;
+ private final @NonNull ClassLoader target;
+
+ @SuppressWarnings("rawtypes")
+ private static final AtomicReferenceFieldUpdater<LeafCodecClassLoader, ImmutableSet> DEPENDENCIES_UPDATER =
+ AtomicReferenceFieldUpdater.newUpdater(LeafCodecClassLoader.class, ImmutableSet.class, "dependencies");
+ private volatile ImmutableSet<LeafCodecClassLoader> dependencies = ImmutableSet.of();
LeafCodecClassLoader(final RootCodecClassLoader root, final ClassLoader target) {
super(root);
@Override
protected Class<?> findClass(final String name) throws ClassNotFoundException {
- return target.loadClass(name);
+ try {
+ return target.loadClass(name);
+ } 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;
+ }
+ }
+
+ throw e;
+ }
}
@Override
final ClassLoader bindingTarget = bindingClass.getClassLoader();
return target.equals(bindingTarget) ? this : root.findClassLoader(bindingClass);
}
+
+ @Override
+ void appendLoaders(final Set<LeafCodecClassLoader> newLoaders) {
+ while (true) {
+ final ImmutableSet<LeafCodecClassLoader> local = dependencies;
+ final List<LeafCodecClassLoader> builder = new ArrayList<>(local.size() + newLoaders.size());
+ builder.addAll(local);
+ builder.addAll(newLoaders);
+ final ImmutableSet<LeafCodecClassLoader> updated = ImmutableSet.copyOf(builder);
+ if (local.equals(updated) || DEPENDENCIES_UPDATER.compareAndSet(this, local, updated)) {
+ // No need for an update or the update was successful
+ return;
+ }
+ }
+ }
}