diff --git a/easy-random-core/src/main/java/org/jeasy/random/util/ClassGraphFacade.java b/easy-random-core/src/main/java/org/jeasy/random/util/ClassGraphFacade.java index ef940513e..41b600594 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/util/ClassGraphFacade.java +++ b/easy-random-core/src/main/java/org/jeasy/random/util/ClassGraphFacade.java @@ -25,7 +25,6 @@ import java.util.Collections; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; import io.github.classgraph.ClassGraph; import io.github.classgraph.ClassInfoList; @@ -37,22 +36,11 @@ * * @author Pascal Schumacher (https://github.com/PascalSchumacher) */ -abstract class ClassGraphFacade { +class ClassGraphFacade { - private static final ConcurrentHashMap, List>> typeToConcreteSubTypes = new ConcurrentHashMap<>(); - private static final ScanResult scanResult = new ClassGraph().enableSystemJarsAndModules().enableClassInfo().scan(); + private final static ScanResult scanResult = new ClassGraph().enableSystemJarsAndModules().enableClassInfo().scan(); - /** - * Searches the classpath for all public concrete subtypes of the given interface or abstract class. - * - * @param type to search concrete subtypes of - * @return a list of all concrete subtypes found - */ - public static List> getPublicConcreteSubTypesOf(final Class type) { - return typeToConcreteSubTypes.computeIfAbsent(type, ClassGraphFacade::searchForPublicConcreteSubTypesOf); - } - - private static List> searchForPublicConcreteSubTypesOf(final Class type) { + static List> searchForPublicConcreteSubTypesOf(final Class type) { String typeName = type.getName(); ClassInfoList subTypes = type.isInterface() ? scanResult.getClassesImplementing(typeName) : scanResult.getSubclasses(typeName); List> loadedSubTypes = subTypes.filter(subType -> subType.isPublic() && !subType.isAbstract()).loadClasses(true); diff --git a/easy-random-core/src/main/java/org/jeasy/random/util/ReflectionUtils.java b/easy-random-core/src/main/java/org/jeasy/random/util/ReflectionUtils.java index c7b078eed..7b760eb02 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/util/ReflectionUtils.java +++ b/easy-random-core/src/main/java/org/jeasy/random/util/ReflectionUtils.java @@ -32,6 +32,7 @@ import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; @@ -52,6 +53,11 @@ */ public final class ReflectionUtils { + private static final ConcurrentHashMap, List>> typeToConcreteSubTypes = new ConcurrentHashMap<>(); + + // NOTE: indirection to avoid loading ClassGraphFacade class too early + private static volatile Function, List>> typeToConcreteSubTypesProvider = ReflectionUtils::defaultTypeToConcreteSubTypesProvider; + private ReflectionUtils() { } @@ -402,7 +408,25 @@ public static boolean isTypeVariable(final Type type) { * @return a list of all concrete subtypes found */ public static List> getPublicConcreteSubTypesOf(final Class type) { - return ClassGraphFacade.getPublicConcreteSubTypesOf(type); + return typeToConcreteSubTypes.computeIfAbsent(type, typeToConcreteSubTypesProvider); + } + + /** + * Override the default implementation of ClassGraphFacade for searching the + * classpath for any concrete subtype of given interface or abstract class. + * + * @param typeToConcreteSubTypesProvider custom implementation + */ + public static void setPublicConcreteSubTypeProvider( + final Function, List>> typeToConcreteSubTypesProvider) { + if (typeToConcreteSubTypesProvider == null) { + throw new IllegalArgumentException(); + } + ReflectionUtils.typeToConcreteSubTypesProvider = typeToConcreteSubTypesProvider; + } + + private static List> defaultTypeToConcreteSubTypesProvider(final Class cls) { + return ClassGraphFacade.searchForPublicConcreteSubTypesOf(cls); } /**