Expand class customization capabilities
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / loader / CodecClassLoader.java
index 22f7396957fbf565ac2eebe4e978eba0ede8ebed..c8b2a60140ab512569a57bb2f5a4d05853414508 100644 (file)
@@ -10,10 +10,15 @@ 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 java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import javassist.CannotCompileException;
 import javassist.ClassPool;
 import javassist.CtClass;
@@ -21,6 +26,8 @@ import javassist.LoaderClassPath;
 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
@@ -56,18 +63,32 @@ public abstract class CodecClassLoader extends ClassLoader {
          * @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) {
@@ -116,7 +137,8 @@ public abstract class CodecClassLoader extends ClassLoader {
     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 {
@@ -127,7 +149,24 @@ public abstract class CodecClassLoader extends ClassLoader {
         }
     }
 
-    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 {
@@ -150,18 +189,22 @@ public abstract class CodecClassLoader extends ClassLoader {
                 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.
@@ -170,4 +213,29 @@ public abstract class CodecClassLoader extends ClassLoader {
             }
         }
     }
+
+    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);
+        }
+    }
 }