Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
02ef108
Support scanning for class path resources
mpkorstanje Jan 9, 2024
2f76493
Formatting
mpkorstanje Feb 25, 2024
277272a
Doc
mpkorstanje Feb 25, 2024
f683deb
Fix
mpkorstanje Feb 25, 2024
c457b58
Clean up todos
mpkorstanje Feb 29, 2024
c9b3b0e
Clean up todos
mpkorstanje Feb 29, 2024
e5a1751
Clean up
mpkorstanje Mar 7, 2024
2bab069
Extract common code in findClassesForPath and findResourcesForPath
mpkorstanje Mar 7, 2024
f34c4c9
Extract common file visitor code
mpkorstanje Mar 7, 2024
c84ad5e
Docs
mpkorstanje Mar 7, 2024
e43258e
Tests
mpkorstanje Mar 7, 2024
c374b93
Spotless
mpkorstanje Mar 7, 2024
440e0e7
Merge remote-tracking branch 'origin/main' into support-classpath-res…
mpkorstanje Mar 7, 2024
6cca62a
Update CHANGELOG
mpkorstanje Mar 7, 2024
704e584
Update CHANGELOG
mpkorstanje Mar 7, 2024
9f99477
Remove resource name filter predicate
mpkorstanje Jun 13, 2024
2df5201
Clean up to do.
mpkorstanje Jun 13, 2024
862cb3b
Merge remote-tracking branch 'origin/main' into support-classpath-res…
mpkorstanje Jun 13, 2024
f5d892b
Replace resource filter with predicate
mpkorstanje Jun 14, 2024
6e95eeb
Revert "Replace resource filter with predicate"
mpkorstanje Jun 14, 2024
e59cc59
Revert "Remove resource name filter predicate"
mpkorstanje Jun 14, 2024
767dcf4
Load canonical resources through classloader
mpkorstanje Jun 14, 2024
94215bc
Touch up
mpkorstanje Jun 14, 2024
794793a
Correctly resolve shadowed resources
mpkorstanje Jun 14, 2024
ac77e69
Fixup changelog
mpkorstanje Jun 14, 2024
79f13bf
Fix up java doc
mpkorstanje Jun 14, 2024
cdeb3ea
Merge remote-tracking branch 'origin/main' into support-classpath-res…
mpkorstanje Jun 14, 2024
966e21f
Fix tryToLoadResource
mpkorstanje Jun 15, 2024
358693f
Wrap class loader call in try
mpkorstanje Jun 15, 2024
917f004
Add more tryToLoadResource tests
mpkorstanje Jun 15, 2024
ca1f81b
Touchups
mpkorstanje Jun 15, 2024
59705ba
Merge remote-tracking branch 'origin/main' into support-classpath-res…
mpkorstanje Jun 15, 2024
7ffff49
Remove unnesesary try-catch
mpkorstanje Jun 15, 2024
2144abe
Add more reflection support tests
mpkorstanje Jun 15, 2024
27c0750
Merge branch 'main' into support-classpath-resources-scanning
mpkorstanje Jun 17, 2024
52e5884
Merge remote-tracking branch 'origin/main' into support-classpath-res…
mpkorstanje Jun 26, 2024
7df46a3
Find all available resources, including duplicates
mpkorstanje Jul 8, 2024
ee7b42a
Remove unused tryToLoadResource
mpkorstanje Jul 8, 2024
c0c9ee6
Suppress warnings
mpkorstanje Jul 8, 2024
8621935
Remove resourceNameFilter predicate from Javadoc
marcphilipp Jul 9, 2024
3a14696
Add test for finding duplicate resources
marcphilipp Jul 9, 2024
3f348b3
Merge branch 'main' into support-classpath-resources-scanning
marcphilipp Jul 9, 2024
b826b32
Clean up unused import
mpkorstanje Jul 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Load canonical resources through classloader
  • Loading branch information
mpkorstanje committed Jun 14, 2024
commit 767dcf467011b5426327d04b4f6ace3bef690443
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,27 @@ public static Try<Class<?>> tryToLoadClass(String name, ClassLoader classLoader)
return ReflectionUtils.tryToLoadClass(name, classLoader);
}

/**
* Tries to load the {@link Resource} for the supplied classpath resource name.
*
* <p>The name of a <em>classpath resource</em> must follow the semantics
* for resource paths as defined in {@link ClassLoader#getResource(String)}.
*
* <p>If the supplied classpath resource name is prefixed with a slash
* ({@code /}), the slash will be removed.
*
* @param classpathResourceName the name of the resource to load; never {@code null} or blank
* @param classLoader the {@code ClassLoader} to use; never {@code null}
* @return a successful {@code Try} containing the loaded class or a failed
* {@code Try} containing the exception if no such resource could be loaded;
* never {@code null}
* @since 1.11
*/
@API(status = EXPERIMENTAL, since = "1.11")
public static Try<Resource> tryToLoadResource(String classpathResourceName, ClassLoader classLoader) {
return ReflectionUtils.tryToLoadResource(classpathResourceName, classLoader);
}

