com.ui4j.bytebuddy.dynamic.loading.ClassLoaderByteArrayInjector Maven / Gradle / Ivy
The newest version!
package com.ui4j.bytebuddy.dynamic.loading;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.*;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* An injector that loads classes by reflectively invoking non-public methods on a given {@link java.lang.ClassLoader}.
*
* Note that the injector is only able to load classes in a linear manner. Thus, classes that refer to other classes
* which are not yet loaded cannot be injected but will result in a {@link java.lang.NoClassDefFoundError}. This becomes
* a problem when classes refer to each other using cyclic references. This injector can further not be applied to the
* bootstrap class loader which is usually represented by a {@code null} value and can therefore not be accessed by
* reflection.
*/
public class ClassLoaderByteArrayInjector {
/**
* A convenience reference to the default protection domain which is {@code null}.
*/
private static final ProtectionDomain DEFAULT_PROTECTION_DOMAIN = null;
/**
* A storage for the reflection method representations that are obtained on loading this classes.
*/
private static final ReflectionStore REFLECTION_STORE;
/**
* Obtains the reflective instances used by this injector or a no-op instance that throws the exception
* that occurred when attempting to obtain the reflective member instances.
*/
static {
ReflectionStore reflectionStore;
try {
Method findLoadedClassMethod = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class);
findLoadedClassMethod.setAccessible(true);
Method loadByteArrayMethod = ClassLoader.class.getDeclaredMethod("defineClass",
String.class,
byte[].class,
int.class,
int.class,
ProtectionDomain.class);
loadByteArrayMethod.setAccessible(true);
reflectionStore = new ReflectionStore.Resolved(findLoadedClassMethod, loadByteArrayMethod);
} catch (Exception e) {
reflectionStore = new ReflectionStore.Faulty(e);
}
REFLECTION_STORE = reflectionStore;
}
/**
* The class loader into which the classes are to be injected.
*/
private final ClassLoader classLoader;
/**
* The protection domain that is used when loading classes.
*/
private final ProtectionDomain protectionDomain;
/**
* The access control context of this class loader's instantiation.
*/
private final AccessControlContext accessControlContext;
/**
* Creates a new injector for the given {@link java.lang.ClassLoader} and a default
* {@link java.security.ProtectionDomain}.
*
* @param classLoader The {@link java.lang.ClassLoader} into which new class definitions are to be injected.
*/
public ClassLoaderByteArrayInjector(ClassLoader classLoader) {
this(classLoader, DEFAULT_PROTECTION_DOMAIN);
}
/**
* Creates a new injector for the given {@link java.lang.ClassLoader} and {@link java.security.ProtectionDomain}.
*
* @param classLoader The {@link java.lang.ClassLoader} into which new class definitions are to be injected.
* @param protectionDomain The protection domain to apply during class definition.
*/
public ClassLoaderByteArrayInjector(ClassLoader classLoader, ProtectionDomain protectionDomain) {
if (classLoader == null) {
throw new IllegalArgumentException("Cannot inject classes into the bootstrap class loader");
}
this.classLoader = classLoader;
this.protectionDomain = protectionDomain;
accessControlContext = AccessController.getContext();
}
/**
* Injects a given type mapping into a class loader byte array injector.
*
* @param classLoaderByteArrayInjector The target of the injection.
* @param types A mapping of types to their binary representation.
* @return A map of loaded classes which were injected into the class loader byte array injector.
*/
public static Map> inject(ClassLoaderByteArrayInjector classLoaderByteArrayInjector,
Map types) {
Map> loadedTypes = new LinkedHashMap>(types.size());
for (Map.Entry entry : types.entrySet()) {
loadedTypes.put(entry.getKey(), classLoaderByteArrayInjector.inject(entry.getKey().getName(), entry.getValue()));
}
return loadedTypes;
}
/**
* Explicitly loads a {@link java.lang.Class} by reflective access into the represented class loader.
*
* @param name The fully qualified name of the {@link java.lang.Class} to be loaded.
* @param binaryRepresentation The type's binary representation.
* @return The loaded class that is a result of the class loading attempt.
*/
public Class> inject(String name, byte[] binaryRepresentation) {
try {
synchronized (classLoader) {
Class> type = (Class>) REFLECTION_STORE.getFindLoadedClassMethod().invoke(classLoader, name);
if (type != null) {
return type;
} else {
try {
return AccessController.doPrivileged(new ClassLoadingAction(name, binaryRepresentation), accessControlContext);
} catch (PrivilegedActionException e) {
if (e.getCause() instanceof IllegalAccessException) {
throw (IllegalAccessException) e.getCause();
} else if (e.getCause() instanceof InvocationTargetException) {
throw (InvocationTargetException) e.getCause();
} else {
throw (RuntimeException) e.getCause();
}
}
}
}
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not access injection method", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException("Exception on invoking loader method", e.getCause());
}
}
@Override
public String toString() {
return "ClassLoaderByteArrayInjector{" +
"protectionDomain=" + protectionDomain +
"accessControlContext=" + accessControlContext +
", classLoader=" + classLoader +
'}';
}
/**
* A storage for method representations in order to access a class loader reflectively.
*/
protected static interface ReflectionStore {
/**
* Returns the method for finding a class on a class loader.
*
* @return The method for finding a class on a class loader.
*/
Method getFindLoadedClassMethod();
/**
* Returns the method for loading a class into a class loader.
*
* @return The method for loading a class into a class loader.
*/
Method getLoadByteArrayMethod();
/**
* Represents a successfully loaded method lookup.
*/
static class Resolved implements ReflectionStore {
/**
* The method for finding a class on a class loader.
*/
private final Method findLoadedClassMethod;
/**
* The method for loading a class into a class loader.
*/
private final Method loadByteArrayMethod;
/**
* Creates a new resolved reflection store.
*
* @param findLoadedClassMethod The method for finding a class on a class loader.
* @param loadByteArrayMethod The method for loading a class into a class loader.
*/
protected Resolved(Method findLoadedClassMethod, Method loadByteArrayMethod) {
this.findLoadedClassMethod = findLoadedClassMethod;
this.loadByteArrayMethod = loadByteArrayMethod;
}
@Override
public Method getFindLoadedClassMethod() {
return findLoadedClassMethod;
}
@Override
public Method getLoadByteArrayMethod() {
return loadByteArrayMethod;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Resolved resolved = (Resolved) other;
return findLoadedClassMethod.equals(resolved.findLoadedClassMethod)
&& loadByteArrayMethod.equals(resolved.loadByteArrayMethod);
}
@Override
public int hashCode() {
int result = findLoadedClassMethod.hashCode();
result = 31 * result + loadByteArrayMethod.hashCode();
return result;
}
@Override
public String toString() {
return "ClassLoaderByteArrayInjector.ReflectionStore.Resolved{" +
"findLoadedClassMethod=" + findLoadedClassMethod +
", loadByteArrayMethod=" + loadByteArrayMethod +
'}';
}
}
/**
* Represents an unsuccessfully loaded method lookup.
*/
static class Faulty implements ReflectionStore {
/**
* The message to display in an exception.
*/
private static final String MESSAGE = "Cannot access reflection API for class loading";
/**
* The exception that occurred when looking up the reflection methods.
*/
private final Exception exception;
/**
* Creates a new faulty reflection store.
*
* @param exception The exception that was thrown when attempting to lookup the method.
*/
protected Faulty(Exception exception) {
this.exception = exception;
}
@Override
public Method getFindLoadedClassMethod() {
throw new RuntimeException(MESSAGE, exception);
}
@Override
public Method getLoadByteArrayMethod() {
throw new RuntimeException(MESSAGE, exception);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& exception.equals(((Faulty) other).exception);
}
@Override
public int hashCode() {
return exception.hashCode();
}
@Override
public String toString() {
return "ClassLoaderByteArrayInjector.ReflectionStore.Faulty{exception=" + exception + '}';
}
}
}
/**
* A privileged action for loading a class reflectively.
*/
protected class ClassLoadingAction implements PrivilegedExceptionAction> {
/**
* A convenience variable representing the first index of an array, to make the code more readable.
*/
private static final int FROM_BEGINNING = 0;
/**
* The name of the class that is being loaded.
*/
private final String name;
/**
* The binary representation of the class that is being loaded.
*/
private final byte[] binaryRepresentation;
/**
* Creates a new class loading action.
*
* @param name The name of the class that is being loaded.
* @param binaryRepresentation The binary representation of the class that is being loaded.
*/
protected ClassLoadingAction(String name, byte[] binaryRepresentation) {
this.name = name;
this.binaryRepresentation = binaryRepresentation;
}
@Override
public Class> run() throws IllegalAccessException, InvocationTargetException {
return (Class>) REFLECTION_STORE.getLoadByteArrayMethod().invoke(classLoader,
name,
binaryRepresentation,
FROM_BEGINNING,
binaryRepresentation.length,
protectionDomain);
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ClassLoadingAction that = (ClassLoadingAction) other;
return Arrays.equals(binaryRepresentation, that.binaryRepresentation)
&& ClassLoaderByteArrayInjector.this.equals(that.getOuter())
&& name.equals(that.name);
}
/**
* Returns the outer instance.
*
* @return The outer instance.
*/
private ClassLoaderByteArrayInjector getOuter() {
return ClassLoaderByteArrayInjector.this;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + ClassLoaderByteArrayInjector.class.hashCode();
result = 31 * result + Arrays.hashCode(binaryRepresentation);
return result;
}
@Override
public String toString() {
return "ClassLoaderByteArrayInjector.ClassLoadingAction{" +
"injector=" + ClassLoaderByteArrayInjector.this +
", name='" + name + '\'' +
", binaryRepresentation=" + Arrays.toString(binaryRepresentation) +
'}';
}
}
}