net.bytebuddy.dynamic.ClassFileLocator Maven / Gradle / Ivy
Show all versions of byte-buddy-dep Show documentation
package net.bytebuddy.dynamic;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.utility.StreamDrainer;
import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.*;
import java.util.jar.JarFile;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Locates a class file or its byte array representation when it is given its type description.
*/
public interface ClassFileLocator extends Closeable {
/**
* The file extension for a Java class file.
*/
String CLASS_FILE_EXTENSION = ".class";
/**
* Locates the class file for a given type and returns the binary data of the class file.
*
* @param typeName The name of the type to locate a class file representation for.
* @return Any binary representation of the type which might be illegal.
* @throws java.io.IOException If reading a class file causes an error.
*/
Resolution locate(String typeName) throws IOException;
/**
* Represents a class file as binary data.
*/
interface Resolution {
/**
* Checks if this binary representation is valid.
*
* @return {@code true} if this binary representation is valid.
*/
boolean isResolved();
/**
* Finds the data of this binary representation. Calling this method is only legal for resolved instances.
* For non-resolved instances, an exception is thrown.
*
* @return The requested binary data. The returned array must not be altered.
*/
byte[] resolve();
/**
* A canonical representation of an illegal binary representation.
*/
class Illegal implements Resolution {
/**
* The name of the unresolved class file.
*/
private final String typeName;
/**
* Creates an illegal resolution for a class file.
*
* @param typeName The name of the unresolved class file.
*/
public Illegal(String typeName) {
this.typeName = typeName;
}
@Override
public boolean isResolved() {
return false;
}
@Override
public byte[] resolve() {
throw new IllegalStateException("Could not locate class file for " + typeName);
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Illegal illegal = (Illegal) object;
return typeName.equals(illegal.typeName);
}
@Override
public int hashCode() {
return typeName.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.Resolution.Illegal{" +
"typeName='" + typeName + '\'' +
'}';
}
}
/**
* Represents a byte array as binary data.
*/
class Explicit implements Resolution {
/**
* The represented data.
*/
private final byte[] binaryRepresentation;
/**
* Creates a new explicit resolution of a given array of binary data.
*
* @param binaryRepresentation The binary data to represent. The array must not be modified.
*/
@SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "The array is not to be modified by contract")
public Explicit(byte[] binaryRepresentation) {
this.binaryRepresentation = binaryRepresentation;
}
@Override
public boolean isResolved() {
return true;
}
@Override
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract")
public byte[] resolve() {
return binaryRepresentation;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& Arrays.equals(binaryRepresentation, ((Explicit) other).binaryRepresentation);
}
@Override
public int hashCode() {
return Arrays.hashCode(binaryRepresentation);
}
@Override
public String toString() {
return "ClassFileLocator.Resolution.Explicit{" +
"binaryRepresentation=<" + binaryRepresentation.length + " bytes>" +
'}';
}
}
}
/**
* A class file locator that cannot locate any class files.
*/
enum NoOp implements ClassFileLocator {
/**
* The singleton instance.
*/
INSTANCE;
@Override
public Resolution locate(String typeName) {
return new Resolution.Illegal(typeName);
}
@Override
public void close() throws IOException {
/* do nothing */
}
@Override
public String toString() {
return "ClassFileLocator.NoOp." + name();
}
}
/**
* A simple class file locator that returns class files from a selection of given types.
*/
class Simple implements ClassFileLocator {
/**
* The class files that are known to this class file locator mapped by their type name.
*/
private final Map classFiles;
/**
* Creates a new simple class file locator.
*
* @param classFiles The class files that are known to this class file locator mapped by their type name.
*/
public Simple(Map classFiles) {
this.classFiles = classFiles;
}
/**
* Creates a class file locator for a single known type.
*
* @param typeName The name of the type.
* @param binaryRepresentation The binary representation of the type.
* @return An appropriate class file locator.
*/
public static ClassFileLocator of(String typeName, byte[] binaryRepresentation) {
return new Simple(Collections.singletonMap(typeName, binaryRepresentation));
}
/**
* Creates a class file locator for a single known type with an additional fallback locator.
*
* @param typeName The name of the type.
* @param binaryRepresentation The binary representation of the type.
* @param fallback The class file locator to query in case that a lookup triggers any other type.
* @return An appropriate class file locator.
*/
public static ClassFileLocator of(String typeName, byte[] binaryRepresentation, ClassFileLocator fallback) {
return new Compound(new Simple(Collections.singletonMap(typeName, binaryRepresentation)), fallback);
}
@Override
public Resolution locate(String typeName) {
byte[] binaryRepresentation = classFiles.get(typeName);
return binaryRepresentation == null
? new Resolution.Illegal(typeName)
: new Resolution.Explicit(binaryRepresentation);
}
@Override
public void close() {
/* do nothing */
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& classFiles.equals(((Simple) other).classFiles);
}
@Override
public int hashCode() {
return classFiles.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.Simple{" +
"classFiles=" + classFiles +
'}';
}
}
/**
* A class file locator that queries a class loader for binary representations of class files.
*/
class ForClassLoader implements ClassFileLocator {
/**
* The class loader to query.
*/
private final ClassLoader classLoader;
/**
* Creates a new class file locator for the given class loader.
*
* @param classLoader The class loader to query which must not be the bootstrap class loader, i.e. {@code null}.
*/
protected ForClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Creates a class file locator that queries the system class loader.
*
* @return A class file locator that queries the system class loader.
*/
public static ClassFileLocator ofClassPath() {
return new ForClassLoader(ClassLoader.getSystemClassLoader());
}
/**
* Creates a class file locator for a given class loader.
*
* @param classLoader The class loader to be used. If this class loader represents the bootstrap class
* loader which is represented by the {@code null} value, this system class loader
* is used instead.
* @return A corresponding source locator.
*/
public static ClassFileLocator of(ClassLoader classLoader) {
return new ForClassLoader(classLoader == null
? ClassLoader.getSystemClassLoader()
: classLoader);
}
/**
* Attempts to create a binary representation of a loaded type by requesting data from its
* {@link java.lang.ClassLoader}.
*
* @param type The type of interest.
* @return The binary data to this type which might be illegal.
*/
public static Resolution read(Class> type) {
try {
ClassLoader classLoader = type.getClassLoader();
return locate(classLoader == null
? ClassLoader.getSystemClassLoader()
: classLoader, TypeDescription.ForLoadedType.getName(type));
} catch (IOException exception) {
throw new IllegalStateException("Cannot read class file for " + type, exception);
}
}
@Override
public Resolution locate(String typeName) throws IOException {
return locate(classLoader, typeName);
}
@Override
public void close() throws IOException {
if (classLoader instanceof Closeable) {
((Closeable) classLoader).close();
}
}
/**
* Locates the class file for the supplied type by requesting a resource from the class loader.
*
* @param classLoader The class loader to query for the resource.
* @param typeName The name of the type for which to locate a class file.
* @return A resolution for the class file.
* @throws IOException If reading the class file causes an exception.
*/
protected static Resolution locate(ClassLoader classLoader, String typeName) throws IOException {
InputStream inputStream = classLoader.getResourceAsStream(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
if (inputStream != null) {
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
}
} else {
return new Resolution.Illegal(typeName);
}
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& classLoader.equals(((ForClassLoader) other).classLoader);
}
@Override
public int hashCode() {
return classLoader.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.ForClassLoader{" +
"classLoader=" + classLoader +
'}';
}
/**
* A class file locator that queries a class loader for binary representations of class files.
* The class loader is only weakly referenced.
*/
public static class WeaklyReferenced extends WeakReference implements ClassFileLocator {
/**
* The represented class loader's hash code.
*/
private final int hashCode;
/**
* Creates a class file locator for a class loader that is weakly referenced.
*
* @param classLoader The class loader to represent.
*/
protected WeaklyReferenced(ClassLoader classLoader) {
super(classLoader);
hashCode = System.identityHashCode(classLoader);
}
/**
* Creates a class file locator for a given class loader. If the class loader is not the bootstrap
* class loader or the system class loader which cannot be collected, the class loader is only weakly
* referenced.
*
* @param classLoader The class loader to be used. If this class loader represents the bootstrap class
* loader which is represented by the {@code null} value, this system class loader
* is used instead.
* @return A corresponding source locator.
*/
public static ClassFileLocator of(ClassLoader classLoader) {
return classLoader == null || classLoader == ClassLoader.getSystemClassLoader()
? ForClassLoader.of(classLoader)
: new WeaklyReferenced(classLoader);
}
@Override
public Resolution locate(String typeName) throws IOException {
ClassLoader classLoader = get();
return classLoader == null
? new Resolution.Illegal(typeName)
: ForClassLoader.locate(classLoader, typeName);
}
@Override
public void close() throws IOException {
ClassLoader classLoader = get();
if (classLoader instanceof Closeable) {
((Closeable) classLoader).close();
}
}
@Override
public int hashCode() {
return hashCode;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
WeaklyReferenced that = (WeaklyReferenced) object;
ClassLoader classLoader = that.get();
return classLoader != null && get() == classLoader;
}
@Override
public String toString() {
return "ClassFileLocator.ForClassLoader.WeaklyReferenced{" +
"classLoader=" + get() +
", hashCode=" + hashCode +
'}';
}
}
}
/**
* A class file locator that locates classes within a Java jar file.
*/
class ForJarFile implements ClassFileLocator {
/**
* A list of potential locations of the runtime jar for different platforms.
*/
private static final List RUNTIME_LOCATIONS = Arrays.asList("lib/rt.jar", "../lib/rt.jar", "../Classes/classes.jar");
/**
* The jar file to read from.
*/
private final JarFile jarFile;
/**
* Creates a new class file locator for the given jar file.
*
* @param jarFile The jar file to read from.
*/
public ForJarFile(JarFile jarFile) {
this.jarFile = jarFile;
}
/**
* Creates a new class file locator for the given jar file.
*
* @param file The jar file to read from.
* @return A class file locator for the jar file.
* @throws IOException If an I/O exception is thrown.
*/
public static ClassFileLocator of(File file) throws IOException {
return new ForJarFile(new JarFile(file));
}
/**
* Resolves a class file locator for the class path that reads class files directly from the file system. The resulting
* class file locator does not imply classes on the boot path.
*
* @return A class file locator for the class path.
* @throws IOException If an I/O exception occurs.
*/
public static ClassFileLocator ofClassPath() throws IOException {
return ofClassPath(System.getProperty("java.class.path"));
}
/**
*
* Resolves a class file locator for the class path that reads class files directly from the file system.
*
*
* Note: The resulting class file locator does not include classes of the bootstrap class loader.
*
*
* @param classPath The class path to scan with the elements separated by {@code path.separator}.
* @return A class file locator for the class path.
* @throws IOException If an I/O exception occurs.
*/
public static ClassFileLocator ofClassPath(String classPath) throws IOException {
List classFileLocators = new ArrayList();
for (String element : Pattern.compile(System.getProperty("path.separator"), Pattern.LITERAL).split(classPath)) {
File file = new File(element);
if (file.isDirectory()) {
classFileLocators.add(new ForFolder(file));
} else if (file.isFile()) {
classFileLocators.add(of(file));
}
}
return new Compound(classFileLocators);
}
/**
* Resolves a class file locator for the runtime jar. If such a file does not exist or cannot be located, a runtime exception is thrown.
*
* @return A class file locator for the runtime jar, if available.
* @throws IOException If an I/O exception occurs.
*/
public static ClassFileLocator ofRuntimeJar() throws IOException {
String javaHome = System.getProperty("java.home").replace('\\', '/');
File runtimeJar = null;
for (String location : RUNTIME_LOCATIONS) {
File candidate = new File(javaHome, location);
if (candidate.isFile()) {
runtimeJar = candidate;
break;
}
}
if (runtimeJar == null) {
throw new IllegalStateException("Runtime jar does not exist in " + javaHome + " for any of " + RUNTIME_LOCATIONS);
}
return of(runtimeJar);
}
@Override
public Resolution locate(String typeName) throws IOException {
ZipEntry zipEntry = jarFile.getEntry(typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
if (zipEntry == null) {
return new Resolution.Illegal(typeName);
} else {
InputStream inputStream = jarFile.getInputStream(zipEntry);
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
}
}
}
@Override
public void close() throws IOException {
jarFile.close();
}
@Override
public boolean equals(Object other) {
return this == other || other instanceof ForJarFile
&& jarFile.equals(((ForJarFile) other).jarFile);
}
@Override
public int hashCode() {
return jarFile.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.ForJarFile{" +
"jarFile=" + jarFile +
'}';
}
}
/**
* A class file locator that locates classes within a Java jmod file. This class file locator should not be used
* for reading modular jar files for which {@link ForJarFile} is appropriate.
*/
class ForModuleFile implements ClassFileLocator {
/**
* The file extension of a modular Java package.
*/
private static final String JMOD_FILE_EXTENSION = ".jmod";
/**
* A list of potential locations of the boot path for different platforms.
*/
private static final List BOOT_LOCATIONS = Arrays.asList("jmods", "../jmods");
/**
* The represented jmod file.
*/
private final ZipFile zipFile;
/**
* Creates a new class file locator for a jmod file.
*
* @param zipFile The represented jmod file.
*/
public ForModuleFile(ZipFile zipFile) {
this.zipFile = zipFile;
}
/**
* Creates a new class file locator for this VM's boot module path.
*
* @return A class file locator for this VM's boot module path.
* @throws IOException If an I/O error occurs.
*/
public static ClassFileLocator ofBootPath() throws IOException {
String javaHome = System.getProperty("java.home").replace('\\', '/');
File bootPath = null;
for (String location : BOOT_LOCATIONS) {
File candidate = new File(javaHome, location);
if (candidate.isDirectory()) {
bootPath = candidate;
break;
}
}
if (bootPath == null) {
throw new IllegalStateException("Boot modules do not exist in " + javaHome + " for any of " + BOOT_LOCATIONS);
}
return ofBootPath(bootPath);
}
/**
* Creates a new class file locator for a Java boot module path.
*
* @param bootPath The boot path folder.
* @return A class file locator for this VMs boot module path.
* @throws IOException If an I/O error occurs.
*/
public static ClassFileLocator ofBootPath(File bootPath) throws IOException {
File[] module = bootPath.listFiles();
if (module == null) {
return NoOp.INSTANCE;
}
List classFileLocators = new ArrayList(module.length);
for (File aModule : module) {
if (aModule.isFile()) {
classFileLocators.add(of(aModule));
}
}
return new Compound(classFileLocators);
}
/**
*
* Resolves a class file locator for this VM's Java module path that reads class files directly from the file system.
*
*
* Note: The resulting class file locator does not include classes of the bootstrap class loader.
*
*
* @return A class file locator for the class path.
* @throws IOException If an I/O exception occurs.
*/
public static ClassFileLocator ofModulePath() throws IOException {
String modulePath = System.getProperty("jdk.module.path");
return modulePath == null
? NoOp.INSTANCE
: ofModulePath(modulePath);
}
/**
*
* Resolves a class file locator for a Java module path that reads class files directly from the file system. All
* elements of the module path are resolved releatively to this VM's {@code user.dir}.
*
*
* Note: The resulting class file locator does not include classes of the bootstrap class loader.
*
*
* @param modulePath The module path to scan with the elements separated by {@code path.separator}.
* @return A class file locator for the class path.
* @throws IOException If an I/O exception occurs.
*/
public static ClassFileLocator ofModulePath(String modulePath) throws IOException {
return ofModulePath(modulePath, System.getProperty("user.dir"));
}
/**
*
* Resolves a class file locator for a Java module path that reads class files directly from the file system.
*
*
* Note: The resulting class file locator does not include classes of the bootstrap class loader.
*
*
* @param modulePath The module path to scan with the elements separated by {@code path.separator}.
* @param baseFolder The relative location of the elements on the module path.
* @return A class file locator for the class path.
* @throws IOException If an I/O exception occurs.
*/
public static ClassFileLocator ofModulePath(String modulePath, String baseFolder) throws IOException {
List classFileLocators = new ArrayList();
for (String element : Pattern.compile(System.getProperty("path.separator"), Pattern.LITERAL).split(modulePath)) {
File file = new File(baseFolder, element);
if (file.isDirectory()) {
File[] module = file.listFiles();
if (module != null) {
for (File aModule : module) {
if (aModule.isDirectory()) {
classFileLocators.add(new ForFolder(aModule));
} else if (aModule.isFile()) {
classFileLocators.add(aModule.getName().endsWith(JMOD_FILE_EXTENSION)
? of(aModule)
: ForJarFile.of(aModule));
}
}
}
} else if (file.isFile()) {
classFileLocators.add(file.getName().endsWith(JMOD_FILE_EXTENSION)
? of(file)
: ForJarFile.of(file));
}
}
return new Compound(classFileLocators);
}
/**
* Returns a class file locator for the given module file.
*
* @param file The module file.
* @return A class file locator for the given module
* @throws IOException If an I/O error occurs.
*/
public static ClassFileLocator of(File file) throws IOException {
return new ForModuleFile(new ZipFile(file));
}
@Override
public Resolution locate(String typeName) throws IOException {
ZipEntry zipEntry = zipFile.getEntry("classes/" + typeName.replace('.', '/') + CLASS_FILE_EXTENSION);
if (zipEntry == null) {
return new Resolution.Illegal(typeName);
} else {
InputStream inputStream = zipFile.getInputStream(zipEntry);
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
}
}
}
@Override
public void close() throws IOException {
zipFile.close();
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
ForModuleFile that = (ForModuleFile) object;
return zipFile.equals(that.zipFile);
}
@Override
public int hashCode() {
return zipFile.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.ForModuleFile{" +
"zipFile=" + zipFile +
'}';
}
}
/**
* A class file locator that finds files from a standardized Java folder structure with
* folders donating packages and class files being saved as {@code .class} files
* within their package folder.
*/
class ForFolder implements ClassFileLocator {
/**
* The base folder of the package structure.
*/
private final File folder;
/**
* Creates a new class file locator for a folder structure of class files.
*
* @param folder The base folder of the package structure.
*/
public ForFolder(File folder) {
this.folder = folder;
}
@Override
public Resolution locate(String typeName) throws IOException {
File file = new File(folder, typeName.replace('.', File.separatorChar) + CLASS_FILE_EXTENSION);
if (file.exists()) {
InputStream inputStream = new FileInputStream(file);
try {
return new Resolution.Explicit(StreamDrainer.DEFAULT.drain(inputStream));
} finally {
inputStream.close();
}
} else {
return new Resolution.Illegal(typeName);
}
}
@Override
public void close() throws IOException {
/* do nothing */
}
@Override
public boolean equals(Object other) {
return this == other || other instanceof ForFolder
&& folder.equals(((ForFolder) other).folder);
}
@Override
public int hashCode() {
return folder.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.ForFolder{" +
"folder=" + folder +
'}';
}
}
/**
* A Java agent that allows the location of class files by emulating a retransformation. Note that this class file
* locator causes a class to be loaded in order to look up its class file. Also, this locator does deliberately not
* support the look-up of classes that represent lambda expressions.
*/
class AgentBased implements ClassFileLocator {
/**
* The name of the Byte Buddy {@code net.bytebuddy.agent.Installer} class.
*/
private static final String INSTALLER_TYPE = "net.bytebuddy.agent.Installer";
/**
* The name of the {@code net.bytebuddy.agent.Installer} field containing an installed {@link Instrumentation}.
*/
private static final String INSTRUMENTATION_FIELD = "instrumentation";
/**
* Indicator for accessing a field using reflection to make the code more readable.
*/
private static final Object STATIC_FIELD = null;
/**
* The instrumentation instance to use for looking up the binary format of a type.
*/
private final Instrumentation instrumentation;
/**
* The delegate to load a class by its name.
*/
private final ClassLoadingDelegate classLoadingDelegate;
/**
* Creates an agent-based class file locator.
*
* @param instrumentation The instrumentation to be used.
* @param classLoader The class loader to read a class from.
*/
public AgentBased(Instrumentation instrumentation, ClassLoader classLoader) {
this(instrumentation, ClassLoadingDelegate.Default.of(classLoader));
}
/**
* Creates an agent-based class file locator.
*
* @param instrumentation The instrumentation to be used.
* @param classLoadingDelegate The delegate responsible for class loading.
*/
public AgentBased(Instrumentation instrumentation, ClassLoadingDelegate classLoadingDelegate) {
if (!instrumentation.isRetransformClassesSupported()) {
throw new IllegalArgumentException(instrumentation + " does not support retransformation");
}
this.instrumentation = instrumentation;
this.classLoadingDelegate = classLoadingDelegate;
}
/**
* Returns an agent-based class file locator for the given class loader and an already installed
* Byte Buddy-agent.
*
* @param classLoader The class loader that is expected to load the looked-up a class.
* @return A class file locator for the given class loader based on a Byte Buddy agent.
*/
public static ClassFileLocator fromInstalledAgent(ClassLoader classLoader) {
try {
Instrumentation instrumentation = (Instrumentation) ClassLoader.getSystemClassLoader()
.loadClass(INSTALLER_TYPE)
.getDeclaredField(INSTRUMENTATION_FIELD)
.get(STATIC_FIELD);
if (instrumentation == null) {
throw new IllegalStateException("The Byte Buddy agent is not installed");
}
return new AgentBased(instrumentation, classLoader);
} catch (RuntimeException exception) {
throw exception;
} catch (Exception exception) {
throw new IllegalStateException("The Byte Buddy agent is not installed or not accessible", exception);
}
}
/**
* Returns a class file locator that is capable of locating a class file for the given type using the given instrumentation instance.
*
* @param instrumentation The instrumentation instance to query for a retransformation.
* @param type The locatable type which class loader is used as a fallback.
* @return A class file locator for locating the class file of the given type.
*/
public static ClassFileLocator of(Instrumentation instrumentation, Class> type) {
return new AgentBased(instrumentation, ClassLoadingDelegate.Explicit.of(type));
}
@Override
public Resolution locate(String typeName) {
try {
ExtractionClassFileTransformer classFileTransformer = new ExtractionClassFileTransformer(classLoadingDelegate.getClassLoader(), typeName);
instrumentation.addTransformer(classFileTransformer, true);
try {
instrumentation.retransformClasses(classLoadingDelegate.locate(typeName));
byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
return binaryRepresentation == null
? new Resolution.Illegal(typeName)
: new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch (RuntimeException exception) {
throw exception;
} catch (Exception ignored) {
return new Resolution.Illegal(typeName);
}
}
@Override
public void close() throws IOException {
/* do nothing */
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& classLoadingDelegate.equals(((AgentBased) other).classLoadingDelegate)
&& instrumentation.equals(((AgentBased) other).instrumentation);
}
@Override
public int hashCode() {
return 31 * instrumentation.hashCode() + classLoadingDelegate.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased{" +
"instrumentation=" + instrumentation +
", classLoadingDelegate=" + classLoadingDelegate +
'}';
}
/**
* A delegate that is queried for loading a class.
*/
public interface ClassLoadingDelegate {
/**
* Loads a class by its name.
*
* @param name The name of the type.
* @return The class with the given name.
* @throws ClassNotFoundException If a class cannot be found.
*/
Class> locate(String name) throws ClassNotFoundException;
/**
* Returns the underlying class loader.
*
* @return The underlying class loader.
*/
ClassLoader getClassLoader();
/**
* A default implementation of a class loading delegate.
*/
class Default implements ClassLoadingDelegate {
/**
* The underlying class loader.
*/
protected final ClassLoader classLoader;
/**
* Creates a default class loading delegate.
*
* @param classLoader The class loader to be queried.
*/
protected Default(ClassLoader classLoader) {
this.classLoader = classLoader;
}
/**
* Creates a class loading delegate for the given class loader.
*
* @param classLoader The class loader for which to create a delegate.
* @return The class loading delegate for the provided class loader.
*/
public static ClassLoadingDelegate of(ClassLoader classLoader) {
return ForDelegatingClassLoader.isDelegating(classLoader)
? new ForDelegatingClassLoader(classLoader)
: new Default(classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader);
}
@Override
public Class> locate(String name) throws ClassNotFoundException {
return classLoader.loadClass(name);
}
@Override
public ClassLoader getClassLoader() {
return classLoader;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
Default aDefault = (Default) other;
return !(classLoader != null ? !classLoader.equals(aDefault.classLoader) : aDefault.classLoader != null);
}
@Override
public int hashCode() {
return classLoader != null ? classLoader.hashCode() : 0;
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased.ClassLoadingDelegate.Default{" +
"classLoader=" + classLoader +
'}';
}
}
/**
* A class loading delegate that accounts for a {@code sun.reflect.DelegatingClassLoader} which
* cannot load its own classes by name.
*/
class ForDelegatingClassLoader extends Default {
/**
* The name of the delegating class loader.
*/
private static final String DELEGATING_CLASS_LOADER_NAME = "sun.reflect.DelegatingClassLoader";
/**
* An index indicating the first element of a collection.
*/
private static final int ONLY = 0;
/**
* A dispatcher for extracting a class loader's loaded classes.
*/
private static final Dispatcher.Initializable DISPATCHER;
/*
* Locates the {@link java.lang.ClassLoader}'s field that contains all loaded classes.
*/
static {
Dispatcher.Initializable dispatcher;
try {
dispatcher = new Dispatcher.Resolved(ClassLoader.class.getDeclaredField("classes"));
} catch (NoSuchFieldException exception) {
dispatcher = new Dispatcher.Unresolved(exception);
}
DISPATCHER = dispatcher;
}
/**
* Creates a class loading delegate for a delegating class loader.
*
* @param classLoader The delegating class loader.
*/
protected ForDelegatingClassLoader(ClassLoader classLoader) {
super(classLoader);
}
/**
* Checks if a class loader is a delegating class loader.
*
* @param classLoader The class loader to inspect.
* @return {@code true} if the class loader is a delegating class loader.
*/
protected static boolean isDelegating(ClassLoader classLoader) {
return classLoader != null && classLoader.getClass().getName().equals(DELEGATING_CLASS_LOADER_NAME);
}
@Override
@SuppressWarnings("unchecked")
public Class> locate(String name) throws ClassNotFoundException {
Vector> classes;
try {
classes = DISPATCHER.initialize().extract(classLoader);
} catch (RuntimeException ignored) {
return super.locate(name);
}
if (classes.size() != 1) {
return super.locate(name);
}
Class> type = classes.get(ONLY);
return TypeDescription.ForLoadedType.getName(type).equals(name)
? type
: super.locate(name);
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader{" +
"classLoader=" + classLoader +
'}';
}
/**
* Representation of a Java {@link java.lang.reflect.Field}.
*/
protected interface Dispatcher {
/**
* Reads the classes of the represented collection.
*
* @param classLoader The class loader to read from.
* @return The class loader's loaded classes.
*/
Vector> extract(ClassLoader classLoader);
/**
* An unitialized version of a dispatcher for extracting a class loader's loaded classes.
*/
interface Initializable {
/**
* Initializes the dispatcher.
*
* @return An initialized dispatcher.
*/
Dispatcher initialize();
}
/**
* Represents a field that could be located.
*/
class Resolved implements Dispatcher, Initializable, PrivilegedAction {
/**
* The represented field.
*/
private final Field field;
/**
* Creates a new resolved field.
*
* @param field the represented field.l
*/
public Resolved(Field field) {
this.field = field;
}
@Override
public Dispatcher initialize() {
return AccessController.doPrivileged(this);
}
@Override
@SuppressWarnings("unchecked")
public Vector> extract(ClassLoader classLoader) {
try {
return (Vector>) field.get(classLoader);
} catch (IllegalAccessException exception) {
throw new IllegalStateException("Cannot access field", exception);
}
}
@Override
public Dispatcher run() {
field.setAccessible(true);
return this;
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& field.equals(((Resolved) other).field);
}
@Override
public int hashCode() {
return field.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader.Dispatcher.Resolved{" +
"field=" + field +
'}';
}
}
/**
* Represents a field that could not be located.
*/
class Unresolved implements Initializable {
/**
* The exception that occurred when attempting to locate the field.
*/
private final Exception exception;
/**
* Creates a representation of a non-resolved field.
*
* @param exception The exception that occurred when attempting to locate the field.
*/
public Unresolved(Exception exception) {
this.exception = exception;
}
@Override
public Dispatcher initialize() {
throw new IllegalStateException("Could not locate classes vector", exception);
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& exception.equals(((Unresolved) other).exception);
}
@Override
public int hashCode() {
return exception.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased.ClassLoadingDelegate.ForDelegatingClassLoader.Dispatcher.Unresolved{" +
"exception=" + exception +
'}';
}
}
}
}
/**
* A class loading delegate that allows the location of explicitly registered classes that cannot
* be located by a class loader directly. This allows for locating classes that are loaded by
* an anonymous class loader which does not register its classes in a system dictionary.
*/
class Explicit implements ClassLoadingDelegate {
/**
* A class loading delegate that is queried for classes that are not registered explicitly.
*/
private final ClassLoadingDelegate fallbackDelegate;
/**
* The map of registered classes mapped by their name.
*/
private final Map> types;
/**
* Creates a new class loading delegate with a possibility of looking up explicitly
* registered classes.
*
* @param classLoader The class loader to be used for looking up classes.
* @param types A collection of classes that cannot be looked up explicitly.
*/
public Explicit(ClassLoader classLoader, Collection extends Class>> types) {
this(Default.of(classLoader), types);
}
/**
* Creates a new class loading delegate with a possibility of looking up explicitly
* registered classes.
*
* @param fallbackDelegate The class loading delegate to query for any class that is not
* registered explicitly.
* @param types A collection of classes that cannot be looked up explicitly.
*/
public Explicit(ClassLoadingDelegate fallbackDelegate, Collection extends Class>> types) {
this.fallbackDelegate = fallbackDelegate;
this.types = new HashMap>();
for (Class> type : types) {
this.types.put(TypeDescription.ForLoadedType.getName(type), type);
}
}
/**
* Creates an explicit class loading delegate for the given type.
*
* @param type The type that is explicitly locatable.
* @return A suitable class loading delegate.
*/
public static ClassLoadingDelegate of(Class> type) {
return new Explicit(type.getClassLoader(), Collections.singleton(type));
}
@Override
public Class> locate(String name) throws ClassNotFoundException {
Class> type = types.get(name);
return type == null
? fallbackDelegate.locate(name)
: type;
}
@Override
public ClassLoader getClassLoader() {
return fallbackDelegate.getClassLoader();
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& fallbackDelegate.equals(((Explicit) other).fallbackDelegate)
&& types.equals(((Explicit) other).types);
}
@Override
public int hashCode() {
int result = fallbackDelegate.hashCode();
result = 31 * result + types.hashCode();
return result;
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased.ClassLoadingDelegate.Explicit{" +
"fallbackDelegate=" + fallbackDelegate +
", types=" + types +
'}';
}
}
}
/**
* A non-operational class file transformer that remembers the binary format of a given class.
*/
protected static class ExtractionClassFileTransformer implements ClassFileTransformer {
/**
* An indicator that an attempted class file transformation did not alter the handed class file.
*/
private static final byte[] DO_NOT_TRANSFORM = null;
/**
* The class loader that is expected to have loaded the looked-up a class.
*/
private final ClassLoader classLoader;
/**
* The name of the type to look up.
*/
private final String typeName;
/**
* The binary representation of the looked-up class.
*/
@SuppressFBWarnings(value = "VO_VOLATILE_REFERENCE_TO_ARRAY", justification = "The array is not to be modified by contract")
private volatile byte[] binaryRepresentation;
/**
* Creates a class file transformer for the purpose of extraction.
*
* @param classLoader The class loader that is expected to have loaded the looked-up a class.
* @param typeName The name of the type to look up.
*/
protected ExtractionClassFileTransformer(ClassLoader classLoader, String typeName) {
this.classLoader = classLoader;
this.typeName = typeName;
}
@Override
@SuppressFBWarnings(value = {"EI_EXPOSE_REP", "EI_EXPOSE_REP2"}, justification = "The array is not to be modified by contract")
public byte[] transform(ClassLoader classLoader,
String internalName,
Class> redefinedType,
ProtectionDomain protectionDomain,
byte[] binaryRepresentation) {
if (internalName != null && isChild(classLoader) && typeName.equals(internalName.replace('/', '.'))) {
this.binaryRepresentation = binaryRepresentation;
}
return DO_NOT_TRANSFORM;
}
/**
* Checks if the given class loader is a child of the specified class loader.
*
* @param classLoader The class loader that loaded the retransformed class.
* @return {@code true} if te given class loader is a child of the specified class loader.
*/
private boolean isChild(ClassLoader classLoader) {
if (this.classLoader == null) {
return true; // The bootstrap class loader is any class loader's parent.
}
do {
if (classLoader == this.classLoader) {
return true;
}
} while ((classLoader = classLoader.getParent()) != null);
return false;
}
/**
* Returns the binary representation of the class file that was looked up. The returned array must never be modified.
*
* @return The binary representation of the class file or {@code null} if no such class file could
* be located.
*/
@SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "The array is not to be modified by contract")
protected byte[] getBinaryRepresentation() {
return binaryRepresentation;
}
@Override
public String toString() {
return "ClassFileLocator.AgentBased.ExtractionClassFileTransformer{" +
"classLoader=" + classLoader +
", typeName=" + typeName +
", binaryRepresentation=" +
(binaryRepresentation != null
? "<" + binaryRepresentation.length + " bytes>"
: "null") +
'}';
}
}
}
/**
* A compound {@link ClassFileLocator} that chains several locators.
* Any class file locator is queried in the supplied order until one locator is able to provide an input
* stream of the class file.
*/
class Compound implements ClassFileLocator, Closeable {
/**
* The {@link ClassFileLocator}s which are represented by this compound
* class file locator in the order of their application.
*/
private final List extends ClassFileLocator> classFileLocators;
/**
* Creates a new compound class file locator.
*
* @param classFileLocator The {@link ClassFileLocator}s to be
* represented by this compound class file locator in the order of their application.
*/
public Compound(ClassFileLocator... classFileLocator) {
this(Arrays.asList(classFileLocator));
}
/**
* Creates a new compound class file locator.
*
* @param classFileLocators The {@link ClassFileLocator}s to be represented by this compound class file locator in
* the order of their application.
*/
public Compound(List extends ClassFileLocator> classFileLocators) {
this.classFileLocators = classFileLocators;
}
@Override
public Resolution locate(String typeName) throws IOException {
for (ClassFileLocator classFileLocator : classFileLocators) {
Resolution resolution = classFileLocator.locate(typeName);
if (resolution.isResolved()) {
return resolution;
}
}
return new Resolution.Illegal(typeName);
}
@Override
public void close() throws IOException {
for (ClassFileLocator classFileLocator : classFileLocators) {
classFileLocator.close();
}
}
@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& classFileLocators.equals(((Compound) other).classFileLocators);
}
@Override
public int hashCode() {
return classFileLocators.hashCode();
}
@Override
public String toString() {
return "ClassFileLocator.Compound{classFileLocators=" + classFileLocators + '}';
}
}
}