/**
* Find all {@linkplain Class classes} in the supplied classpath {@code root}
* that match the specified {@code classFilter} and {@code classNameFilter}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,15 @@ class ClasspathScanner {
private final Supplier<ClassLoader> classLoaderSupplier;

private final BiFunction<String, ClassLoader, Try<Class<?>>> loadClass;
private final BiFunction<String, ClassLoader, Try<Resource>> loadResource;

ClasspathScanner(Supplier<ClassLoader> classLoaderSupplier,
BiFunction<String, ClassLoader, Try<Class<?>>> loadClass) {
BiFunction<String, ClassLoader, Try<Class<?>>> loadClass,
BiFunction<String, ClassLoader, Try<Resource>> loadResource) {

this.classLoaderSupplier = classLoaderSupplier;
this.loadClass = loadClass;
this.loadResource = loadResource;
}

List<Class<?>> scanForClassesInPackage(String basePackageName, ClassFilter classFilter) {
Expand Down Expand Up @@ -208,10 +211,17 @@ private void processResourceFileSafely(Path baseDir, String basePackageName, Res
String fullyQualifiedResourceName = determineFullyQualifiedResourceName(baseDir, basePackageName,
resourceFile);
if (resourceFilter.match(fullyQualifiedResourceName)) {
Resource resource = new ClasspathResource(fullyQualifiedResourceName, resourceFile.toUri());
// Always use "resourceFilter.test" to include future predicates.
if (resourceFilter.test(resource)) {
resourceConsumer.accept(resource);
try {
// @formatter:off
loadResource.apply(fullyQualifiedResourceName, getClassLoader())
.toOptional()
// Always use ".filter(classFilter)" to include future predicates.
.filter(resourceFilter)
.ifPresent(resourceConsumer);
// @formatter:on
}
catch (InternalError internalError) {
handleInternalError(resourceFile, fullyQualifiedResourceName, internalError);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand All @@ -56,6 +57,7 @@

import org.apiguardian.api.API;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.PreconditionViolationException;
import org.junit.platform.commons.function.Try;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
Expand Down Expand Up @@ -146,7 +148,7 @@ public enum HierarchyTraversalMode {
private static final Class<?>[] EMPTY_CLASS_ARRAY = new Class<?>[0];

private static final ClasspathScanner classpathScanner = new ClasspathScanner(
ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass);
ClassLoaderUtils::getDefaultClassLoader, ReflectionUtils::tryToLoadClass, ReflectionUtils::tryToLoadResource);

/**
* Cache for equivalent methods on an interface implemented by the declaring class.
Expand Down Expand Up @@ -890,6 +892,27 @@ public static Try<Class<?>> tryToLoadClass(String name, ClassLoader classLoader)
});
}

/**
* Tries to load the {@link Resource} for the supplied classpath resource name.
*
* <p>See {@link org.junit.platform.commons.support.ReflectionSupport#tryToLoadResource(String, ClassLoader)}
* for details.
*
* @param classpathResourceName the name of the resource to load; never {@code null} or blank
* @param classLoader the {@code ClassLoader} to use; never {@code null}
* @since 1.11
*/
@API(status = INTERNAL, since = "1.11")
public static Try<Resource> tryToLoadResource(String classpathResourceName, ClassLoader classLoader) {
Preconditions.notBlank(classpathResourceName, "Resource name must not be null or blank");
boolean startsWithSlash = classpathResourceName.startsWith("/");
URL resource = classLoader.getResource(startsWithSlash ? "/" + classpathResourceName : classpathResourceName);
if (resource == null) {
return Try.failure(new PreconditionViolationException("classLoader.getResource returned null"));
}
return Try.call(() -> new ClasspathResource(classpathResourceName, resource.toURI()));
}

private static Class<?> loadArrayType(ClassLoader classLoader, String componentTypeName, int dimensions)
throws ClassNotFoundException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,16 @@ class ClasspathScannerTests {
private static final ResourceFilter allResources = ResourceFilter.of(type -> true);

private final List<Class<?>> loadedClasses = new ArrayList<>();
private final List<Resource> loadedResources = new ArrayList<>();

private final BiFunction<String, ClassLoader, Try<Class<?>>> trackingClassLoader = (name,
classLoader) -> ReflectionUtils.tryToLoadClass(name, classLoader).ifSuccess(loadedClasses::add);

private final BiFunction<String, ClassLoader, Try<Resource>> trackingResourceLoader = (name,
classLoader) -> ReflectionUtils.tryToLoadResource(name, classLoader).ifSuccess(loadedResources::add);

private final ClasspathScanner classpathScanner = new ClasspathScanner(ClassLoaderUtils::getDefaultClassLoader,
trackingClassLoader);
trackingClassLoader, trackingResourceLoader);

@Test
void scanForClassesInClasspathRootWhenMalformedClassnameInternalErrorOccursWithNullDetailedMessage(
Expand Down Expand Up @@ -189,7 +193,8 @@ private void scanForClassesInClasspathRootWithinJarFile(String resourceName) thr
var jarfile = getClass().getResource(resourceName);

try (var classLoader = new URLClassLoader(new URL[] { jarfile })) {
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass);
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);

var classes = classpathScanner.scanForClassesInClasspathRoot(jarfile.toURI(), allClasses);
assertThat(classes).extracting(Class::getName) //
Expand All @@ -213,7 +218,8 @@ private void scanForResourcesInClasspathRootWithinJarFile(String resourceName) t
var jarfile = getClass().getResource(resourceName);

try (var classLoader = new URLClassLoader(new URL[] { jarfile })) {
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass);
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);

var resources = classpathScanner.scanForResourcesInClasspathRoot(jarfile.toURI(), allResources);
assertThat(resources).extracting(Resource::getName) //
Expand Down Expand Up @@ -270,7 +276,8 @@ private void checkModules2500(ModuleFinder finder) {
var parent = ClassLoader.getPlatformClassLoader();
var layer = ModuleLayer.defineModulesWithOneLoader(configuration, List.of(boot), parent).layer();

var classpathScanner = new ClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass);
var classpathScanner = new ClasspathScanner(() -> layer.findLoader(root), ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);
{
var classes = classpathScanner.scanForClassesInPackage("foo", allClasses);
var classNames = classes.stream().map(Class::getName).collect(Collectors.toList());
Expand All @@ -289,7 +296,8 @@ void findAllClassesInPackageWithinJarFileConcurrently() throws Exception {
var jarUri = URI.create("jar:" + jarFile);

try (var classLoader = new URLClassLoader(new URL[] { jarFile })) {
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass);
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);

var results = executeConcurrently(10,
() -> classpathScanner.scanForClassesInPackage("org.junit.platform.jartest.included", allClasses));
Expand All @@ -314,7 +322,8 @@ void findAllResourcesInPackageWithinJarFileConcurrently() throws Exception {
var jarUri = URI.create("jar:" + jarFile);

try (var classLoader = new URLClassLoader(new URL[] { jarFile })) {
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass);
var classpathScanner = new ClasspathScanner(() -> classLoader, ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);

var results = executeConcurrently(10,
() -> classpathScanner.scanForResourcesInPackage("org.junit.platform.jartest.included", allResources));
Expand Down Expand Up @@ -416,14 +425,16 @@ void scanForClassesInPackageForNullClassFilter() {

@Test
void scanForClassesInPackageWhenIOExceptionOccurs() {
var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass);
var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);
var classes = scanner.scanForClassesInPackage("org.junit.platform.commons", allClasses);
assertThat(classes).isEmpty();
}

@Test
void scanForResourcesInPackageWhenIOExceptionOccurs() {
var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass);
var scanner = new ClasspathScanner(ThrowingClassLoader::new, ReflectionUtils::tryToLoadClass,
ReflectionUtils::tryToLoadResource);
var classes = scanner.scanForResourcesInPackage("org.junit.platform.commons", allResources);
assertThat(classes).isEmpty();
}
Expand All @@ -438,6 +449,17 @@ void scanForClassesInPackageOnlyLoadsClassesThatAreIncludedByTheClassNameFilter(
assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class);
}

@Test
void scanForResourcesInPackageOnlyLoadsResourcesThatAreIncludedByTheResourceNameFilter() {
Predicate<String> resourceNameFilter = name -> name.endsWith("/example.resource");
var resourceFilter = ResourceFilter.of(resourceNameFilter, type -> true);

classpathScanner.scanForResourcesInPackage("org.junit.platform.commons", resourceFilter);

assertThat(loadedResources).extracting(Resource::getName).containsExactly(
"org/junit/platform/commons/example.resource");
}

@Test
void findAllClassesInClasspathRoot() throws Exception {
var thisClassOnly = ClassFilter.of(clazz -> clazz == ClasspathScannerTests.class);
Expand Down Expand Up @@ -519,6 +541,18 @@ void onlyLoadsClassesInClasspathRootThatAreIncludedByTheClassNameFilter() throws
assertThat(loadedClasses).containsExactly(ClasspathScannerTests.class);
}

@Test
void onlyLoadsResourcesInClasspathRootThatAreIncludedByTheResourceNameFilter() {
Predicate<String> resourceNameFilter = name -> name.endsWith("/example.resource");
var resourceFilter = ResourceFilter.of(resourceNameFilter, type -> true);
var root = getTestClasspathResourceRoot();

classpathScanner.scanForResourcesInClasspathRoot(root, resourceFilter);

assertThat(loadedResources).extracting(Resource::getName).containsExactly(
"org/junit/platform/commons/example.resource");
}

private static URI uriOf(String name) {
var resource = ClasspathScannerTests.class.getResource(name);
try {
Expand Down