diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc index 1a7edb11fc3c..5b3a371fe690 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.10.0-M1.adoc @@ -26,8 +26,7 @@ repository on GitHub. ==== New Features and Improvements -* ❓ - +* All utility methods from `ReflectionSupport` now have counterparts returning `Stream` instead of `List` [[release-notes-5.10.0-M1-junit-jupiter]] === JUnit Jupiter diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java index 810d7e5a0a18..e5da1c415bdb 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/descriptor/ExtensionUtils.java @@ -15,8 +15,8 @@ import static org.junit.platform.commons.util.AnnotationUtils.findRepeatableAnnotations; import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated; import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.TOP_DOWN; -import static org.junit.platform.commons.util.ReflectionUtils.findFields; import static org.junit.platform.commons.util.ReflectionUtils.getDeclaredConstructor; +import static org.junit.platform.commons.util.ReflectionUtils.streamFields; import static org.junit.platform.commons.util.ReflectionUtils.tryToReadFieldValue; import java.lang.reflect.AnnotatedElement; @@ -93,7 +93,7 @@ static void registerExtensionsFromFields(ExtensionRegistrar registrar, Class Predicate predicate = (instance == null ? ReflectionUtils::isStatic : ReflectionUtils::isNotStatic); - findFields(clazz, predicate, TOP_DOWN).stream()// + streamFields(clazz, predicate, TOP_DOWN)// .sorted(orderComparator)// .forEach(field -> { List> extensionTypes = streamExtensionTypes(field).collect(toList()); diff --git a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java index 18f7107b992b..9aaf7c28879b 100644 --- a/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java +++ b/junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/ClassSelectorResolver.java @@ -13,7 +13,7 @@ import static java.util.function.Predicate.isEqual; import static java.util.stream.Collectors.toCollection; import static org.junit.jupiter.engine.discovery.predicates.IsTestClassWithTests.isTestOrTestFactoryOrTestTemplateMethod; -import static org.junit.platform.commons.support.ReflectionSupport.findNestedClasses; +import static org.junit.platform.commons.support.ReflectionSupport.streamNestedClasses; import static org.junit.platform.commons.util.FunctionUtils.where; import static org.junit.platform.commons.util.ReflectionUtils.findMethods; import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; @@ -135,7 +135,7 @@ private Resolution toResolution(Optional tes return Resolution.match(Match.exact(it, () -> { Stream methods = findMethods(testClass, isTestOrTestFactoryOrTestTemplateMethod).stream() .map(method -> selectMethod(testClasses, method)); - Stream nestedClasses = findNestedClasses(testClass, isNestedTestClass).stream() + Stream nestedClasses = streamNestedClasses(testClass, isNestedTestClass) .map(nestedClass -> DiscoverySelectors.selectNestedClass(testClasses, nestedClass)); return Stream.concat(methods, nestedClasses).collect(toCollection((Supplier>) LinkedHashSet::new)); })); diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java index ae11ea789ed0..f8265680b2b0 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/support/ReflectionSupport.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; import java.util.function.Predicate; +import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; @@ -114,6 +115,31 @@ public static List> findAllClassesInClasspathRoot(URI root, PredicateThe classpath scanning algorithm searches recursively in subpackages + * beginning with the root of the classpath. + * + * @param root the URI for the classpath root in which to scan; never + * {@code null} + * @param classFilter the class type filter; never {@code null} + * @param classNameFilter the class name filter; never {@code null} + * @return a stream of all such classes found; never {@code null} + * but potentially empty + * @since 1.10 + * @see #findAllClassesInPackage(String, Predicate, Predicate) + * @see #findAllClassesInModule(String, Predicate, Predicate) + */ + @API(status = MAINTAINED, since = "1.10") + public static Stream> streamAllClassesInClasspathRoot(URI root, Predicate> classFilter, + Predicate classNameFilter) { + + return ReflectionUtils.streamAllClassesInClasspathRoot(root, classFilter, classNameFilter); + } + /** * Find all {@linkplain Class classes} in the supplied {@code basePackageName} * that match the specified {@code classFilter} and {@code classNameFilter} @@ -138,6 +164,32 @@ public static List> findAllClassesInPackage(String basePackageName, Pre return ReflectionUtils.findAllClassesInPackage(basePackageName, classFilter, classNameFilter); } + /** + * Find all {@linkplain Class classes} in the supplied {@code basePackageName} + * that match the specified {@code classFilter} and {@code classNameFilter} + * predicates. + * + *

The classpath scanning algorithm searches recursively in subpackages + * beginning within the supplied base package. + * + * @param basePackageName the name of the base package in which to start + * scanning; must not be {@code null} and must be valid in terms of Java + * syntax + * @param classFilter the class type filter; never {@code null} + * @param classNameFilter the class name filter; never {@code null} + * @return a stream of all such classes found; never {@code null} + * but potentially empty + * @since 1.10 + * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) + * @see #findAllClassesInModule(String, Predicate, Predicate) + */ + @API(status = MAINTAINED, since = "1.10") + public static Stream> streamAllClassesInPackage(String basePackageName, Predicate> classFilter, + Predicate classNameFilter) { + + return ReflectionUtils.streamAllClassesInPackage(basePackageName, classFilter, classNameFilter); + } + /** * Find all {@linkplain Class classes} in the supplied {@code moduleName} * that match the specified {@code classFilter} and {@code classNameFilter} @@ -162,6 +214,31 @@ public static List> findAllClassesInModule(String moduleName, Predicate return ReflectionUtils.findAllClassesInModule(moduleName, classFilter, classNameFilter); } + /** + * Find all {@linkplain Class classes} in the supplied {@code moduleName} + * that match the specified {@code classFilter} and {@code classNameFilter} + * predicates. + * + *

