Bug 522732: [JUnit 5] JUnit Jupiter @Nested test classes are executed in
separate launcher

part 1

Change-Id: I42b294a61364f4e3e0de31ab601f76df4bd802b0
diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitCorePlugin.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitCorePlugin.java
index 372b279..679cd7e 100644
--- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitCorePlugin.java
+++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/JUnitCorePlugin.java
@@ -63,6 +63,7 @@
 
 	public final static String JUNIT5_TESTABLE_ANNOTATION_NAME= "org.junit.platform.commons.annotation.Testable"; //$NON-NLS-1$
 	public final static String JUNIT5_JUPITER_TEST_ANNOTATION_NAME= "org.junit.jupiter.api.Test"; //$NON-NLS-1$
+	public final static String JUNIT5_JUPITER_NESTED_ANNOTATION_NAME= "org.junit.jupiter.api.Nested"; //$NON-NLS-1$
 
 	public final static String JUNIT4_ANNOTATION_NAME= "org.junit.Test"; //$NON-NLS-1$
 	public static final String SIMPLE_TEST_INTERFACE_NAME= "Test"; //$NON-NLS-1$
diff --git a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/launcher/JUnit5TestFinder.java b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/launcher/JUnit5TestFinder.java
index 4aef2fb..5b9822e 100644
--- a/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/launcher/JUnit5TestFinder.java
+++ b/org.eclipse.jdt.junit.core/src/org/eclipse/jdt/internal/junit/launcher/JUnit5TestFinder.java
@@ -50,6 +50,8 @@
 
 		private static final Annotation TESTABLE= new Annotation(JUnitCorePlugin.JUNIT5_TESTABLE_ANNOTATION_NAME);
 
+		private static final Annotation NESTED= new Annotation(JUnitCorePlugin.JUNIT5_JUPITER_NESTED_ANNOTATION_NAME);
+
 		private final String fName;
 
 		private Annotation(String name) {
@@ -60,6 +62,47 @@
 			return fName;
 		}
 
+		public boolean annotatesAtLeastOneInnerClass(ITypeBinding type) {
+			if (type == null) {
+				return false;
+			}
+			if (annotatesDeclaredTypes(type)) {
+				return true;
+			}
+			ITypeBinding superClass= type.getSuperclass();
+			if (annotatesAtLeastOneInnerClass(superClass)) {
+				return true;
+			}
+			ITypeBinding[] interfaces= type.getInterfaces();
+			for (int i= 0; i < interfaces.length; i++) {
+				if (annotatesAtLeastOneInnerClass(interfaces[i])) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		private boolean annotatesDeclaredTypes(ITypeBinding type) {
+			ITypeBinding[] declaredTypes= type.getDeclaredTypes();
+			for (int i= 0; i < declaredTypes.length; i++) {
+				if (isNestedClass(declaredTypes[i])) {
+					return true;
+				}
+			}
+			return false;
+		}
+
+		private boolean isNestedClass(ITypeBinding type) {
+			int modifiers= type.getModifiers();
+			if (type.isInterface() || Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers)) {
+				return false;
+			}
+			if (annotates(type.getAnnotations())) {
+				return true;
+			}
+			return false;
+		}
+
 		public boolean annotatesTypeOrSuperTypes(ITypeBinding type) {
 			while (type != null) {
 				if (annotates(type.getAnnotations())) {
@@ -110,7 +153,7 @@
 				if (matchesName(annotation.getAnnotationType())) {
 					return true;
 				}
-				if (TESTABLE.getName().equals(fName)) {
+				if (TESTABLE.getName().equals(fName) || NESTED.getName().equals(fName)) {
 					Set<ITypeBinding> hierarchy= new HashSet<>();
 					if (matchesNameInAnnotationHierarchy(annotation, hierarchy)) {
 						return true;
@@ -252,7 +295,8 @@
 		if (Annotation.RUN_WITH.annotatesTypeOrSuperTypes(binding)
 				|| Annotation.TEST_4.annotatesAtLeastOneMethod(binding)
 				|| Annotation.TESTABLE.annotatesAtLeastOneMethod(binding)
-				|| Annotation.TESTABLE.annotatesTypeOrSuperTypes(binding)) {
+				|| Annotation.TESTABLE.annotatesTypeOrSuperTypes(binding)
+				|| Annotation.NESTED.annotatesAtLeastOneInnerClass(binding)) {
 			return true;
 		}
 		return CoreTestSearchEngine.isTestImplementor(binding);