@@ -153,7 +153,7 @@ public enum HierarchyTraversalMode {
153153 * <p>This serves as a cache to avoid repeated cycle detection for classes
154154 * that have already been checked.
155155 * @since 1.6
156- * @see #detectInnerClassCycle(Class)
156+ * @see #detectInnerClassCycle(Class, CycleErrorHandling )
157157 */
158158 private static final Set <String > noCyclesDetectedCache = ConcurrentHashMap .newKeySet ();
159159
@@ -1234,11 +1234,17 @@ public static Stream<Resource> streamAllResourcesInModule(String moduleName, Pre
12341234 * @see org.junit.platform.commons.support.ReflectionSupport#findNestedClasses(Class, Predicate)
12351235 */
12361236 public static List <Class <?>> findNestedClasses (Class <?> clazz , Predicate <Class <?>> predicate ) {
1237+ return findNestedClasses (clazz , predicate , CycleErrorHandling .THROW_EXCEPTION );
1238+ }
1239+
1240+ @ API (status = INTERNAL , since = "5.13.2" )
1241+ public static List <Class <?>> findNestedClasses (Class <?> clazz , Predicate <Class <?>> predicate ,
1242+ CycleErrorHandling errorHandling ) {
12371243 Preconditions .notNull (clazz , "Class must not be null" );
12381244 Preconditions .notNull (predicate , "Predicate must not be null" );
12391245
12401246 Set <Class <?>> candidates = new LinkedHashSet <>();
1241- visitAllNestedClasses (clazz , predicate , candidates ::add );
1247+ visitAllNestedClasses (clazz , predicate , candidates ::add , errorHandling );
12421248 return Collections .unmodifiableList (new ArrayList <>(candidates ));
12431249 }
12441250
@@ -1255,13 +1261,15 @@ public static List<Class<?>> findNestedClasses(Class<?> clazz, Predicate<Class<?
12551261 * @return {@code true} if such a nested class is present
12561262 * @throws JUnitException if a cycle is detected within an inner class hierarchy
12571263 */
1258- @ API (status = INTERNAL , since = "1.13" )
1259- public static boolean isNestedClassPresent (Class <?> clazz , Predicate <Class <?>> predicate ) {
1264+ @ API (status = INTERNAL , since = "1.13.2" )
1265+ public static boolean isNestedClassPresent (Class <?> clazz , Predicate <Class <?>> predicate ,
1266+ CycleErrorHandling errorHandling ) {
12601267 Preconditions .notNull (clazz , "Class must not be null" );
12611268 Preconditions .notNull (predicate , "Predicate must not be null" );
1269+ Preconditions .notNull (errorHandling , "CycleErrorHandling must not be null" );
12621270
12631271 AtomicBoolean foundNestedClass = new AtomicBoolean (false );
1264- visitAllNestedClasses (clazz , predicate , __ -> foundNestedClass .set (true ));
1272+ visitAllNestedClasses (clazz , predicate , __ -> foundNestedClass .set (true ), errorHandling );
12651273 return foundNestedClass .get ();
12661274 }
12671275
@@ -1273,27 +1281,37 @@ public static Stream<Class<?>> streamNestedClasses(Class<?> clazz, Predicate<Cla
12731281 return findNestedClasses (clazz , predicate ).stream ();
12741282 }
12751283
1284+ @ API (status = INTERNAL , since = "5.13.2" )
1285+ public static Stream <Class <?>> streamNestedClasses (Class <?> clazz , Predicate <Class <?>> predicate ,
1286+ CycleErrorHandling errorHandling ) {
1287+ return findNestedClasses (clazz , predicate , errorHandling ).stream ();
1288+ }
1289+
12761290 /**
12771291 * Visit <em>all</em> nested classes without support for short-circuiting
12781292 * in order to ensure all of them are checked for class cycles.
12791293 */
12801294 private static void visitAllNestedClasses (Class <?> clazz , Predicate <Class <?>> predicate ,
1281- Consumer <Class <?>> consumer ) {
1295+ Consumer <Class <?>> consumer , CycleErrorHandling errorHandling ) {
12821296
12831297 if (!isSearchable (clazz )) {
12841298 return ;
12851299 }
12861300
12871301 if (isInnerClass (clazz ) && predicate .test (clazz )) {
1288- detectInnerClassCycle (clazz );
1302+ if (detectInnerClassCycle (clazz , errorHandling )) {
1303+ return ;
1304+ }
12891305 }
12901306
12911307 try {
12921308 // Candidates in current class
12931309 for (Class <?> nestedClass : clazz .getDeclaredClasses ()) {
12941310 if (predicate .test (nestedClass )) {
1295- detectInnerClassCycle (nestedClass );
12961311 consumer .accept (nestedClass );
1312+ if (detectInnerClassCycle (nestedClass , errorHandling )) {
1313+ return ;
1314+ }
12971315 }
12981316 }
12991317 }
@@ -1302,48 +1320,47 @@ private static void visitAllNestedClasses(Class<?> clazz, Predicate<Class<?>> pr
13021320 }
13031321
13041322 // Search class hierarchy
1305- visitAllNestedClasses (clazz .getSuperclass (), predicate , consumer );
1323+ visitAllNestedClasses (clazz .getSuperclass (), predicate , consumer , errorHandling );
13061324
13071325 // Search interface hierarchy
13081326 for (Class <?> ifc : clazz .getInterfaces ()) {
1309- visitAllNestedClasses (ifc , predicate , consumer );
1327+ visitAllNestedClasses (ifc , predicate , consumer , errorHandling );
13101328 }
13111329 }
13121330
13131331 /**
13141332 * Detect a cycle in the inner class hierarchy in which the supplied class
13151333 * resides — from the supplied class up to the outermost enclosing class
13161334 * — and throw a {@link JUnitException} if a cycle is detected.
1317- *
13181335 * <p>This method does <strong>not</strong> detect cycles within inner class
13191336 * hierarchies <em>below</em> the supplied class.
1320- *
13211337 * <p>If the supplied class is not an inner class and does not have a
13221338 * searchable superclass, this method is effectively a no-op.
13231339 *
13241340 * @since 1.6
13251341 * @see #isInnerClass(Class)
13261342 * @see #isSearchable(Class)
13271343 */
1328- private static void detectInnerClassCycle (Class <?> clazz ) {
1344+ private static boolean detectInnerClassCycle (Class <?> clazz , CycleErrorHandling errorHandling ) {
13291345 Preconditions .notNull (clazz , "Class must not be null" );
13301346 String className = clazz .getName ();
13311347
13321348 if (noCyclesDetectedCache .contains (className )) {
1333- return ;
1349+ return false ;
13341350 }
13351351
13361352 Class <?> superclass = clazz .getSuperclass ();
13371353 if (isInnerClass (clazz ) && isSearchable (superclass )) {
13381354 for (Class <?> enclosing = clazz .getEnclosingClass (); enclosing != null ; enclosing = enclosing .getEnclosingClass ()) {
13391355 if (superclass .equals (enclosing )) {
1340- throw new JUnitException ( String . format ( "Detected cycle in inner class hierarchy between %s and %s" ,
1341- className , enclosing . getName ())) ;
1356+ errorHandling . handle ( clazz , enclosing );
1357+ return true ;
13421358 }
13431359 }
13441360 }
13451361
13461362 noCyclesDetectedCache .add (className );
1363+ return false ;
13471364 }
13481365
13491366 /**
@@ -2113,4 +2130,25 @@ private static boolean getLegacySearchSemanticsFlag() {
21132130 return isTrue ;
21142131 }
21152132
2133+ @ API (status = INTERNAL , since = "5.13.2" )
2134+ public enum CycleErrorHandling {
2135+
2136+ THROW_EXCEPTION {
2137+ @ Override
2138+ void handle (Class <?> clazz , Class <?> enclosing ) {
2139+ throw new JUnitException (String .format ("Detected cycle in inner class hierarchy between %s and %s" ,
2140+ clazz .getName (), enclosing .getName ()));
2141+ }
2142+ },
2143+
2144+ ABORT_VISIT {
2145+ @ Override
2146+ void handle (Class <?> clazz , Class <?> enclosing ) {
2147+ // ignore
2148+ }
2149+ };
2150+
2151+ abstract void handle (Class <?> clazz , Class <?> enclosing );
2152+ }
2153+
21162154}
0 commit comments