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

net.bytebuddy.dynamic.ClassFileLocator Maven / Gradle / Ivy

Go to download

Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with a remaining dependency onto ASM. You should never depend on this module without repackaging Byte Buddy and ASM into your own namespace.

There is a newer version: 1.15.11
Show newest version
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> 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> 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 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 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 + '}'; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy