Skip to content

Commit 8e8268c

Browse files
authored
Support scanning for classpath resources (#3705)
Adds several new methods to ReflectionSupport to find or stream resource objects in the classpath root, package, and modules. A resource is represented via thew new `Resource` interface. Resolves #3630.
1 parent 6d2ae1d commit 8e8268c

File tree

18 files changed

+976
-74
lines changed

18 files changed

+976
-74
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-RC1.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ repository on GitHub.
2929
* New `rootCause()` condition in `TestExecutionResultConditions` that matches if an
3030
exception's _root_ cause matches all supplied conditions, for use with the
3131
`EngineTestKit`.
32+
* `ReflectionSupport` now supports scanning for classpath resources.
3233

3334

3435
[[release-notes-5.11.0-RC1-junit-jupiter]]

junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ private ReflectionSupport() {
6767
*/
6868
@API(status = DEPRECATED, since = "1.4")
6969
@Deprecated
70-
@SuppressWarnings("deprecation")
7170
public static Optional<Class<?>> loadClass(String name) {
7271
return ReflectionUtils.loadClass(name);
7372
}
@@ -137,6 +136,28 @@ public static List<Class<?>> findAllClassesInClasspathRoot(URI root, Predicate<C
137136
return ReflectionUtils.findAllClassesInClasspathRoot(root, classFilter, classNameFilter);
138137
}
139138

139+
/**
140+
* Find all {@linkplain Resource resources} in the supplied classpath {@code root}
141+
* that match the specified {@code resourceFilter} predicate.
142+
*
143+
* <p>The classpath scanning algorithm searches recursively in subpackages
144+
* beginning with the root of the classpath.
145+
*
146+
* @param root the URI for the classpath root in which to scan; never
147+
* {@code null}
148+
* @param resourceFilter the resource type filter; never {@code null}
149+
* @return an immutable list of all such resources found; never {@code null}
150+
* but potentially empty
151+
* @since 1.11
152+
* @see #findAllResourcesInPackage(String, Predicate)
153+
* @see #findAllResourcesInModule(String, Predicate)
154+
*/
155+
@API(status = EXPERIMENTAL, since = "1.11")
156+
public static List<Resource> findAllResourcesInClasspathRoot(URI root, Predicate<Resource> resourceFilter) {
157+
158+
return ReflectionUtils.findAllResourcesInClasspathRoot(root, resourceFilter);
159+
}
160+
140161
/**
141162
* Find all {@linkplain Class classes} in the supplied classpath {@code root}
142163
* that match the specified {@code classFilter} and {@code classNameFilter}
@@ -162,6 +183,28 @@ public static Stream<Class<?>> streamAllClassesInClasspathRoot(URI root, Predica
162183
return ReflectionUtils.streamAllClassesInClasspathRoot(root, classFilter, classNameFilter);
163184
}
164185

186+
/**
187+
* Find all {@linkplain Resource resources} in the supplied classpath {@code root}
188+
* that match the specified {@code resourceFilter} predicate.
189+
*
190+
* <p>The classpath scanning algorithm searches recursively in subpackages
191+
* beginning with the root of the classpath.
192+
*
193+
* @param root the URI for the classpath root in which to scan; never
194+
* {@code null}
195+
* @param resourceFilter the resource type filter; never {@code null}
196+
* @return a stream of all such classes found; never {@code null}
197+
* but potentially empty
198+
* @since 1.11
199+
* @see #streamAllResourcesInPackage(String, Predicate)
200+
* @see #streamAllResourcesInModule(String, Predicate)
201+
*/
202+
@API(status = EXPERIMENTAL, since = "1.11")
203+
public static Stream<Resource> streamAllResourcesInClasspathRoot(URI root, Predicate<Resource> resourceFilter) {
204+
205+
return ReflectionUtils.streamAllResourcesInClasspathRoot(root, resourceFilter);
206+
}
207+
165208
/**
166209
* Find all {@linkplain Class classes} in the supplied {@code basePackageName}
167210
* that match the specified {@code classFilter} and {@code classNameFilter}
@@ -186,6 +229,29 @@ public static List<Class<?>> findAllClassesInPackage(String basePackageName, Pre
186229
return ReflectionUtils.findAllClassesInPackage(basePackageName, classFilter, classNameFilter);
187230
}
188231

232+
/**
233+
* Find all {@linkplain Resource resources} in the supplied {@code basePackageName}
234+
* that match the specified {@code resourceFilter} predicate.
235+
*
236+
* <p>The classpath scanning algorithm searches recursively in subpackages
237+
* beginning within the supplied base package.
238+
*
239+
* @param basePackageName the name of the base package in which to start
240+
* scanning; must not be {@code null} and must be valid in terms of Java
241+
* syntax
242+
* @param resourceFilter the resource type filter; never {@code null}
243+
* @return an immutable list of all such classes found; never {@code null}
244+
* but potentially empty
245+
* @since 1.11
246+
* @see #findAllResourcesInClasspathRoot(URI, Predicate)
247+
* @see #findAllResourcesInModule(String, Predicate)
248+
*/
249+
@API(status = EXPERIMENTAL, since = "1.11")
250+
public static List<Resource> findAllResourcesInPackage(String basePackageName, Predicate<Resource> resourceFilter) {
251+
252+
return ReflectionUtils.findAllResourcesInPackage(basePackageName, resourceFilter);
253+
}
254+
189255
/**
190256
* Find all {@linkplain Class classes} in the supplied {@code basePackageName}
191257
* that match the specified {@code classFilter} and {@code classNameFilter}
@@ -212,6 +278,30 @@ public static Stream<Class<?>> streamAllClassesInPackage(String basePackageName,
212278
return ReflectionUtils.streamAllClassesInPackage(basePackageName, classFilter, classNameFilter);
213279
}
214280

281+
/**
282+
* Find all {@linkplain Resource resources} in the supplied {@code basePackageName}
283+
* that match the specified {@code resourceFilter} predicate.
284+
*
285+
* <p>The classpath scanning algorithm searches recursively in subpackages
286+
* beginning within the supplied base package.
287+
*
288+
* @param basePackageName the name of the base package in which to start
289+
* scanning; must not be {@code null} and must be valid in terms of Java
290+
* syntax
291+
* @param resourceFilter the resource type filter; never {@code null}
292+
* @return a stream of all such resources found; never {@code null}
293+
* but potentially empty
294+
* @since 1.11
295+
* @see #streamAllResourcesInClasspathRoot(URI, Predicate)
296+
* @see #streamAllResourcesInModule(String, Predicate)
297+
*/
298+
@API(status = EXPERIMENTAL, since = "1.11")
299+
public static Stream<Resource> streamAllResourcesInPackage(String basePackageName,
300+
Predicate<Resource> resourceFilter) {
301+
302+
return ReflectionUtils.streamAllResourcesInPackage(basePackageName, resourceFilter);
303+
}
304+
215305
/**
216306
* Find all {@linkplain Class classes} in the supplied {@code moduleName}
217307
* that match the specified {@code classFilter} and {@code classNameFilter}
@@ -236,6 +326,28 @@ public static List<Class<?>> findAllClassesInModule(String moduleName, Predicate
236326
return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter);
237327
}
238328

329+
/**
330+
* Find all {@linkplain Resource resources} in the supplied {@code moduleName}
331+
* that match the specified {@code resourceFilter} predicate.
332+
*
333+
* <p>The module-path scanning algorithm searches recursively in all
334+
* packages contained in the module.
335+
*
336+
* @param moduleName the name of the module to scan; never {@code null} or
337+
* <em>empty</em>
338+
* @param resourceFilter the resource type filter; never {@code null}
339+
* @return an immutable list of all such resources found; never {@code null}
340+
* but potentially empty
341+
* @since 1.11
342+
* @see #findAllResourcesInClasspathRoot(URI, Predicate)
343+
* @see #findAllResourcesInPackage(String, Predicate)
344+
*/
345+
@API(status = EXPERIMENTAL, since = "1.11")
346+
public static List<Resource> findAllResourcesInModule(String moduleName, Predicate<Resource> resourceFilter) {
347+
348+
return ReflectionUtils.findAllResourcesInModule(moduleName, resourceFilter);
349+
}
350+
239351
/**
240352
* Find all {@linkplain Class classes} in the supplied {@code moduleName}
241353
* that match the specified {@code classFilter} and {@code classNameFilter}
@@ -261,6 +373,28 @@ public static Stream<Class<?>> streamAllClassesInModule(String moduleName, Predi
261373
return ReflectionUtils.streamAllClassesInModule(moduleName, classFilter, classNameFilter);
262374
}
263375

376+
/**
377+
* Find all {@linkplain Resource resources} in the supplied {@code moduleName}
378+
* that match the specified {@code resourceFilter} predicate.
379+
*
380+
* <p>The module-path scanning algorithm searches recursively in all
381+
* packages contained in the module.
382+
*
383+
* @param moduleName the name of the module to scan; never {@code null} or
384+
* <em>empty</em>
385+
* @param resourceFilter the resource type filter; never {@code null}
386+
* @return a stream of all such resources found; never {@code null}
387+
* but potentially empty
388+
* @since 1.11
389+
* @see #streamAllResourcesInClasspathRoot(URI, Predicate)
390+
* @see #streamAllResourcesInPackage(String, Predicate)
391+
*/
392+
@API(status = EXPERIMENTAL, since = "1.11")
393+
public static Stream<Resource> streamAllResourcesInModule(String moduleName, Predicate<Resource> resourceFilter) {
394+
395+
return ReflectionUtils.streamAllResourcesInModule(moduleName, resourceFilter);
396+
}
397+
264398
/**
265399
* Create a new instance of the specified {@link Class} by invoking
266400
* the constructor whose argument list matches the types of the supplied
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.commons.support;
12+
13+
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
14+
15+
import java.io.IOException;
16+
import java.io.InputStream;
17+
import java.net.URI;
18+
import java.util.function.Predicate;
19+
20+
import org.apiguardian.api.API;
21+
22+
/**
23+
* Represents a resource on the classpath.
24+
* @since 1.11
25+
* @see ReflectionSupport#findAllResourcesInClasspathRoot(URI, Predicate)
26+
* @see ReflectionSupport#findAllResourcesInPackage(String, Predicate)
27+
* @see ReflectionSupport#findAllResourcesInModule(String, Predicate)
28+
* @see ReflectionSupport#streamAllResourcesInClasspathRoot(URI, Predicate)
29+
* @see ReflectionSupport#streamAllResourcesInPackage(String, Predicate)
30+
* @see ReflectionSupport#streamAllResourcesInModule(String, Predicate)
31+
*/
32+
@API(status = EXPERIMENTAL, since = "1.11")
33+
public interface Resource {
34+
35+
/**
36+
* Get the resource name.
37+
* <p>
38+
* The resource name is a {@code /}-separated path. The path is relative to
39+
* the classpath root in which the resource is located.
40+
*
41+
* @return the resource name; never {@code null}
42+
*/
43+
String getName();
44+
45+
/**
46+
* Get URI to a resource.
47+
*
48+
* @return the uri of the resource; never {@code null}
49+
*/
50+
URI getUri();
51+
52+
/**
53+
* Returns an input stream for reading this resource.
54+
*
55+
* @return an input stream for this resource; never {@code null}
56+
* @throws IOException if an I/O exception occurs
57+
*/
58+
default InputStream getInputStream() throws IOException {
59+
return getUri().toURL().openStream();
60+
}
61+
}

junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClassFileVisitor.java renamed to junit-platform-commons/src/main/java/org/junit/platform/commons/util/ClasspathFileVisitor.java

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,33 @@
1717
import java.nio.file.Path;
1818
import java.nio.file.SimpleFileVisitor;
1919
import java.nio.file.attribute.BasicFileAttributes;
20-
import java.util.function.Consumer;
20+
import java.util.function.BiConsumer;
21+
import java.util.function.Predicate;
2122

2223
import org.junit.platform.commons.logging.Logger;
2324
import org.junit.platform.commons.logging.LoggerFactory;
2425

2526
/**
26-
* @since 1.0
27+
* @since 1.11
2728
*/
28-
class ClassFileVisitor extends SimpleFileVisitor<Path> {
29+
class ClasspathFileVisitor extends SimpleFileVisitor<Path> {
2930

30-
private static final Logger logger = LoggerFactory.getLogger(ClassFileVisitor.class);
31+
private static final Logger logger = LoggerFactory.getLogger(ClasspathFileVisitor.class);
3132

32-
static final String CLASS_FILE_SUFFIX = ".class";
33-
private static final String PACKAGE_INFO_FILE_NAME = "package-info" + CLASS_FILE_SUFFIX;
34-
private static final String MODULE_INFO_FILE_NAME = "module-info" + CLASS_FILE_SUFFIX;
33+
private final Path basePath;
34+
private final BiConsumer<Path, Path> consumer;
35+
private final Predicate<Path> filter;
3536

36-
private final Consumer<Path> classFileConsumer;
37-
38-
ClassFileVisitor(Consumer<Path> classFileConsumer) {
39-
this.classFileConsumer = classFileConsumer;
37+
ClasspathFileVisitor(Path basePath, Predicate<Path> filter, BiConsumer<Path, Path> consumer) {
38+
this.basePath = basePath;
39+
this.filter = filter;
40+
this.consumer = consumer;
4041
}
4142

4243
@Override
4344
public FileVisitResult visitFile(Path file, BasicFileAttributes attributes) {
44-
if (isNotPackageInfo(file) && isNotModuleInfo(file) && isClassFile(file)) {
45-
classFileConsumer.accept(file);
45+
if (filter.test(file)) {
46+
consumer.accept(basePath, file);
4647
}
4748
return CONTINUE;
4849
}
@@ -61,16 +62,4 @@ public FileVisitResult postVisitDirectory(Path dir, IOException ex) {
6162
return CONTINUE;
6263
}
6364

64-
private static boolean isNotPackageInfo(Path path) {
65-
return !path.endsWith(PACKAGE_INFO_FILE_NAME);
66-
}
67-
68-
private static boolean isNotModuleInfo(Path path) {
69-
return !path.endsWith(MODULE_INFO_FILE_NAME);
70-
}
71-
72-
private static boolean isClassFile(Path file) {
73-
return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX);
74-
}
75-
7665
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.platform.commons.util;
12+
13+
import java.nio.file.Path;
14+
import java.util.function.Predicate;
15+
16+
/**
17+
* @since 1.11
18+
*/
19+
class ClasspathFilters {
20+
21+
static final String CLASS_FILE_SUFFIX = ".class";
22+
private static final String PACKAGE_INFO_FILE_NAME = "package-info" + CLASS_FILE_SUFFIX;
23+
private static final String MODULE_INFO_FILE_NAME = "module-info" + CLASS_FILE_SUFFIX;
24+
25+
static Predicate<Path> classFiles() {
26+
return file -> isNotPackageInfo(file) && isNotModuleInfo(file) && isClassFile(file);
27+
}
28+
29+
static Predicate<Path> resourceFiles() {
30+
return file -> !isClassFile(file);
31+
}
32+
33+
private static boolean isNotPackageInfo(Path path) {
34+
return !path.endsWith(PACKAGE_INFO_FILE_NAME);
35+
}
36+
37+
private static boolean isNotModuleInfo(Path path) {
38+
return !path.endsWith(MODULE_INFO_FILE_NAME);
39+
}
40+
41+
private static boolean isClassFile(Path file) {
42+
return file.getFileName().toString().endsWith(CLASS_FILE_SUFFIX);
43+
}
44+
45+
}

0 commit comments

Comments
 (0)