Convert mdsal-binding-dom-codec to a JPMS module
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / loader / RootCodecClassLoader.java
diff --git a/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/loader/RootCodecClassLoader.java b/binding/mdsal-binding-dom-codec/src/main/java/org/opendaylight/mdsal/binding/dom/codec/impl/loader/RootCodecClassLoader.java
new file mode 100644 (file)
index 0000000..b52fe17
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * 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.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.AccessController;
+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());
+    }
+
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<RootCodecClassLoader, ImmutableMap> LOADERS_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(RootCodecClassLoader.class, ImmutableMap.class, "loaders");
+
+    private volatile ImmutableMap<ClassLoader, CodecClassLoader> loaders = ImmutableMap.of();
+
+    RootCodecClassLoader() {
+        super(LOADER);
+    }
+
+    @Override
+    CodecClassLoader findClassLoader(final Class<?> bindingClass) {
+        final ClassLoader target = bindingClass.getClassLoader();
+        if (target == null) {
+            // No class loader associated ... well, let's use root then
+            return this;
+        }
+
+        // Cache for update
+        ImmutableMap<ClassLoader, CodecClassLoader> local = loaders;
+        final CodecClassLoader known = local.get(target);
+        if (known != null) {
+            return known;
+        }
+
+        // Alright, we need to determine if the class is accessible through our hierarchy (in which case we use
+        // ourselves) or we need to create a new Leaf.
+        final CodecClassLoader found;
+        if (!isOurClass(bindingClass)) {
+            verifyStaticLinkage(target);
+            found = AccessController.doPrivileged(
+                (PrivilegedAction<CodecClassLoader>)() -> new LeafCodecClassLoader(this, target));
+        } else {
+            found = this;
+        }
+
+        // Now make sure we cache this result
+        while (true) {
+            final Builder<ClassLoader, CodecClassLoader> builder = ImmutableMap.builderWithExpectedSize(
+                local.size() + 1);
+            builder.putAll(local);
+            builder.put(target, found);
+
+            if (LOADERS_UPDATER.compareAndSet(this, local, builder.build())) {
+                return found;
+            }
+
+            local = loaders;
+            final CodecClassLoader recheck = local.get(target);
+            if (recheck != null) {
+                return recheck;
+            }
+        }
+    }
+
+    @Override
+    void appendLoaders(final Set<LeafCodecClassLoader> newLoaders) {
+        // Root loader should never see the requirement for other loaders, as that would violate loop-free nature
+        // of generated code: if a binding class is hosted in root loader, all its references must be visible from
+        // the root loader and hence all the generated code ends up residing in the root loader, too.
+        throw new IllegalStateException("Attempted to extend root loader with " + newLoaders);
+    }
+
+    private boolean isOurClass(final Class<?> bindingClass) {
+        final Class<?> ourClass;
+        try {
+            ourClass = loadClass(bindingClass.getName(), false);
+        } catch (ClassNotFoundException e) {
+            LOG.debug("Failed to load {}", bindingClass, e);
+            return false;
+        }
+        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());
+    }
+}