
name.remal.reflection.ExtendedURLClassLoader Maven / Gradle / Ivy
package name.remal.reflection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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 static name.remal.reflection.ExtendedURLClassLoader.LoadingOrder.*;
import static org.apache.commons.lang3.ArrayUtils.contains;
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