package net.bytebuddy.dynamic.loading; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import lombok.EqualsAndHashCode; import net.bytebuddy.matcher.ElementMatcher; import java.io.IOException; import java.net.URL; import java.util.*; import static net.bytebuddy.matcher.ElementMatchers.is; import static net.bytebuddy.matcher.ElementMatchers.not; /** * <p> * This {@link java.lang.ClassLoader} is capable of loading classes from multiple parents. This class loader * implicitly defines the bootstrap class loader to be its direct parent as it is required for all class loaders. * This can be useful when creating a type that inherits a super type and interfaces that are defined by different, * non-compatible class loaders. * </p> * <p> * <b>Note</b>: Instances of this class loader can have the same class loader as its parent multiple times, * either directly or indirectly by multiple parents sharing a common parent class loader. By definition, * this implies that the bootstrap class loader is {@code #(direct parents) + 1} times a parent of this class loader. * For the {@link java.lang.ClassLoader#getResources(java.lang.String)} method, this means that this class loader * might return the same url multiple times by representing the same class loader multiple times. * </p> * <p> * <b>Important</b>: This class loader does not support the location of packages from its multiple parents. This breaks * package equality when loading classes by either loading them directly via this class loader (e.g. by subclassing) or * by loading classes with child class loaders of this class loader. * </p> */ public class MultipleParentClassLoader extends ClassLoader { /** * The parents of this class loader in their application order. */ private final List<? extends ClassLoader> parents; /** * Creates a new class loader with multiple parents. * * @param parents The parents of this class loader in their application order. This list must not contain {@code null}, * i.e. the bootstrap class loader which is an implicit parent of any class loader. */ public MultipleParentClassLoader(List<? extends ClassLoader> parents) { this(ClassLoadingStrategy.BOOTSTRAP_LOADER, parents); } /** * Creates a new class loader with multiple parents. * * @param parent An explicit parent in compliance with the class loader API. This explicit parent should only be set if * the current platform does not allow creating a class loader that extends the bootstrap loader. * @param parents The parents of this class loader in their application order. This list must not contain {@code null}, * i.e. the bootstrap class loader which is an implicit parent of any class loader. */ public MultipleParentClassLoader(ClassLoader parent, List<? extends ClassLoader> parents) { super(parent); this.parents = parents; } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { for (ClassLoader parent : parents) { try { Class<?> type = parent.loadClass(name); if (resolve) { resolveClass(type); } return type; } catch (ClassNotFoundException ignored) { /* try next class loader */ } } return super.loadClass(name, resolve); } @Override public URL getResource(String name) { for (ClassLoader parent : parents) { URL url = parent.getResource(name); if (url != null) { return url; } } return super.getResource(name); } @Override public Enumeration<URL> getResources(String name) throws IOException { List<Enumeration<URL>> enumerations = new ArrayList<Enumeration<URL>>(parents.size() + 1); for (ClassLoader parent : parents) { enumerations.add(parent.getResources(name)); } enumerations.add(super.getResources(name)); return new CompoundEnumeration(enumerations); } /** * A compound URL enumeration. */ protected static class CompoundEnumeration implements Enumeration<URL> { /** * Indicates the first index of a list. */ private static final int FIRST = 0; /** * The remaining lists of enumerations. */ private final List<Enumeration<URL>> enumerations; /** * The currently represented enumeration or {@code null} if no such enumeration is currently selected. */ private Enumeration<URL> currentEnumeration; /** * Creates a compound enumeration. * * @param enumerations The enumerations to represent. */ protected CompoundEnumeration(List<Enumeration<URL>> enumerations) { this.enumerations = enumerations; } @Override public boolean hasMoreElements() { if (currentEnumeration != null && currentEnumeration.hasMoreElements()) { return true; } else if (!enumerations.isEmpty()) { currentEnumeration = enumerations.remove(FIRST); return hasMoreElements(); } else { return false; } } @Override @SuppressFBWarnings(value = "UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR", justification = "Null reference is impossible due to element check") public URL nextElement() { if (hasMoreElements()) { return currentEnumeration.nextElement(); } else { throw new NoSuchElementException(); } } } /** * A builder to collect class loader and that creates a * {@link net.bytebuddy.dynamic.loading.MultipleParentClassLoader} only if multiple or no * {@link java.lang.ClassLoader}s are found in the process. If exactly a single class loader is found, * this class loader is returned. All class loaders are applied in their collection order with the exception * of the bootstrap class loader which is represented by {@code null} and which is an implicit parent of any * class loader. */ @EqualsAndHashCode public static class Builder { /** * Indicates the first index of a list. */ private static final int ONLY = 0; /** * The class loaders that were collected. */ private final List<? extends ClassLoader> classLoaders; /** * Creates a new builder without any class loaders. */ public Builder() { this(Collections.<ClassLoader>emptyList()); } /** * Creates a new builder. * * @param classLoaders The class loaders that were collected until now. */ private Builder(List<? extends ClassLoader> classLoaders) { this.classLoaders = classLoaders; } /** * Appends the class loaders of the given types. The bootstrap class loader is implicitly skipped as * it is an implicit parent of any class loader. * * @param type The types of which to collect the class loaders. * @return A new builder instance with the additional class loaders of the provided types if they were not * yet collected. */ public Builder append(Class<?>... type) { return append(Arrays.asList(type)); } /** * Appends the class loaders of the given types if those class loaders were not yet collected. The bootstrap class * loader is implicitly skipped as it is an implicit parent of any class loader. * * @param types The types of which to collect the class loaders. * @return A new builder instance with the additional class loaders. */ public Builder append(Collection<? extends Class<?>> types) { List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(types.size()); for (Class<?> type : types) { classLoaders.add(type.getClassLoader()); } return append(classLoaders); } /** * Appends the given class loaders if they were not yet collected. The bootstrap class loader is implicitly * skipped as it is an implicit parent of any class loader. * * @param classLoader The class loaders to be collected. * @return A new builder instance with the additional class loaders. */ public Builder append(ClassLoader... classLoader) { return append(Arrays.asList(classLoader)); } /** * Appends the given class loaders if they were not yet appended. The bootstrap class loader is never appended as * it is an implicit parent of any class loader. * * @param classLoaders The class loaders to collected. * @return A new builder instance with the additional class loaders. */ public Builder append(List<? extends ClassLoader> classLoaders) { List<ClassLoader> filtered = new ArrayList<ClassLoader>(this.classLoaders.size() + classLoaders.size()); Set<ClassLoader> registered = new HashSet<ClassLoader>(this.classLoaders); filtered.addAll(this.classLoaders); for (ClassLoader classLoader : classLoaders) { if (classLoader != null && registered.add(classLoader)) { filtered.add(classLoader); } } return new Builder(filtered); } /** * Only retains all class loaders that match the given matcher. * * @param matcher The matcher to be used for filtering. * @return A builder that does not longer consider any appended class loaders that matched the provided matcher. */ public Builder filter(ElementMatcher<? super ClassLoader> matcher) { List<ClassLoader> classLoaders = new ArrayList<ClassLoader>(this.classLoaders.size()); for (ClassLoader classLoader : this.classLoaders) { if (matcher.matches(classLoader)) { classLoaders.add(classLoader); } } return new Builder(classLoaders); } /** * <p> * Returns the only class loader that was appended if exactly one class loader was appended or a multiple parent class loader as * a parent of all supplied class loader and with the bootstrap class loader as an implicit parent. If no class loader was appended, * a new class loader is created that declares no parents. If a class loader is created, its explicit parent is set to be the * bootstrap class loader. * </p> * <p> * <b>Important</b>: Byte Buddy does not provide any access control for the creation of the class loader. It is the responsibility * of the user of this builder to provide such privileges. * </p> * * @return A suitable class loader. */ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility") public ClassLoader build() { return classLoaders.size() == 1 ? classLoaders.get(ONLY) : new MultipleParentClassLoader(classLoaders); } /** * <p> * Returns the only class loader that was appended if exactly one class loader was appended or a multiple parent class loader as * a parent of all supplied class loader and with the bootstrap class loader as an implicit parent. If no class loader was appended, * or if only the supplied parent to this method was included, this class loader is returned, * </p> * <p> * <b>Important</b>: Byte Buddy does not provide any access control for the creation of the class loader. It is the responsibility * of the user of this builder to provide such privileges. * </p> * * @param parent The class loader's contractual parent which is accessible via {@link ClassLoader#getParent()}. If this class loader * is also included in the appended class loaders, it is not * @return A suitable class loader. */ public ClassLoader build(ClassLoader parent) { return classLoaders.isEmpty() || (classLoaders.size() == 1 && classLoaders.contains(parent)) ? parent : filter(not(is(parent))).doBuild(parent); } /** * Creates a multiple parent class loader with an explicit parent. * * @param parent The explicit parent class loader. * @return A multiple parent class loader that includes all collected class loaders and the explicit parent. */ @SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Privilege is explicit user responsibility") private ClassLoader doBuild(ClassLoader parent) { return new MultipleParentClassLoader(parent, classLoaders); } } }