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

com.ui4j.bytebuddy.dynamic.loading.ByteArrayClassLoader Maven / Gradle / Ivy

The newest version!
package com.ui4j.bytebuddy.dynamic.loading;

import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * A {@link java.lang.ClassLoader} that is capable of loading explicitly defined classes. The class loader will free
 * any binary resources once a class that is defined by its binary data is loaded. This class loader is thread safe since
 * the class loading mechanics are only called from synchronized context.
 */
public class ByteArrayClassLoader extends ClassLoader {

    /**
     * A mutable map of type names mapped to their binary representation.
     */
    protected final Map typeDefinitions;

    /**
     * The persistence handler of this class loader.
     */
    protected final PersistenceHandler persistenceHandler;

    /**
     * The protection domain to apply. Might be {@code null} when referencing the default protection domain.
     */
    protected final ProtectionDomain protectionDomain;

    /**
     * The access control context of this class loader's instantiation.
     */
    protected final AccessControlContext accessControlContext;

    /**
     * Creates a new class loader for a given definition of classes.
     *
     * @param parent             The {@link java.lang.ClassLoader} that is the parent of this class loader.
     * @param typeDefinitions    A map of fully qualified class names pointing to their binary representations.
     * @param protectionDomain   The protection domain to apply where {@code null} references an implicit
     *                           protection domain.
     * @param persistenceHandler The persistence handler of this class loader.
     */
    public ByteArrayClassLoader(ClassLoader parent,
                                Map typeDefinitions,
                                ProtectionDomain protectionDomain,
                                PersistenceHandler persistenceHandler) {
        super(parent);
        this.typeDefinitions = new HashMap(typeDefinitions);
        this.protectionDomain = protectionDomain;
        this.persistenceHandler = persistenceHandler;
        accessControlContext = AccessController.getContext();
    }

    /**
     * Creates a new class loader for a given definition of classes.
     *
     * @param parent             The {@link java.lang.ClassLoader} that is the parent of this class loader.
     * @param typeDefinitions    A map of type descriptions pointing to their binary representations.
     * @param protectionDomain   The protection domain to apply where {@code null} references an implicit
     *                           protection domain.
     * @param persistenceHandler The persistence handler to be used by the created class loader.
     * @param childFirst         {@code true} if the class loader should apply child first semantics when loading
     *                           the {@code typeDefinitions}.
     * @return A corresponding class loader.
     */
    public static ClassLoader of(ClassLoader parent,
                                 Map typeDefinitions,
                                 ProtectionDomain protectionDomain,
                                 PersistenceHandler persistenceHandler,
                                 boolean childFirst) {
        Map rawTypeDefinitions = new HashMap(typeDefinitions.size());
        for (Map.Entry entry : typeDefinitions.entrySet()) {
            rawTypeDefinitions.put(entry.getKey().getName(), entry.getValue());
        }
        return childFirst
                ? new ChildFirst(parent, rawTypeDefinitions, protectionDomain, persistenceHandler)
                : new ByteArrayClassLoader(parent, rawTypeDefinitions, protectionDomain, persistenceHandler);
    }

    /**
     * Loads a given set of class descriptions and their binary representations.
     *
     * @param classLoader        The parent class loader.
     * @param types              The raw types to load.
     * @param protectionDomain   The protection domain to apply where {@code null} references an implicit
     *                           protection domain.
     * @param persistenceHandler The persistence handler of the created class loader.
     * @param childFirst         {@code true} {@code true} if the created class loader should apply child-first
     *                           semantics when loading the {@code types}.
     * @return A map of the given type descriptions pointing to their loaded representations.
     */
    public static Map> load(ClassLoader classLoader,
                                                      Map types,
                                                      ProtectionDomain protectionDomain,
                                                      PersistenceHandler persistenceHandler,
                                                      boolean childFirst) {
        Map> loadedTypes = new LinkedHashMap>(types.size());
        classLoader = ByteArrayClassLoader.of(classLoader, types, protectionDomain, persistenceHandler, childFirst);
        for (TypeDescription typeDescription : types.keySet()) {
            try {
                loadedTypes.put(typeDescription, classLoader.loadClass(typeDescription.getName()));
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException("Cannot load class " + typeDescription, e);
            }
        }
        return loadedTypes;
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        try {
            // This does not need synchronization because this method is only called from within
            // ClassLoader in a synchronized context.
            return AccessController.doPrivileged(new ClassLoadingAction(name), accessControlContext);
        } catch (PrivilegedActionException e) {
            throw (ClassNotFoundException) e.getCause();
        }
    }