The module-path scanning algorithm searches recursively in all + * packages contained in the module. + * + * @param moduleName the name of the module to scan; never {@code null} or + * empty + * @param classFilter the class type filter; never {@code null} + * @param classNameFilter the class name filter; never {@code null} + * @return a stream of all such classes found; never {@code null} + * but potentially empty + * @since 1.10 + * @see #findAllClassesInClasspathRoot(URI, Predicate, Predicate) + * @see #findAllClassesInPackage(String, Predicate, Predicate) + */ + @API(status = MAINTAINED, since = "1.10") + public static Stream> streamAllClassesInModule(String moduleName, Predicate> classFilter, + Predicate classNameFilter) { + + return ReflectionUtils.streamAllClassesInModule(moduleName, classFilter, classNameFilter); + } + /** * Create a new instance of the specified {@link Class} by invoking * the constructor whose argument list matches the types of the supplied @@ -225,6 +302,31 @@ public static List findFields(Class clazz, Predicate predicate, ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } + /** + * Find all {@linkplain Field fields} of the supplied class or interface + * that match the specified {@code predicate}. + * + *

Fields declared in the same class or interface will be ordered using + * an algorithm that is deterministic but intentionally nonobvious. + * + *

The results will not contain fields that are hidden or + * {@linkplain Field#isSynthetic() synthetic}. + * + * @param clazz the class or interface in which to find the fields; never {@code null} + * @param predicate the field filter; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return a stream of all such fields found; never {@code null} + * but potentially empty + * @since 1.10 + */ + @API(status = MAINTAINED, since = "1.10") + public static Stream streamFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + return ReflectionUtils.streamFields(clazz, predicate, + ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); + } + /** * Try to read the value of a potentially inaccessible field. * @@ -307,6 +409,34 @@ public static List findMethods(Class clazz, Predicate predica ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); } + /** + * Find all distinct {@linkplain Method methods} of the supplied class or + * interface that match the specified {@code predicate}. + * + *

The results will not contain instance methods that are overridden + * or {@code static} methods that are hidden. + * + *

If you're are looking for methods annotated with a certain annotation + * type, consider using + * {@link AnnotationSupport#findAnnotatedMethods(Class, Class, HierarchyTraversalMode)}. + * + * @param clazz the class or interface in which to find the methods; never {@code null} + * @param predicate the method filter; never {@code null} + * @param traversalMode the hierarchy traversal mode; never {@code null} + * @return a stream of all such methods found; never {@code null} + * @since 1.10 + * but potentially empty + */ + @API(status = MAINTAINED, since = "1.10") + public static Stream streamMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + + Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); + + return ReflectionUtils.streamMethods(clazz, predicate, + ReflectionUtils.HierarchyTraversalMode.valueOf(traversalMode.name())); + } + /** * Find all nested classes within the supplied class, or inherited by the * supplied class, that conform to the supplied predicate. @@ -333,4 +463,32 @@ public static List> findNestedClasses(Class clazz, PredicateThis method does not search for nested classes + * recursively. + * + *

As of JUnit Platform 1.6, this method detects cycles in inner + * class hierarchies — from the supplied class up to the outermost + * enclosing class — and throws a {@link JUnitException} if such a cycle + * is detected. Cycles within inner class hierarchies below the + * supplied class are not detected by this method. + * + * @param clazz the class to be searched; never {@code null} + * @param predicate the predicate against which the list of nested classes is + * checked; never {@code null} + * @return a stream of all such classes found; never {@code null} + * but potentially empty + * @throws JUnitException if a cycle is detected within an inner class hierarchy + * @since 1.10 + */ + @API(status = MAINTAINED, since = "1.10") + public static Stream> streamNestedClasses(Class clazz, Predicate> predicate) + throws JUnitException { + + return ReflectionUtils.streamNestedClasses(clazz, predicate); + } + } diff --git a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java index 31dd5cdc8572..e1b54cb65286 100644 --- a/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java +++ b/junit-platform-commons/src/main/java/org/junit/platform/commons/util/ReflectionUtils.java @@ -51,6 +51,7 @@ import java.util.function.Predicate; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import org.apiguardian.api.API; import org.junit.platform.commons.JUnitException; @@ -997,6 +998,15 @@ public static List> findAllClassesInClasspathRoot(URI root, Predicate> streamAllClassesInClasspathRoot(URI root, Predicate> classFilter, + Predicate classNameFilter) { + return streamAllClassesInClasspathRoot(root, ClassFilter.of(classNameFilter, classFilter)); + } + /** * @since 1.1 */ @@ -1004,6 +1014,13 @@ public static List> findAllClassesInClasspathRoot(URI root, ClassFilter return Collections.unmodifiableList(classpathScanner.scanForClassesInClasspathRoot(root, classFilter)); } + /** + * @since 1.10 + */ + public static Stream> streamAllClassesInClasspathRoot(URI root, ClassFilter classFilter) { + return findAllClassesInClasspathRoot(root, classFilter).stream(); + } + /** * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInPackage(String, Predicate, Predicate) */ @@ -1013,6 +1030,15 @@ public static List> findAllClassesInPackage(String basePackageName, Pre return findAllClassesInPackage(basePackageName, ClassFilter.of(classNameFilter, classFilter)); } + /** + * since 1.10 + * @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInPackage(String, Predicate, Predicate) + */ + public static Stream> streamAllClassesInPackage(String basePackageName, Predicate> classFilter, + Predicate classNameFilter) { + return streamAllClassesInPackage(basePackageName, ClassFilter.of(classNameFilter, classFilter)); + } + /** * @since 1.1 */ @@ -1020,6 +1046,13 @@ public static List> findAllClassesInPackage(String basePackageName, Cla return Collections.unmodifiableList(classpathScanner.scanForClassesInPackage(basePackageName, classFilter)); } + /** + * @since 1.10 + */ + public static Stream> streamAllClassesInPackage(String basePackageName, ClassFilter classFilter) { + return findAllClassesInPackage(basePackageName, classFilter).stream(); + } + /** * @since 1.1.1 * @see org.junit.platform.commons.support.ReflectionSupport#findAllClassesInModule(String, Predicate, Predicate) @@ -1030,6 +1063,15 @@ public static List> findAllClassesInModule(String moduleName, Predicate return findAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter)); } + /** + * @since 1.10 + * @see org.junit.platform.commons.support.ReflectionSupport#streamAllClassesInModule(String, Predicate, Predicate) + */ + public static Stream> streamAllClassesInModule(String moduleName, Predicate> classFilter, + Predicate classNameFilter) { + return streamAllClassesInModule(moduleName, ClassFilter.of(classNameFilter, classFilter)); + } + /** * @since 1.1.1 */ @@ -1037,6 +1079,13 @@ public static List> findAllClassesInModule(String moduleName, ClassFilt return Collections.unmodifiableList(ModuleUtils.findAllClassesInModule(moduleName, classFilter)); } + /** + * @since 1.10 + */ + public static Stream> streamAllClassesInModule(String moduleName, ClassFilter classFilter) { + return findAllClassesInModule(moduleName, classFilter).stream(); + } + /** * @see org.junit.platform.commons.support.ReflectionSupport#findNestedClasses(Class, Predicate) */ @@ -1049,6 +1098,15 @@ public static List> findNestedClasses(Class clazz, Predicate(candidates)); } + /** + * since 1.10 + * @see org.junit.platform.commons.support.ReflectionSupport#findNestedClasses(Class, Predicate) + * @see org.junit.platform.commons.support.ReflectionSupport#streamNestedClasses(Class, Predicate) + */ + public static Stream> streamNestedClasses(Class clazz, Predicate> predicate) { + return findNestedClasses(clazz, predicate).stream(); + } + private static void findNestedClasses(Class clazz, Predicate> predicate, Set> candidates) { if (!isSearchable(clazz)) { return; @@ -1178,20 +1236,25 @@ public static List> findConstructors(Class clazz, Predicate findFields(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { + return streamFields(clazz, predicate, traversalMode).collect(toUnmodifiableList()); + } + + /** + * @since 1.10 + * @see org.junit.platform.commons.support.ReflectionSupport#streamFields(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) + */ + public static Stream streamFields(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); - // @formatter:off - return findAllFieldsInHierarchy(clazz, traversalMode).stream() - .filter(predicate) - // unmodifiable since returned by public, non-internal method(s) - .collect(toUnmodifiableList()); - // @formatter:on + return findAllFieldsInHierarchy(clazz, traversalMode).stream().filter(predicate); } private static List findAllFieldsInHierarchy(Class clazz, HierarchyTraversalMode traversalMode) { @@ -1407,10 +1470,21 @@ public static List findMethods(Class clazz, Predicate predica /** * @see org.junit.platform.commons.support.ReflectionSupport#findMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) + * @see org.junit.platform.commons.support.ReflectionSupport#streamMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) */ public static List findMethods(Class clazz, Predicate predicate, HierarchyTraversalMode traversalMode) { + return streamMethods(clazz, predicate, traversalMode).collect(toUnmodifiableList()); + } + + /** + * @since 1.10 + * @see org.junit.platform.commons.support.ReflectionSupport#streamMethods(Class, Predicate, org.junit.platform.commons.support.HierarchyTraversalMode) + */ + public static Stream streamMethods(Class clazz, Predicate predicate, + HierarchyTraversalMode traversalMode) { + Preconditions.notNull(clazz, "Class must not be null"); Preconditions.notNull(predicate, "Predicate must not be null"); Preconditions.notNull(traversalMode, "HierarchyTraversalMode must not be null"); @@ -1418,9 +1492,7 @@ public static List findMethods(Class clazz, Predicate predica // @formatter:off return findAllMethodsInHierarchy(clazz, traversalMode).stream() .filter(predicate) - .distinct() - // unmodifiable since returned by public, non-internal method(s) - .collect(toUnmodifiableList()); + .distinct(); // @formatter:on }