FilterableClassLoader.java
package fr.sii.ogham.testing.mock.classloader;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* {@link ClassLoader} decorator that can be used to mock some missing classes.
* It can be useful to test how some code behaves if a dependency is missing.
*
* This is mostly useful for Ogham itself since it adatps to what is present in
* the classpath.
*
* NOTE: On some Java versions (especially some versions of Eclipse OpenJ9), the
* {@link ClassLoader} initializes assertions directly in the constructor (see
* https://github.com/eclipse/openj9/blob/cef51dfd748a15b6f8ceb9be065d8a9eb8389a6b/jcl/src/java.base/share/classes/java/lang/ClassLoader.java#L438).
* So we need to register method calls to apply them once delegate has been set.
*
* @author Aurélien Baudet
*
*/
public class FilterableClassLoader extends ClassLoader {
private static final Logger LOG = LoggerFactory.getLogger(FilterableClassLoader.class);
private ClassLoader delegate;
private Predicate<String> predicate;
private static final Map<ClassLoader, List<Consumer<ClassLoader>>> lateCalls = new ConcurrentHashMap<>();
public FilterableClassLoader(ClassLoader delegate, Predicate<String> predicate) {
super();
this.delegate = delegate;
this.predicate = predicate;
applyLateCalls();
}
@SuppressWarnings("squid:S2658")
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
if (predicate.test(name)) {
return delegate.loadClass(name);
} else {
LOG.info("Class {} not accepted", name);
throw new ClassNotFoundException("Class " + name + " not accepted");
}
}
@Override
public URL getResource(String name) {
if (predicate.test(name)) {
return delegate.getResource(name);
} else {
LOG.info("Resource {} not accepted", name);
return null;
}
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if (predicate.test(name)) {
return delegate.getResources(name);
} else {
LOG.info("Resources {} not accepted", name);
return Collections.emptyEnumeration();
}
}
@Override
public InputStream getResourceAsStream(String name) {
if (predicate.test(name)) {
return delegate.getResourceAsStream(name);
} else {
LOG.info("Resource {} not accepted", name);
return null;
}
}
@Override
public void setDefaultAssertionStatus(boolean enabled) {
super.setDefaultAssertionStatus(enabled);
if (delegate != null) {
delegate.setDefaultAssertionStatus(enabled);
} else {
getLateCalls(this).add(new SetDefaultAssertionStatusCall(enabled));
}
}
@Override
public void setPackageAssertionStatus(String packageName, boolean enabled) {
super.setPackageAssertionStatus(packageName, enabled);
if (delegate != null) {
delegate.setPackageAssertionStatus(packageName, enabled);
} else {
getLateCalls(this).add(new SetPackageAssertionStatusCall(packageName, enabled));
}
}
@Override
public void setClassAssertionStatus(String className, boolean enabled) {
super.setClassAssertionStatus(className, enabled);
if (delegate != null) {
delegate.setClassAssertionStatus(className, enabled);
} else {
getLateCalls(this).add(new SetClassAssertionStatusCall(className, enabled));
}
}
@Override
public void clearAssertionStatus() {
super.clearAssertionStatus();
if (delegate != null) {
delegate.clearAssertionStatus();
} else {
getLateCalls(this).add(new ClearAssertionStatusCall());
}
}
private void applyLateCalls() {
for (Consumer<ClassLoader> lateCall : getLateCalls(this)) {
lateCall.accept(delegate);
}
}
private static List<Consumer<ClassLoader>> getLateCalls(ClassLoader classLoader) {
return lateCalls.computeIfAbsent(classLoader, k -> new ArrayList<>());
}
private static class SetDefaultAssertionStatusCall implements Consumer<ClassLoader> {
private final boolean enabled;
public SetDefaultAssertionStatusCall(boolean enabled) {
super();
this.enabled = enabled;
}
@Override
public void accept(ClassLoader delegate) {
delegate.setDefaultAssertionStatus(enabled);
}
}
private static class SetPackageAssertionStatusCall implements Consumer<ClassLoader> {
private final String packageName;
private final boolean enabled;
public SetPackageAssertionStatusCall(String packageName, boolean enabled) {
super();
this.packageName = packageName;
this.enabled = enabled;
}
@Override
public void accept(ClassLoader delegate) {
delegate.setPackageAssertionStatus(packageName, enabled);
}
}
private static class SetClassAssertionStatusCall implements Consumer<ClassLoader> {
private final String className;
private final boolean enabled;
public SetClassAssertionStatusCall(String className, boolean enabled) {
super();
this.className = className;
this.enabled = enabled;
}
@Override
public void accept(ClassLoader delegate) {
delegate.setClassAssertionStatus(className, enabled);
}
}
private static class ClearAssertionStatusCall implements Consumer<ClassLoader> {
@Override
public void accept(ClassLoader delegate) {
delegate.clearAssertionStatus();
}
}
}