/*
 * Copyright 2015-2016 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v1.0 which
 * accompanies this distribution and is available at
 *
 * http://www.eclipse.org/legal/epl-v10.html
 */

package org.junit.gen5.engine.junit4.discovery;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

import org.junit.gen5.commons.util.ReflectionUtils;
import org.junit.internal.builders.AllDefaultPossibilitiesBuilder;
import org.junit.internal.builders.AnnotatedBuilder;
import org.junit.internal.builders.JUnit4Builder;
import org.junit.runner.Runner;
import org.junit.runners.model.RunnerBuilder;

/**
 * Customization of {@link AllDefaultPossibilitiesBuilder} from JUnit 4 to
 * ignore certain classes that would otherwise be reported as errors or cause
 * infinite recursion.
 *
 * @see DefensiveAnnotatedBuilder
 * @see DefensiveJUnit4Builder
 */
class DefensiveAllDefaultPossibilitiesBuilder extends AllDefaultPossibilitiesBuilder {

	private final AnnotatedBuilder annotatedBuilder;
	private final DefensiveJUnit4Builder defensiveJUnit4Builder;

	DefensiveAllDefaultPossibilitiesBuilder() {
		super(true);
		annotatedBuilder = createAnnotatedBuilder();
		defensiveJUnit4Builder = new DefensiveJUnit4Builder();
	}

	private AnnotatedBuilder createAnnotatedBuilder() {
		// Load via reflection because it might not be available at runtime.
		Optional<Class<?>> junit5RunnerClass = ReflectionUtils.loadClass("org.junit.gen5.junit4.runner.JUnit5");
		if (junit5RunnerClass.isPresent()) {
			return new DefensiveAnnotatedBuilder(this, junit5RunnerClass.get());
		}
		return new AnnotatedBuilder(this);
	}

	@Override
	protected AnnotatedBuilder annotatedBuilder() {
		return annotatedBuilder;
	}

	@Override
	protected JUnit4Builder junit4Builder() {
		return defensiveJUnit4Builder;
	}

	/**
	 * Customization of {@link AnnotatedBuilder} that ignores classes annotated
	 * with {@code @RunWith(JUnit5.class)} to avoid infinite recursion.
	 */
	private static class DefensiveAnnotatedBuilder extends AnnotatedBuilder {

		private final Class<?> junit5RunnerClass;

		public DefensiveAnnotatedBuilder(RunnerBuilder suiteBuilder, Class<?> junit5RunnerClass) {
			super(suiteBuilder);
			this.junit5RunnerClass = junit5RunnerClass;
		}

		@Override
		public Runner buildRunner(Class<? extends Runner> runnerClass, Class<?> testClass) throws Exception {
			if (junit5RunnerClass.isAssignableFrom(runnerClass)) {
				return null;
			}
			return super.buildRunner(runnerClass, testClass);
		}
	}

	/**
	 * Customization of {@link JUnit4Builder} that ignores classes that do not
	 * contain any test methods in order not to report errors for them.
	 */
	private static class DefensiveJUnit4Builder extends JUnit4Builder {

		private final Predicate<Method> hasTestAnnotation = new IsPotentialJUnit4TestMethod();

		@Override
		public Runner runnerForClass(Class<?> testClass) throws Throwable {
			if (containsTestMethods(testClass)) {
				return super.runnerForClass(testClass);
			}
			return null;
		}

		private boolean containsTestMethods(Class<?> testClass) {
			List<Method> testMethods = ReflectionUtils.findMethods(testClass, hasTestAnnotation);
			return !testMethods.isEmpty();
		}
	}

}

@@ -416,35 +416,8 @@
 st;%0A
-import java.util.Optional;%0A
 impo
@@ -1285,21 +1285,28 @@
 ilder = 
-creat
+new Defensiv
 eAnnotat
@@ -1311,24 +1311,28 @@
 atedBuilder(
+this
 );%0A%09%09defensi
@@ -1387,395 +1387,8 @@
 %09%7D%0A%0A
-%09private AnnotatedBuilder createAnnotatedBuilder() %7B%0A%09%09// Load via reflection because it might not be available at runtime.%0A%09%09Optional%3CClass%3C?%3E%3E junit5RunnerClass = ReflectionUtils.loadClass(%22org.junit.gen5.junit4.runner.JUnit5%22);%0A%09%09if (junit5RunnerClass.isPresent()) %7B%0A%09%09%09return new DefensiveAnnotatedBuilder(this, junit5RunnerClass.get());%0A%09%09%7D%0A%09%09return new AnnotatedBuilder(this);%0A%09%7D%0A%0A
 %09@Ov
@@ -1801,53 +1801,8 @@
  %7B%0A%0A
-%09%09private final Class%3C?%3E junit5RunnerClass;%0A%0A
 %09%09pu
@@ -1862,109 +1862,34 @@
 lder
-, Class%3C?%3E junit5RunnerClass) %7B%0A%09%09%09super(suiteBuilder);%0A%09%09%09this.junit5RunnerClass = junit5RunnerClass
+) %7B%0A%09%09%09super(suiteBuilder)
 ;%0A%09%09
@@ -2014,46 +2014,127 @@
 %0A%09%09%09
-if (junit5RunnerClass.isAssignableFrom
+// Referenced by name because it might not be available at runtime.%0A%09%09%09if (%22org.junit.gen5.junit4.runner.JUnit5%22.equals
 (run
@@ -2137,24 +2137,34 @@
 (runnerClass
+.getName()
 )) %7B%0A%09%09%09%09ret

Simplify DefensiveAllDefaultPossibilitiesBuilder