    @Override
    public InputStream getResourceAsStream(String name) {
        InputStream inputStream = super.getResourceAsStream(name);
        if (inputStream != null) {
            return inputStream;
        } else {
            return persistenceHandler.inputStream(name, typeDefinitions);
        }
    }

    @Override
    public String toString() {
        return "ByteArrayClassLoader{" +
                "parent=" + getParent() +
                ", typeDefinitions=" + typeDefinitions +
                ", persistenceHandler=" + persistenceHandler +
                ", protectionDomain=" + protectionDomain +
                ", accessControlContext=" + accessControlContext +
                '}';
    }

    /**
     * A persistence handler decides on weather the byte array that represents a loaded class is exposed by
     * the {@link java.lang.ClassLoader#getResourceAsStream(String)} method.
     */
    public static enum PersistenceHandler {

        /**
         * The manifest persistence handler retains all class file representations and makes them accessible.
         */
        MANIFEST(true) {
            @Override
            protected byte[] lookup(String name, Map typeDefinitions) {
                return typeDefinitions.get(name);
            }

            @Override
            protected InputStream inputStream(String resourceName, Map typeDefinitions) {
                if (!resourceName.endsWith(CLASS_FILE_SUFFIX)) {
                    return null;
                }
                byte[] binaryRepresentation = typeDefinitions.get(resourceName.replace('/', '.')
                        .substring(0, resourceName.length() - CLASS_FILE_SUFFIX.length()));
                return binaryRepresentation == null
                        ? null
                        : new ByteArrayInputStream(binaryRepresentation);
            }
        },

        /**
         * The latent persistence handler hides all class file representations and does not make them accessible
         * even before they are loaded.
         */
        LATENT(false) {
            @Override
            protected byte[] lookup(String name, Map typeDefinitions) {
                return typeDefinitions.remove(name);
            }

            @Override
            protected InputStream inputStream(String resourceName, Map typeDefinitions) {
                return null;
            }
        };

        /**
         * The suffix of files in the Java class file format.
         */
        private static final String CLASS_FILE_SUFFIX = ".class";

        /**
         * {@code true} if this persistence handler represents manifest class file storage.
         */
        private final boolean manifest;

        /**
         * Creates a new persistence handler.
         *
         * @param manifest {@code true} if this persistence handler represents manifest class file storage.
         */
        private PersistenceHandler(boolean manifest) {
            this.manifest = manifest;
        }

        /**
         * Checks if this persistence handler represents manifest class file storage.
         *
         * @return {@code true} if this persistence handler represents manifest class file storage.
         */
        public boolean isManifest() {
            return manifest;
        }

        /**
         * Performs a lookup of a class file by its name.
         *
         * @param name            The name of the class to be loaded.
         * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
         * @return The byte array representing the requested class or {@code null} if no such class is known.
         */
        protected abstract byte[] lookup(String name, Map typeDefinitions);

        /**
         * Performs a lookup of an input stream for exposing a class file as a resource.
         *
         * @param resourceName    The resource name of the class to be exposed as its class file.
         * @param typeDefinitions A map of fully qualified class names pointing to their binary representations.
         * @return An input stream representing the requested resource or {@code null} if no such resource is known.
         */
        protected abstract InputStream inputStream(String resourceName, Map typeDefinitions);
    }

    /**
     * A {@link com.ui4j.bytebuddy.dynamic.loading.ByteArrayClassLoader} which applies child-first semantics for the
     * given type definitions.
     */
    public static class ChildFirst extends ByteArrayClassLoader {

        /**
         * The suffix of files in the Java class file format.
         */
        private static final String CLASS_FILE_SUFFIX = ".class";

        /**
         * Creates a new child-first byte array class loader.
         *
         * @param parent             The {@link java.lang.ClassLoader} that is the parent of this class loader.
         * @param typeDefinitions    A map of fully qualified class names pointing to their binary representations.
         * @param protectionDomain   The protection domain to apply where {@code null} references an implicit
         *                           protection domain.
         * @param persistenceHandler The persistence handler of this class loader.
         */
        public ChildFirst(ClassLoader parent,
                          Map typeDefinitions,
                          ProtectionDomain protectionDomain,
                          PersistenceHandler persistenceHandler) {
            super(parent, typeDefinitions, protectionDomain, persistenceHandler);
        }

        @Override
        protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class type = findLoadedClass(name);
            if (type != null) {
                return type;
            }
            try {
                type = findClass(name);
                if (resolve) {
                    resolveClass(type);
                }
                return type;
            } catch (ClassNotFoundException e) {
                // If an unknown class is loaded, this implementation causes the findClass method of this instance
                // to be triggered twice. This is however of minor importance because this would result in a
                // ClassNotFoundException which is rather uncommon.
                return super.loadClass(name, resolve);
            }
        }

        @Override
        public InputStream getResourceAsStream(String name) {
            InputStream inputStream = persistenceHandler.inputStream(name, typeDefinitions);
            // A non-persistent class must maintain to
            if (inputStream != null || (!persistenceHandler.isManifest() && isSelfDefined(name))) {
                return inputStream;
            } else {
                URL url = getResource(name);
                try {
                    return url != null ? url.openStream() : null;
                } catch (IOException ignored) {
                    return null;
                }
            }
        }

        /**
         * Checks if a resource name represents a class file of a class that was loaded by this class loader.
         *
         * @param resourceName The resource name of the class to be exposed as its class file.
         * @return {@code true} if this class represents a class that was already loaded by this class loader.
         */
        private boolean isSelfDefined(String resourceName) {
            if (!resourceName.endsWith(CLASS_FILE_SUFFIX)) {
                return false;
            }
            // This synchronization is required to avoid a racing condition to the actual class loading.
            synchronized (this) {
                String typeName = resourceName.replace('/', '.').substring(0, resourceName.length() - CLASS_FILE_SUFFIX.length());
                if (typeDefinitions.containsKey(typeName)) {
                    return true;
                }
                Class loadedClass = findLoadedClass(typeName);
                return loadedClass != null && loadedClass.getClassLoader() == this;
            }
        }

        @Override
        public String toString() {
            return "ByteArrayClassLoader.ChildFirst{" +
                    "parent=" + getParent() +
                    ", typeDefinitions=" + typeDefinitions +
                    ", protectionDomain=" + protectionDomain +
                    ", persistenceHandler=" + persistenceHandler +
                    ", accessControlContext=" + accessControlContext +
                    '}';
        }
    }

    /**
     * A class loading action is responsible to perform the loading of a class in a privileged security context.
     */
    private class ClassLoadingAction implements PrivilegedExceptionAction> {

        /**
         * A convenience index referencing the beginning of an array to improve code readability.
         */
        private static final int FROM_BEGINNING = 0;

        /**
         * The name of the type to be loaded.
         */
        private final String name;

        /**
         * Creates a new class loading action.
         *
         * @param name The name of the type to be loaded.
         */
        private ClassLoadingAction(String name) {
            this.name = name;
        }

        @Override
        public Class run() throws ClassNotFoundException {
            byte[] javaType = persistenceHandler.lookup(name, typeDefinitions);
            if (javaType != null) {
                return defineClass(name, javaType, FROM_BEGINNING, javaType.length, protectionDomain);
            }
            throw new ClassNotFoundException(name);
        }

        @Override
        public String toString() {
            return "ByteArrayClassLoader.ClassLoadingAction{classLoader=" + ByteArrayClassLoader.this + ", name='" + name + "'}";
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy