net.bytebuddy.dynamic.loading.ByteArrayClassLoader Maven / Gradle / Ivy
Show all versions of byte-buddy Show documentation
/*
* Copyright 2014 - Present Rafael Winterhalter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.bytebuddy.dynamic.loading;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.AccessControllerPlugin;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.utility.GraalImageCode;
import net.bytebuddy.utility.JavaModule;
import net.bytebuddy.utility.dispatcher.JavaDispatcher;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
*
* A {@link java.lang.ClassLoader} that is capable of loading explicitly defined classes. The class loader will free
* any binary resources once a class that is defined by its binary data is loaded. This class loader is thread safe since
* the class loading mechanics are only called from synchronized context.
*
*
* Note: Instances of this class loader return URLs for their represented class loaders with the bytebuddy schema.
* These URLs do not represent URIs as two classes with the same name yield identical URLs but might represents different byte
* arrays.
*
*
* Note: Any class and package definition is performed using the creator's {@code java.security.AccessControlContext}.
*
*/
public class ByteArrayClassLoader extends InjectionClassLoader {
/**
* The schema for URLs that represent a class file of byte array class loaders.
*/
public static final String URL_SCHEMA = "bytebuddy";
/**
* Indicates that an array should be included from its first index. Improves the source code readability.
*/
private static final int FROM_BEGINNING = 0;
/**
* Indicates that a URL does not exist to improve code readability.
*/
@AlwaysNull
private static final URL NO_URL = null;
/**
* A strategy for locating a package by name.
*/
private static final PackageLookupStrategy PACKAGE_LOOKUP_STRATEGY = doPrivileged(PackageLookupStrategy.CreationAction.INSTANCE);
/**
* The synchronization engine for the executing JVM.
*/
protected static final SynchronizationStrategy.Initializable SYNCHRONIZATION_STRATEGY = doPrivileged(SynchronizationStrategy.CreationAction.INSTANCE);
/*
* Register class loader as parallel capable if the current VM supports it.
*/
static {
doRegisterAsParallelCapable();
}
/**
* Registers class loader as parallel capable if possible.
*/
@SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Must be invoked from targeting class loader type.")
private static void doRegisterAsParallelCapable() {
try {
Method method = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable");
method.setAccessible(true);
method.invoke(null);
} catch (Throwable ignored) {
/* do nothing */
}
}
/**
* A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
*
* @param action The action to execute from a privileged context.
* @param The type of the action's resolved value.
* @return The action's resolved value.
*/
@AccessControllerPlugin.Enhance
private static T doPrivileged(PrivilegedAction action) {
return action.run();
}
/**
* A mutable map of type names mapped to their binary representation.
*/
protected final ConcurrentMap typeDefinitions;
/**
* The persistence handler of this class loader.
*/
protected final PersistenceHandler persistenceHandler;
/**
* The protection domain to apply. Might be {@code null} when referencing the default protection domain.
*/
@MaybeNull
protected final ProtectionDomain protectionDomain;
/**
* The package definer to be queried for package definitions.
*/
protected final PackageDefinitionStrategy packageDefinitionStrategy;
/**
* The class file transformer to apply on loaded classes.
*/
protected final ClassFilePostProcessor classFilePostProcessor;
/**
* The access control context to use for loading classes or {@code null} if this is not supported on the current VM.
*/
@MaybeNull
protected final Object accessControlContext;
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent, Map typeDefinitions) {
this(parent, true, typeDefinitions);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent, boolean sealed, Map typeDefinitions) {
this(parent, sealed, typeDefinitions, PersistenceHandler.LATENT);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param persistenceHandler The persistence handler of this class loader.
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent, Map typeDefinitions, PersistenceHandler persistenceHandler) {
this(parent, true, typeDefinitions, persistenceHandler);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param persistenceHandler The persistence handler of this class loader.
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent, boolean sealed, Map typeDefinitions, PersistenceHandler persistenceHandler) {
this(parent, sealed, typeDefinitions, ClassLoadingStrategy.NO_PROTECTION_DOMAIN, persistenceHandler, PackageDefinitionStrategy.Trivial.INSTANCE);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param persistenceHandler The persistence handler of this class loader.
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy) {
this(parent, true, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param persistenceHandler The persistence handler of this class loader.
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent,
boolean sealed,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy) {
this(parent, sealed, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy, ClassFilePostProcessor.NoOp.INSTANCE);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param persistenceHandler The persistence handler of this class loader.
* @param classFilePostProcessor A post processor for class files to apply p
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy,
ClassFilePostProcessor classFilePostProcessor) {
this(parent, true, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy, classFilePostProcessor);
}
/**
* Creates a new class loader for a given definition of classes.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param persistenceHandler The persistence handler of this class loader.
* @param classFilePostProcessor A post processor for class files to apply p
*/
public ByteArrayClassLoader(@MaybeNull ClassLoader parent,
boolean sealed,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy,
ClassFilePostProcessor classFilePostProcessor) {
super(parent, sealed);
this.typeDefinitions = new ConcurrentHashMap(typeDefinitions);
this.protectionDomain = protectionDomain;
this.persistenceHandler = persistenceHandler;
this.packageDefinitionStrategy = packageDefinitionStrategy;
this.classFilePostProcessor = classFilePostProcessor;
accessControlContext = getContext();
}
/**
* A proxy for {@code java.security.AccessController#getContext} that is activated if available.
*
* @return The current access control context or {@code null} if the current VM does not support it.
*/
@MaybeNull
@AccessControllerPlugin.Enhance
private static Object getContext() {
return null;
}
/**
* A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
*
* @param action The action to execute from a privileged context.
* @param context The access control context or {@code null} if the current VM does not support it.
* @param The type of the action's resolved value.
* @return The action's resolved value.
*/
@AccessControllerPlugin.Enhance
private static T doPrivileged(PrivilegedAction action, @MaybeNull @SuppressWarnings("unused") Object context) {
return action.run();
}
/**
* Resolves a method handle in the scope of the {@link ByteArrayClassLoader} class.
*
* @return A method handle for this class.
* @throws Exception If the method handle facility is not supported by the current virtual machine.
*/
private static Object methodHandle() throws Exception {
return Class.forName("java.lang.invoke.MethodHandles").getMethod("lookup").invoke(null);
}
/**
* Loads a given set of class descriptions and their binary representations.
*
* @param classLoader The parent class loader.
* @param types The unloaded types to be loaded.
* @return A map of the given type descriptions pointing to their loaded representations.
*/
public static Map> load(@MaybeNull ClassLoader classLoader, Map types) {
return load(classLoader,
types,
ClassLoadingStrategy.NO_PROTECTION_DOMAIN,
PersistenceHandler.LATENT,
PackageDefinitionStrategy.Trivial.INSTANCE,
false,
true);
}
/**
* Loads a given set of class descriptions and their binary representations.
*
* @param classLoader The parent class loader.
* @param types The unloaded types to be loaded.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param persistenceHandler The persistence handler of the created class loader.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param forbidExisting {@code true} if the class loading should throw an exception if a class was already loaded by a parent class loader.
* @param sealed {@code true} if the class loader should be sealed.
* @return A map of the given type descriptions pointing to their loaded representations.
*/
@SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Assuring privilege is explicit user responsibility.")
public static Map> load(@MaybeNull ClassLoader classLoader,
Map types,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy,
boolean forbidExisting,
boolean sealed) {
Map typesByName = new HashMap();
for (Map.Entry entry : types.entrySet()) {
typesByName.put(entry.getKey().getName(), entry.getValue());
}
classLoader = new ByteArrayClassLoader(classLoader,
sealed,
typesByName,
protectionDomain,
persistenceHandler,
packageDefinitionStrategy,
ClassFilePostProcessor.NoOp.INSTANCE);
Map> result = new LinkedHashMap>();
for (TypeDescription typeDescription : types.keySet()) {
try {
Class> type = Class.forName(typeDescription.getName(), false, classLoader);
if (!GraalImageCode.getCurrent().isNativeImageExecution() && forbidExisting && type.getClassLoader() != classLoader) {
throw new IllegalStateException("Class already loaded: " + type);
}
result.put(typeDescription, type);
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("Cannot load class " + typeDescription, exception);
}
}
return result;
}
@Override
protected Map> doDefineClasses(Map typeDefinitions) throws ClassNotFoundException {
Map previous = new HashMap();
for (Map.Entry entry : typeDefinitions.entrySet()) {
previous.put(entry.getKey(), this.typeDefinitions.putIfAbsent(entry.getKey(), entry.getValue()));
}
try {
Map> types = new LinkedHashMap>();
for (String name : typeDefinitions.keySet()) {
synchronized (SYNCHRONIZATION_STRATEGY.initialize().getClassLoadingLock(this, name)) {
types.put(name, loadClass(name));
}
}
return types;
} finally {
for (Map.Entry entry : previous.entrySet()) {
if (entry.getValue() == null) {
persistenceHandler.release(entry.getKey(), this.typeDefinitions);
} else {
this.typeDefinitions.put(entry.getKey(), entry.getValue());
}
}
}
}
/**
* {@inheritDoc}
*/
protected Class> findClass(String name) throws ClassNotFoundException {
byte[] binaryRepresentation = persistenceHandler.lookup(name, typeDefinitions);
if (binaryRepresentation == null) {
throw new ClassNotFoundException(name);
} else {
return doPrivileged(new ClassDefinitionAction(name, classFilePostProcessor.transform(this,
name,
protectionDomain,
binaryRepresentation)), accessControlContext);
}
}
/**
* {@inheritDoc}
*/
@MaybeNull
protected URL findResource(String name) {
return persistenceHandler.url(name, typeDefinitions);
}
/**
* {@inheritDoc}
*/
protected Enumeration findResources(String name) {
URL url = persistenceHandler.url(name, typeDefinitions);
return url == null
? EmptyEnumeration.INSTANCE
: new SingletonEnumeration(url);
}
/**
* Returns the package for a given name.
*
* @param name The name of the package.
* @return A suitable package or {@code null} if no such package exists.
*/
@MaybeNull
@SuppressWarnings("deprecation")
private Package doGetPackage(String name) {
return getPackage(name);
}
/**
* An engine for receiving a class loading lock when loading a class.
*/
protected interface SynchronizationStrategy {
/**
* Receives the class loading lock.
*
* @param name The name of the class being loaded.
* @param classLoader The class loader loading the class.
* @return The corresponding class loading lock.
*/
Object getClassLoadingLock(ByteArrayClassLoader classLoader, String name);
/**
* An uninitialized synchronization strategy.
*/
interface Initializable {
/**
* Initializes this synchronization strategy.
*
* @return The synchronization strategy to use.
*/
SynchronizationStrategy initialize();
}
/**
* A creation action for a synchronization strategy.
*/
enum CreationAction implements PrivilegedAction {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
public Initializable run() {
try {
try {
Class> methodType = Class.forName("java.lang.invoke.MethodType"), methodHandle = Class.forName("java.lang.invoke.MethodHandle");
return new ForJava8CapableVm(Class.forName("java.lang.invoke.MethodHandles$Lookup")
.getMethod("findVirtual", Class.class, String.class, methodType)
.invoke(ByteArrayClassLoader.methodHandle(), ClassLoader.class, "getClassLoadingLock", methodType.getMethod("methodType",
Class.class,
Class[].class).invoke(null, Object.class, new Class>[]{String.class})),
methodHandle.getMethod("bindTo", Object.class),
methodHandle.getMethod("invokeWithArguments", Object[].class));
} catch (Exception ignored) {
// On the bootstrap class loader, a lookup instance cannot be located reflectively. To avoid issuing a warning for accessing
// a protected method from outside of a class that is caused if the module system does not offer accessing the method.
return ClassFileVersion.ofThisVm(ClassFileVersion.JAVA_V5).isAtLeast(ClassFileVersion.JAVA_V9) && ByteArrayClassLoader.class.getClassLoader() == null
? SynchronizationStrategy.ForLegacyVm.INSTANCE
: new ForJava7CapableVm(ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class));
}
} catch (Exception ignored) {
return SynchronizationStrategy.ForLegacyVm.INSTANCE;
}
}
}
/**
* A synchronization engine for a VM that is not aware of parallel-capable class loaders.
*/
enum ForLegacyVm implements SynchronizationStrategy, Initializable {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public Object getClassLoadingLock(ByteArrayClassLoader classLoader, String name) {
return classLoader;
}
/**
* {@inheritDoc}
*/
public SynchronizationStrategy initialize() {
return this;
}
}
/**
* A synchronization engine for a VM that is aware of parallel-capable class loaders.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForJava7CapableVm implements SynchronizationStrategy, Initializable {
/**
* The {@code ClassLoader#getClassLoadingLock(String)} method.
*/
private final Method method;
/**
* Creates a new synchronization strategy.
*
* @param method The {@code ClassLoader#getClassLoadingLock(String)} method.
*/
protected ForJava7CapableVm(Method method) {
this.method = method;
}
/**
* {@inheritDoc}
*/
public Object getClassLoadingLock(ByteArrayClassLoader classLoader, String name) {
try {
return method.invoke(classLoader, name);
} catch (IllegalAccessException exception) {
throw new IllegalStateException(exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException(exception.getTargetException());
}
}
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Assuring privilege is explicit user responsibility.")
public SynchronizationStrategy initialize() {
try {
method.setAccessible(true);
return this;
} catch (Exception ignored) {
return ForLegacyVm.INSTANCE;
}
}
}
/**
* A synchronization engine for a VM that is aware of parallel-capable class loaders using method handles to respect module boundaries.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForJava8CapableVm implements SynchronizationStrategy, Initializable {
/**
* The {@code java.lang.invoke.MethodHandle} to use.
*/
private final Object methodHandle;
/**
* The {@code java.lang.invoke.MethodHandle#bindTo(Object)} method.
*/
private final Method bindTo;
/**
* The {@code java.lang.invoke.MethodHandle#invokeWithArguments(Object[])} method.
*/
private final Method invokeWithArguments;
/**
* Creates a new synchronization strategy.
*
* @param methodHandle The {@code java.lang.invoke.MethodHandle} to use.
* @param bindTo The {@code java.lang.invoke.MethodHandle#bindTo(Object)} method.
* @param invokeWithArguments The {@code java.lang.invoke.MethodHandle#invokeWithArguments(Object[])} method.
*/
protected ForJava8CapableVm(Object methodHandle, Method bindTo, Method invokeWithArguments) {
this.methodHandle = methodHandle;
this.bindTo = bindTo;
this.invokeWithArguments = invokeWithArguments;
}
/**
* {@inheritDoc}
*/
public SynchronizationStrategy initialize() {
return this;
}
/**
* {@inheritDoc}
*/
public Object getClassLoadingLock(ByteArrayClassLoader classLoader, String name) {
try {
return invokeWithArguments.invoke(bindTo.invoke(methodHandle, classLoader), (Object) new Object[]{name});
} catch (IllegalAccessException exception) {
throw new IllegalStateException(exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException(exception.getTargetException());
}
}
}
}
/**
* An action for defining a located class that is not yet loaded.
*/
@HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true)
protected class ClassDefinitionAction implements PrivilegedAction> {
/**
* The binary name of the class to define.
*/
private final String name;
/**
* The binary representation of the class to be loaded.
*/
private final byte[] binaryRepresentation;
/**
* Creates a new class definition action.
*
* @param name The binary name of the class to define.
* @param binaryRepresentation The binary representation of the class to be loaded.
*/
protected ClassDefinitionAction(String name, byte[] binaryRepresentation) {
this.name = name;
this.binaryRepresentation = binaryRepresentation;
}
/**
* {@inheritDoc}
*/
public Class> run() {
int packageIndex = name.lastIndexOf('.');
if (packageIndex != -1) {
String packageName = name.substring(0, packageIndex);
PackageDefinitionStrategy.Definition definition = packageDefinitionStrategy.define(ByteArrayClassLoader.this, packageName, name);
if (definition.isDefined()) {
Package definedPackage = PACKAGE_LOOKUP_STRATEGY.apply(ByteArrayClassLoader.this, packageName);
if (definedPackage == null) {
definePackage(packageName,
definition.getSpecificationTitle(),
definition.getSpecificationVersion(),
definition.getSpecificationVendor(),
definition.getImplementationTitle(),
definition.getImplementationVersion(),
definition.getImplementationVendor(),
definition.getSealBase());
} else if (!definition.isCompatibleTo(definedPackage)) {
throw new SecurityException("Sealing violation for package " + packageName);
}
}
}
return defineClass(name, binaryRepresentation, FROM_BEGINNING, binaryRepresentation.length, protectionDomain);
}
}
/**
* A package lookup strategy for locating a package by name.
*/
protected interface PackageLookupStrategy {
/**
* Returns a package for a given byte array class loader and a name.
*
* @param classLoader The class loader to locate a package for.
* @param name The name of the package.
* @return A suitable package or {@code null} if no such package exists.
*/
@MaybeNull
Package apply(ByteArrayClassLoader classLoader, String name);
/**
* A creation action for a package lookup strategy.
*/
enum CreationAction implements PrivilegedAction {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
@SuppressFBWarnings(value = "REC_CATCH_EXCEPTION", justification = "Exception should not be rethrown but trigger a fallback.")
public PackageLookupStrategy run() {
if (JavaModule.isSupported()) { // Avoid accidental lookup of method with same name in Java 8 J9 VM.
try {
return new PackageLookupStrategy.ForJava9CapableVm(ClassLoader.class.getMethod("getDefinedPackage", String.class));
} catch (Exception ignored) {
return PackageLookupStrategy.ForLegacyVm.INSTANCE;
}
} else {
return PackageLookupStrategy.ForLegacyVm.INSTANCE;
}
}
}
/**
* A package lookup strategy for a VM prior to Java 9.
*/
enum ForLegacyVm implements PackageLookupStrategy {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
@MaybeNull
public Package apply(ByteArrayClassLoader classLoader, String name) {
return classLoader.doGetPackage(name);
}
}
/**
* A package lookup strategy for Java 9 or newer.
*/
@HashCodeAndEqualsPlugin.Enhance
class ForJava9CapableVm implements PackageLookupStrategy {
/**
* The {@code java.lang.ClassLoader#getDefinedPackage(String)} method.
*/
private final Method getDefinedPackage;
/**
* Creates a new package lookup strategy for a modern VM.
*
* @param getDefinedPackage The {@code java.lang.ClassLoader#getDefinedPackage(String)} method.
*/
protected ForJava9CapableVm(Method getDefinedPackage) {
this.getDefinedPackage = getDefinedPackage;
}
/**
* {@inheritDoc}
*/
@MaybeNull
public Package apply(ByteArrayClassLoader classLoader, String name) {
try {
return (Package) getDefinedPackage.invoke(classLoader, name);
} catch (IllegalAccessException exception) {
throw new IllegalStateException(exception);
} catch (InvocationTargetException exception) {
throw new IllegalStateException(exception.getTargetException());
}
}
}
}
/**
* A persistence handler decides on whether the byte array that represents a loaded class is exposed by
* the {@link java.lang.ClassLoader#getResourceAsStream(String)} method.
*/
public enum PersistenceHandler {
/**
* The manifest persistence handler retains all class file representations and makes them accessible.
*/
MANIFEST(true) {
@Override
protected byte[] lookup(String name, ConcurrentMap typeDefinitions) {
return typeDefinitions.get(name);
}
@Override
protected URL url(String resourceName, ConcurrentMap typeDefinitions) {
if (!resourceName.endsWith(CLASS_FILE_SUFFIX)) {
return NO_URL;
} else if (resourceName.startsWith("/")) {
resourceName = resourceName.substring(1);
}
String typeName = resourceName.replace('/', '.').substring(FROM_BEGINNING, resourceName.length() - CLASS_FILE_SUFFIX.length());
byte[] binaryRepresentation = typeDefinitions.get(typeName);
return binaryRepresentation == null
? NO_URL
: doPrivileged(new UrlDefinitionAction(resourceName, binaryRepresentation));
}
@Override
protected void release(String name, ConcurrentMap typeDefinitions) {
/* do nothing */
}
},
/**
* The latent persistence handler hides all class file representations and does not make them accessible
* even before they are loaded.
*/
LATENT(false) {
@Override
protected byte[] lookup(String name, ConcurrentMap typeDefinitions) {
return typeDefinitions.remove(name);
}
@Override
protected URL url(String resourceName, ConcurrentMap typeDefinitions) {
return NO_URL;
}
@Override
protected void release(String name, ConcurrentMap typeDefinitions) {
typeDefinitions.remove(name);
}
};
/**
* The suffix of files in the Java class file format.
*/
private static final String CLASS_FILE_SUFFIX = ".class";
/**
* {@code true} if this persistence handler represents manifest class file storage.
*/
private final boolean manifest;
/**
* Creates a new persistence handler.
*
* @param manifest {@code true} if this persistence handler represents manifest class file storage.
*/
PersistenceHandler(boolean manifest) {
this.manifest = manifest;
}
/**
* Checks if this persistence handler represents manifest class file storage.
*
* @return {@code true} if this persistence handler represents manifest class file storage.
*/
public boolean isManifest() {
return manifest;
}
/**
* Performs a lookup of a class file by its name.
*
* @param name The name of the class to be loaded.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @return The byte array representing the requested class or {@code null} if no such class is known.
*/
@MaybeNull
protected abstract byte[] lookup(String name, ConcurrentMap typeDefinitions);
/**
* Returns a URL representing a class file.
*
* @param resourceName The name of the requested resource.
* @param typeDefinitions A mapping of byte arrays by their type names.
* @return A URL representing the type definition or {@code null} if the requested resource does not represent a class file.
*/
@MaybeNull
protected abstract URL url(String resourceName, ConcurrentMap typeDefinitions);
/**
* Removes the binary representation of the supplied type if this class loader is latent.
*
* @param name The name of the type.
* @param typeDefinitions A mapping of byte arrays by their type names.
*/
protected abstract void release(String name, ConcurrentMap typeDefinitions);
/**
* An action to define a URL that represents a class file.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class UrlDefinitionAction implements PrivilegedAction {
/**
* A dispatcher for creating URLs.
*/
private static final Dispatcher DISPATCHER = doPrivileged(JavaDispatcher.of(Dispatcher.class));
/**
* The URL's encoding character set.
*/
private static final String ENCODING = "UTF-8";
/**
* A value to define a standard port as Byte Buddy's URLs do not represent a port.
*/
private static final int NO_PORT = -1;
/**
* Indicates that Byte Buddy's URLs do not have a file segment.
*/
private static final String NO_FILE = "";
/**
* The name of the type that this URL represents.
*/
private final String typeName;
/**
* The binary representation of the type's class file.
*/
private final byte[] binaryRepresentation;
/**
* Creates a new URL definition action.
*
* @param typeName The name of the type that this URL represents.
* @param binaryRepresentation The binary representation of the type's class file.
*/
protected UrlDefinitionAction(String typeName, byte[] binaryRepresentation) {
this.typeName = typeName;
this.binaryRepresentation = binaryRepresentation;
}
/**
* A proxy for {@code java.security.AccessController#doPrivileged} that is activated if available.
*
* @param action The action to execute from a privileged context.
* @param The type of the action's resolved value.
* @return The action's resolved value.
*/
@AccessControllerPlugin.Enhance
private static T doPrivileged(PrivilegedAction action) {
return action.run();
}
/**
* {@inheritDoc}
*/
public URL run() {
try {
String path = URLEncoder.encode(typeName.replace('.', '/'), ENCODING);
URLStreamHandler handler = new ByteArrayUrlStreamHandler(binaryRepresentation);
URL url = DISPATCHER.of(URI.create(URL_SCHEMA + "://" + path), handler);
return url == null
? DISPATCHER.make(URL_SCHEMA, path, NO_PORT, NO_FILE, handler)
: url;
} catch (MalformedURLException exception) {
throw new IllegalStateException("Cannot create URL for " + typeName, exception);
} catch (UnsupportedEncodingException exception) {
throw new IllegalStateException("Could not find encoding: " + ENCODING, exception);
}
}
/**
* A stream handler that returns the given binary representation.
*/
@HashCodeAndEqualsPlugin.Enhance
protected static class ByteArrayUrlStreamHandler extends URLStreamHandler {
/**
* The binary representation of a type's class file.
*/
private final byte[] binaryRepresentation;
/**
* Creates a new byte array URL stream handler.
*
* @param binaryRepresentation The binary representation of a type's class file.
*/
protected ByteArrayUrlStreamHandler(byte[] binaryRepresentation) {
this.binaryRepresentation = binaryRepresentation;
}
/**
* {@inheritDoc}
*/
protected URLConnection openConnection(URL url) {
return new ByteArrayUrlConnection(url, new ByteArrayInputStream(binaryRepresentation));
}
/**
* A URL connection for a given byte array.
*/
protected static class ByteArrayUrlConnection extends URLConnection {
/**
* The input stream to return for this connection.
*/
private final InputStream inputStream;
/**
* Creates a new byte array URL connection.
*
* @param url The URL that this connection represents.
* @param inputStream The input stream to return from this connection.
*/
protected ByteArrayUrlConnection(URL url, InputStream inputStream) {
super(url);
this.inputStream = inputStream;
}
/**
* {@inheritDoc}
*/
public void connect() {
connected = true;
}
/**
* {@inheritDoc}
*/
public InputStream getInputStream() {
connect(); // Mimics the semantics of an actual URL connection.
return inputStream;
}
}
}
/**
* A dispatcher for interacting with {@link URL}.
*/
@JavaDispatcher.Proxied("java.net.URL")
protected interface Dispatcher {
/**
* Creates a {@link URL}.
*
* @param protocol The URL's protocol.
* @param host The host on the URL.
* @param port The port on the URL or a negative value if no port is defined.
* @param file The file on the URL.
* @param handler The stream handler to use.
* @return An appropriate URL.
* @throws MalformedURLException If the supplied URL is malformed.
*/
@JavaDispatcher.IsConstructor
URL make(String protocol, String host, int port, String file, URLStreamHandler handler) throws MalformedURLException;
/**
* Resolves a URL from an URI, if possible.
*
* @param uri The URI to represent.
* @param handler The stream handler to attach to that URL.
* @return An appropriate URL.
* @throws MalformedURLException If the supplied URL is malformed.
*/
@MaybeNull
@JavaDispatcher.IsStatic
@JavaDispatcher.Defaults
URL of(URI uri, URLStreamHandler handler) throws MalformedURLException;
}
}
}
/**
*
* A {@link net.bytebuddy.dynamic.loading.ByteArrayClassLoader} which applies child-first semantics for the
* given type definitions.
*
*
* Important: Package definitions remain their parent-first semantics as loaded package definitions do not expose their class loaders.
* Also, it is not possible to make this class or its subclass parallel-capable as the loading strategy is overridden.
*
*/
public static class ChildFirst extends ByteArrayClassLoader {
/**
* The suffix of files in the Java class file format.
*/
private static final String CLASS_FILE_SUFFIX = ".class";
/*
* Register class loader as parallel capable if the current VM supports it.
*/
static {
doRegisterAsParallelCapable();
}
/**
* Registers class loader as parallel capable if possible.
*/
@SuppressFBWarnings(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "Must be invoked from targeting class loader type.")
private static void doRegisterAsParallelCapable() {
try {
Method method = ClassLoader.class.getDeclaredMethod("registerAsParallelCapable");
method.setAccessible(true);
method.invoke(null);
} catch (Throwable ignored) {
/* do nothing */
}
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
*/
public ChildFirst(@MaybeNull ClassLoader parent, Map typeDefinitions) {
super(parent, typeDefinitions);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
*/
public ChildFirst(@MaybeNull ClassLoader parent, boolean sealed, Map typeDefinitions) {
super(parent, sealed, typeDefinitions);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param persistenceHandler The persistence handler of this class loader.
*/
public ChildFirst(@MaybeNull ClassLoader parent, Map typeDefinitions, PersistenceHandler persistenceHandler) {
super(parent, typeDefinitions, persistenceHandler);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param persistenceHandler The persistence handler of this class loader.
*/
public ChildFirst(@MaybeNull ClassLoader parent, boolean sealed, Map typeDefinitions, PersistenceHandler persistenceHandler) {
super(parent, sealed, typeDefinitions, persistenceHandler);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param persistenceHandler The persistence handler of this class loader.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
*/
public ChildFirst(@MaybeNull ClassLoader parent,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy) {
super(parent, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param persistenceHandler The persistence handler of this class loader.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
*/
public ChildFirst(@MaybeNull ClassLoader parent,
boolean sealed,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy) {
super(parent, sealed, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param persistenceHandler The persistence handler of this class loader.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param classFilePostProcessor A post processor for class files to apply p
*/
public ChildFirst(@MaybeNull ClassLoader parent,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy,
ClassFilePostProcessor classFilePostProcessor) {
super(parent, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy, classFilePostProcessor);
}
/**
* Creates a new child-first byte array class loader.
*
* @param parent The {@link java.lang.ClassLoader} that is the parent of this class loader.
* @param sealed {@code true} if this class loader is sealed.
* @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param persistenceHandler The persistence handler of this class loader.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param classFilePostProcessor A post processor for class files to apply p
*/
public ChildFirst(@MaybeNull ClassLoader parent,
boolean sealed,
Map typeDefinitions,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy,
ClassFilePostProcessor classFilePostProcessor) {
super(parent, sealed, typeDefinitions, protectionDomain, persistenceHandler, packageDefinitionStrategy, classFilePostProcessor);
}
/**
* Loads a given set of class descriptions and their binary representations using a child-first class loader.
*
* @param classLoader The parent class loader.
* @param types The unloaded types to be loaded.
* @return A map of the given type descriptions pointing to their loaded representations.
*/
public static Map> load(@MaybeNull ClassLoader classLoader, Map types) {
return load(classLoader,
types,
ClassLoadingStrategy.NO_PROTECTION_DOMAIN,
PersistenceHandler.LATENT,
PackageDefinitionStrategy.Trivial.INSTANCE,
false,
true);
}
/**
* Loads a given set of class descriptions and their binary representations using a child-first class loader.
*
* @param classLoader The parent class loader.
* @param types The unloaded types to be loaded.
* @param protectionDomain The protection domain to apply where {@code null} references an implicit protection domain.
* @param persistenceHandler The persistence handler of the created class loader.
* @param packageDefinitionStrategy The package definer to be queried for package definitions.
* @param forbidExisting {@code true} if the class loading should throw an exception if a class was already loaded by a parent class loader.
* @param sealed {@code true} if the class loader should be sealed.
* @return A map of the given type descriptions pointing to their loaded representations.
*/
@SuppressFBWarnings(value = "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED", justification = "Assuring privilege is explicit user responsibility.")
public static Map> load(@MaybeNull ClassLoader classLoader,
Map types,
@MaybeNull ProtectionDomain protectionDomain,
PersistenceHandler persistenceHandler,
PackageDefinitionStrategy packageDefinitionStrategy,
boolean forbidExisting,
boolean sealed) {
Map typesByName = new HashMap();
for (Map.Entry entry : types.entrySet()) {
typesByName.put(entry.getKey().getName(), entry.getValue());
}
classLoader = new ChildFirst(classLoader,
sealed,
typesByName,
protectionDomain,
persistenceHandler,
packageDefinitionStrategy,
ClassFilePostProcessor.NoOp.INSTANCE);
Map> result = new LinkedHashMap>();
for (TypeDescription typeDescription : types.keySet()) {
try {
Class> type = Class.forName(typeDescription.getName(), false, classLoader);
if (!GraalImageCode.getCurrent().isNativeImageExecution() && forbidExisting && type.getClassLoader() != classLoader) {
throw new IllegalStateException("Class already loaded: " + type);
}
result.put(typeDescription, type);
} catch (ClassNotFoundException exception) {
throw new IllegalStateException("Cannot load class " + typeDescription, exception);
}
}
return result;
}
/**
* {@inheritDoc}
*/
protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (SYNCHRONIZATION_STRATEGY.initialize().getClassLoadingLock(this, name)) {
Class> type = findLoadedClass(name);
if (type != null) {
return type;
}
try {
type = findClass(name);
if (resolve) {
resolveClass(type);
}
return type;
} catch (ClassNotFoundException exception) {
// If an unknown class is loaded, this implementation causes the findClass method of this instance
// to be triggered twice. This is however of minor importance because this would result in a
// ClassNotFoundException what does not alter the outcome.
return super.loadClass(name, resolve);
}
}
}
/**
* {@inheritDoc}
*/
public URL getResource(String name) {
URL url = persistenceHandler.url(name, typeDefinitions);
// If a class resource is defined by this class loader but it is not defined in a manifest manner,
// the resource of the parent class loader should be shadowed by 'null'. Note that the delegation
// model causes a redundant query to the persistent handler but renders a correct result.
return url != null || isShadowed(name)
? url
: super.getResource(name);
}
/**
* {@inheritDoc}
*/
public Enumeration getResources(String name) throws IOException {
URL url = persistenceHandler.url(name, typeDefinitions);
return url == null
? super.getResources(name)
: new PrependingEnumeration(url, super.getResources(name));
}
/**
* Checks if a resource name represents a class file of a class that was loaded by this class loader.
*
* @param resource The resource name of the class to be exposed as its class file.
* @return {@code true} if this class represents a class that is being loaded by this class loader.
*/
private boolean isShadowed(String resource) {
if (persistenceHandler.isManifest() || !resource.endsWith(CLASS_FILE_SUFFIX)) {
return false;
}
// This synchronization is required to avoid a racing condition to the actual class loading.
String name = resource.replace('/', '.').substring(0, resource.length() - CLASS_FILE_SUFFIX.length());
synchronized (SYNCHRONIZATION_STRATEGY.initialize().getClassLoadingLock(this, name)) {
if (typeDefinitions.containsKey(name)) {
return true;
}
Class> loadedClass = findLoadedClass(name);
return loadedClass != null && loadedClass.getClassLoader() == this;
}
}
/**
* An enumeration that prepends an element to another enumeration and skips the last element of the provided enumeration.
*/
protected static class PrependingEnumeration implements Enumeration {
/**
* The next element to return from this enumeration or {@code null} if such an element does not exist.
*/
@MaybeNull
private URL nextElement;
/**
* The enumeration from which the next elements should be pulled.
*/
private final Enumeration enumeration;
/**
* Creates a new prepending enumeration.
*
* @param url The first element of the enumeration.
* @param enumeration An enumeration that is used for pulling subsequent urls.
*/
protected PrependingEnumeration(URL url, Enumeration enumeration) {
nextElement = url;
this.enumeration = enumeration;
}
/**
* {@inheritDoc}
*/
public boolean hasMoreElements() {
return nextElement != null && enumeration.hasMoreElements();
}
/**
* {@inheritDoc}
*/
public URL nextElement() {
if (nextElement != null && enumeration.hasMoreElements()) {
try {
return nextElement;
} finally {
nextElement = enumeration.nextElement();
}
} else {
throw new NoSuchElementException();
}
}
}
}
/**
* An enumeration without any elements.
*/
protected enum EmptyEnumeration implements Enumeration {
/**
* The singleton instance.
*/
INSTANCE;
/**
* {@inheritDoc}
*/
public boolean hasMoreElements() {
return false;
}
/**
* {@inheritDoc}
*/
public URL nextElement() {
throw new NoSuchElementException();
}
}
/**
* An enumeration that contains a single element.
*/
protected static class SingletonEnumeration implements Enumeration {
/**
* The current element or {@code null} if this enumeration does not contain further elements.
*/
@MaybeNull
private URL element;
/**
* Creates a new singleton enumeration.
*
* @param element The only element.
*/
protected SingletonEnumeration(URL element) {
this.element = element;
}
/**
* {@inheritDoc}
*/
public boolean hasMoreElements() {
return element != null;
}
/**
* {@inheritDoc}
*/
public URL nextElement() {
if (element == null) {
throw new NoSuchElementException();
} else {
try {
return element;
} finally {
element = null;
}
}
}
}
}