All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opendaylight.yangtools.binding.loader.BindingClassLoader Maven / Gradle / Ivy

The newest version!
/*
 * 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.yangtools.binding.loader;

import static com.google.common.base.Verify.verify;
import static java.util.Objects.requireNonNull;

import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
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.HexFormat;
import java.util.Set;
import java.util.function.Supplier;
import net.bytebuddy.dynamic.DynamicType.Unloaded;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A ClassLoader hosting types generated for a particular type. A root instance is attached to a
 * BindingCodecContext instance, so any generated classes from it can be garbage-collected when the context
 * is destroyed, as well as to prevent two contexts trampling over each other.
 *
 * 

It semantically combines two class loaders: the class loader in which this class is loaded and the class loader in * which a target Binding interface/class is loaded. This inherently supports multi-classloader environments -- the root * instance has visibility only into codec classes and for each classloader we encounter when presented with a binding * class we create a leaf instance and cache it in the root instance. Leaf instances are using the root loader as their * parent, but consult the binding class's class loader if the root loader fails to load a particular class. * *

In single-classloader environments, obviously, the root loader can load all binding classes, and hence no leaf * loader is created. */ public abstract sealed class BindingClassLoader extends ClassLoader permits LeafBindingClassLoader, RootBindingClassLoader { /** * A class generator, generating a class of a particular type. * * @param Type of generated class */ public interface ClassGenerator { /** * Generate a class. * * @param fqcn Generated class Fully-qualified class name * @param bindingInterface Binding interface for which the class is being generated * @return A result. */ GeneratorResult generateClass(BindingClassLoader loader, String fqcn, Class bindingInterface); /** * 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> loader) { return loader.get(); } } /** * Result of class generation. * * @param Type of generated class. */ public static final class GeneratorResult { private final @NonNull ImmutableSet> dependecies; private final @NonNull Unloaded result; GeneratorResult(final Unloaded result, final ImmutableSet> dependecies) { this.result = requireNonNull(result); this.dependecies = requireNonNull(dependecies); } public static @NonNull GeneratorResult of(final Unloaded result) { return new GeneratorResult<>(result, ImmutableSet.of()); } public static @NonNull GeneratorResult of(final Unloaded result, final Collection> dependencies) { return dependencies.isEmpty() ? of(result) : new GeneratorResult<>(result, ImmutableSet.copyOf(dependencies)); } @NonNull Unloaded getResult() { return result; } @NonNull ImmutableSet> getDependencies() { return dependecies; } } private static final ClassLoadingStrategy STRATEGY = (classLoader, types) -> { verify(types.size() == 1, "Unexpected multiple types", types); final var 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(BindingClassLoader.class); private final @Nullable File dumpDir; BindingClassLoader(final ClassLoader parentLoader, final @Nullable File dumpDir) { super(parentLoader); this.dumpDir = dumpDir; } BindingClassLoader(final BindingClassLoader parentLoader) { this(parentLoader, parentLoader.dumpDir); } /** * Instantiate a new BindingClassLoader, which serves as the root of generated code loading. * * @param rootClass Class from which to derive the class loader * @param dumpDir Directory in which to dump loaded bytecode * @return A new BindingClassLoader. * @throws NullPointerException if {@code parentLoader} is {@code null} */ public static @NonNull BindingClassLoader create(final Class rootClass, final @Nullable File dumpDir) { final var parentLoader = rootClass.getClassLoader(); return AccessController.doPrivileged( (PrivilegedAction)() -> new RootBindingClassLoader(parentLoader, dumpDir)); } /** * Generate a class which is related to specified compile-type-generated interface. * * @param Type of generated class * @param bindingInterface Binding compile-time-generated interface * @param fqcn Fully-Qualified Class Name of the generated class * @param generator Code generator to run * @return A generated class object * @throws NullPointerException if any argument is null */ public final @NonNull Class generateClass(final Class bindingInterface, final String fqcn, final ClassGenerator generator) { return findClassLoader(requireNonNull(bindingInterface)).doGenerateClass(bindingInterface, fqcn, generator); } public final @NonNull Class getGeneratedClass(final Class bindingInterface, final String fqcn) { final var loader = findClassLoader(requireNonNull(bindingInterface)); final Class ret; synchronized (loader.getClassLoadingLock(fqcn)) { ret = loader.findLoadedClass(fqcn); } if (ret == null) { throw new IllegalArgumentException("Failed to find generated class " + fqcn + " for " + bindingInterface); } return ret; } @Override public final int hashCode() { return super.hashCode(); } @Override public final boolean equals(final Object obj) { return super.equals(obj); } @Override public final String toString() { return addToStringAttributes(MoreObjects.toStringHelper(this).omitNullValues()).toString(); } ToStringHelper addToStringAttributes(final ToStringHelper helper) { return helper.add("identity", HexFormat.of().toHexDigits(hashCode())).add("parent", getParent()); } /** * 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 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 BindingClassLoader findClassLoader(@NonNull Class bindingClass); private @NonNull Class doGenerateClass(final Class bindingInterface, final String fqcn, final ClassGenerator generator) { synchronized (getClassLoadingLock(fqcn)) { // Attempt to find a loaded class final var existing = findLoadedClass(fqcn); if (existing != null) { return (Class) existing; } final var result = generator.generateClass(this, fqcn, bindingInterface); final var 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) unloaded.load(this, STRATEGY).getLoaded()); } } final Class loadClass(final String fqcn, final byte[] byteCode) { synchronized (getClassLoadingLock(fqcn)) { final var existing = findLoadedClass(fqcn); verify(existing == null, "Attempted to load existing %s", existing); return defineClass(fqcn, byteCode, 0, byteCode.length); } } private void processDependencies(final Collection> deps) { final var depLoaders = new HashSet(); for (var dep : deps) { final var depLoader = dep.getClassLoader(); verify(depLoader instanceof BindingClassLoader, "Dependency %s is not a generated class", dep); if (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 LeafBindingClassLoader, "Dependency loader %s is not a leaf", depLoader); depLoaders.add((LeafBindingClassLoader) depLoader); } } if (!depLoaders.isEmpty()) { appendLoaders(depLoaders); } } private void dumpBytecode(final Unloaded unloaded) { final var dir = dumpDir; if (dir != null) { try { unloaded.saveIn(dir); } catch (IOException | IllegalArgumentException e) { LOG.info("Failed to save {}", unloaded.getTypeDescription().getName(), e); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy