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

name.remal.reflection.ExtendedURLClassLoader Maven / Gradle / Ivy

The newest version!
package name.remal.reflection;

import static name.remal.reflection.ExtendedURLClassLoader.LoadingOrder.PARENT_FIRST;
import static name.remal.reflection.ExtendedURLClassLoader.LoadingOrder.PARENT_ONLY;
import static name.remal.reflection.ExtendedURLClassLoader.LoadingOrder.THIS_FIRST;
import static name.remal.reflection.ExtendedURLClassLoader.LoadingOrder.THIS_ONLY;
import static org.apache.commons.lang3.ArrayUtils.contains;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class ExtendedURLClassLoader extends URLClassLoader {

    static {
        ClassLoader.registerAsParallelCapable();
    }

    public enum LoadingOrder {
        PARENT_FIRST, THIS_FIRST, PARENT_ONLY, THIS_ONLY
    }

    @FunctionalInterface
    public interface LoadingOrderFactory {
        @NotNull
        LoadingOrder getLoadingOrder(@NotNull String resourceName);
    }

    @NotNull
    private final LoadingOrderFactory loadingOrderFactory;

    public ExtendedURLClassLoader(@NotNull LoadingOrderFactory loadingOrderFactory, @NotNull URL[] urls, @Nullable ClassLoader parent) {
        super(uniqueURLs(urls), parent != null ? parent : getSystemClassLoader());
        this.loadingOrderFactory = loadingOrderFactory;
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrder loadingOrder, @NotNull URL[] urls, @Nullable ClassLoader parent) {
        this(__ -> loadingOrder, urls, parent);
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrderFactory loadingOrderFactory, @NotNull Iterable urls, @Nullable ClassLoader parent) {
        this(loadingOrderFactory, iterableUrlsToArray(urls), parent);
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrder loadingOrder, @NotNull Iterable urls, @Nullable ClassLoader parent) {
        this(__ -> loadingOrder, urls, parent);
    }

    public ExtendedURLClassLoader(@NotNull URL[] urls, @Nullable ClassLoader parent) {
        this(PARENT_FIRST, urls, parent);
    }

    public ExtendedURLClassLoader(@NotNull Iterable urls, @Nullable ClassLoader parent) {
        this(PARENT_FIRST, urls, parent);
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrderFactory loadingOrderFactory, @NotNull URL[] urls) {
        this(loadingOrderFactory, urls, getSystemClassLoader());
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrder loadingOrder, @NotNull URL[] urls) {
        this(loadingOrder, urls, getSystemClassLoader());
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrderFactory loadingOrderFactory, @NotNull Iterable urls) {
        this(loadingOrderFactory, urls, getSystemClassLoader());
    }

    public ExtendedURLClassLoader(@NotNull LoadingOrder loadingOrder, @NotNull Iterable urls) {
        this(loadingOrder, urls, getSystemClassLoader());
    }

    public ExtendedURLClassLoader(@NotNull URL[] urls) {
        this(urls, getSystemClassLoader());
    }

    public ExtendedURLClassLoader(@NotNull Iterable urls) {
        this(urls, getSystemClassLoader());
    }


    @NotNull
    @Override
    public final Class loadClass(@NotNull String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    @NotNull
    @Override
    protected Class loadClass(@NotNull String className, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(className)) {
            Class loadedClass = findLoadedClass(className);

            if (loadedClass == null) loadedClass = findBootstrapClassOrNull(className);

            if (loadedClass == null) {
                String resourceName = className.replace('.', '/') + ".class";
                LoadingOrder loadingOrder = loadingOrderFactory.getLoadingOrder(resourceName);
                if (PARENT_FIRST == loadingOrder) {
                    loadedClass = findParentClassOrNull(className);
                    if (loadedClass == null) loadedClass = findClassOrNull(className);
                } else if (THIS_FIRST == loadingOrder) {
                    loadedClass = findClassOrNull(className);
                    if (loadedClass == null) loadedClass = findParentClassOrNull(className);
                } else if (PARENT_ONLY == loadingOrder) {
                    loadedClass = findParentClassOrNull(className);
                } else if (THIS_ONLY == loadingOrder) {
                    loadedClass = findClassOrNull(className);
                } else {
                    throw new IllegalStateException("Unsupported " + LoadingOrder.class.getSimpleName() + ": " + loadingOrder);
                }
            }

            if (loadedClass == null) throw new ClassNotFoundException(className);

            if (resolve) resolveClass(loadedClass);

            return loadedClass;
        }
    }

    @Nullable
    protected final Class findBootstrapClassOrNull(@NotNull String className) {
        try {
            return BOOTSTRAP_CLASS_LOADER.loadClass(className);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    @Nullable
    protected final Class findClassOrNull(@NotNull String className) {
        try {
            return findClass(className);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }

    @Nullable
    protected final Class findParentClassOrNull(@NotNull String className) {
        ClassLoader parent = getParent();
        if (parent == null) return null;
        try {
            return parent.loadClass(className);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }


    @Nullable
    @Override
    public URL getResource(@NotNull String resourceName) {
        URL result;
        LoadingOrder loadingOrder = loadingOrderFactory.getLoadingOrder(resourceName);
        if (PARENT_FIRST == loadingOrder) {
            result = getResourceFromParent(resourceName);
            if (result == null) result = findResource(resourceName);
        } else if (THIS_FIRST == loadingOrder) {
            result = findResource(resourceName);
            if (result == null) result = getResourceFromParent(resourceName);
        } else if (PARENT_ONLY == loadingOrder) {
            result = getResourceFromParent(resourceName);
        } else if (THIS_ONLY == loadingOrder) {
            result = findResource(resourceName);
        } else {
            throw new IllegalStateException("Unsupported " + LoadingOrder.class.getSimpleName() + ": " + loadingOrder);
        }
        return result;
    }

    @Nullable
    private URL getResourceFromParent(@NotNull String resourceName) {
        ClassLoader parentClassLoader = getParent();
        if (parentClassLoader != null) {
            return parentClassLoader.getResource(resourceName);
        } else {
            return getSystemResource(resourceName);
        }
    }


    @NotNull
    @Override
    @SuppressWarnings("unchecked")
    public Enumeration getResources(@NotNull String resourceName) throws IOException {
        LoadingOrder loadingOrder = loadingOrderFactory.getLoadingOrder(resourceName);
        if (PARENT_FIRST == loadingOrder) {
            return new CompoundEnumeration<>(
                getResourcesFromParent(resourceName),
                findResources(resourceName)
            );
        } else if (THIS_FIRST == loadingOrder) {
            return new CompoundEnumeration<>(
                findResources(resourceName),
                getResourcesFromParent(resourceName)
            );
        } else if (PARENT_ONLY == loadingOrder) {
            return getResourcesFromParent(resourceName);
        } else if (THIS_ONLY == loadingOrder) {
            return findResources(resourceName);
        } else {
            throw new IllegalStateException("Unsupported " + LoadingOrder.class.getSimpleName() + ": " + loadingOrder);
        }
    }

    @NotNull
    private Enumeration getResourcesFromParent(@NotNull String resourceName) throws IOException {
        ClassLoader parentClassLoader = getParent();
        if (parentClassLoader != null) {
            return parentClassLoader.getResources(resourceName);
        } else {
            return getSystemResources(resourceName);
        }
    }


    @Override
    public synchronized void addURL(@NotNull URL url) {
        if (!contains(getURLs(), url)) {
            super.addURL(url);
        }
    }

    @NotNull
    private static URL[] uniqueURLs(@NotNull URL[] urls) {
        return Stream.of(urls).distinct().toArray(URL[]::new);
    }

    @NotNull
    private static URL[] iterableUrlsToArray(@NotNull Iterable urls) {
        if (urls instanceof List) return ((List) urls).toArray(new URL[0]);
        List list = new ArrayList<>();
        for (URL url : urls) list.add(url);
        return list.toArray(new URL[0]);
    }


    private static final ClassLoader BOOTSTRAP_CLASS_LOADER = new BootstrapClassLoader();

    private static final class BootstrapClassLoader extends ClassLoader {
        static {
            ClassLoader.registerAsParallelCapable();
        }

        private BootstrapClassLoader() {
            super(null);
        }
    }


    private static final class CompoundEnumeration implements Enumeration {

        private int index = 0;

        @NotNull
        private final Enumeration[] enums;

        @SafeVarargs
        private CompoundEnumeration(@NotNull Enumeration... enums) {
            this.enums = enums;
        }

        private boolean next() {
            while (index < enums.length) {
                if (enums[index] != null && enums[index].hasMoreElements()) {
                    return true;
                }

                ++index;
            }

            return false;
        }

        @Override
        public boolean hasMoreElements() {
            return next();
        }

        @Override
        public E nextElement() {
            if (!next()) {
                throw new NoSuchElementException();
            } else {
                return enums[index].nextElement();
            }
        }

    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy