1package org.junit.runners.model;
2
3import java.util.ArrayList;
4import java.util.HashSet;
5import java.util.List;
6import java.util.Set;
7
8import org.junit.internal.runners.ErrorReportingRunner;
9import org.junit.runner.Runner;
10
11/**
12 * A RunnerBuilder is a strategy for constructing runners for classes.
13 *
14 * Only writers of custom runners should use <code>RunnerBuilder</code>s.  A custom runner class with a constructor taking
15 * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself.
16 * For example,
17 * imagine a custom runner that builds suites based on a list of classes in a text file:
18 *
19 * <pre>
20 * \@RunWith(TextFileSuite.class)
21 * \@SuiteSpecFile("mysuite.txt")
22 * class MySuite {}
23 * </pre>
24 *
25 * The implementation of TextFileSuite might include:
26 *
27 * <pre>
28 * public TextFileSuite(Class testClass, RunnerBuilder builder) {
29 *   // ...
30 *   for (String className : readClassNames())
31 *     addRunner(builder.runnerForClass(Class.forName(className)));
32 *   // ...
33 * }
34 * </pre>
35 *
36 * @see org.junit.runners.Suite
37 */
38public abstract class RunnerBuilder {
39	private final Set<Class<?>> parents= new HashSet<Class<?>>();
40
41	/**
42	 * Override to calculate the correct runner for a test class at runtime.
43	 *
44	 * @param testClass class to be run
45	 * @return a Runner
46	 * @throws Throwable if a runner cannot be constructed
47	 */
48	public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
49
50	/**
51	 * Always returns a runner, even if it is just one that prints an error instead of running tests.
52	 * @param testClass class to be run
53	 * @return a Runner
54	 */
55	public Runner safeRunnerForClass(Class<?> testClass) {
56		try {
57			return runnerForClass(testClass);
58		} catch (Throwable e) {
59			return new ErrorReportingRunner(testClass, e);
60		}
61	}
62
63	Class<?> addParent(Class<?> parent) throws InitializationError {
64		if (!parents.add(parent))
65			throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
66		return parent;
67	}
68
69	void removeParent(Class<?> klass) {
70		parents.remove(klass);
71	}
72
73	/**
74	 * Constructs and returns a list of Runners, one for each child class in
75	 * {@code children}.  Care is taken to avoid infinite recursion:
76	 * this builder will throw an exception if it is requested for another
77	 * runner for {@code parent} before this call completes.
78	 */
79	public List<Runner> runners(Class<?> parent, Class<?>[] children)
80			throws InitializationError {
81		addParent(parent);
82
83		try {
84			return runners(children);
85		} finally {
86			removeParent(parent);
87		}
88	}
89
90	public List<Runner> runners(Class<?> parent, List<Class<?>> children)
91			throws InitializationError {
92		return runners(parent, children.toArray(new Class<?>[0]));
93	}
94
95	private List<Runner> runners(Class<?>[] children) {
96		ArrayList<Runner> runners= new ArrayList<Runner>();
97		for (Class<?> each : children) {
98			Runner childRunner= safeRunnerForClass(each);
99			if (childRunner != null)
100				runners.add(childRunner);
101		}
102		return runners;
103	}
104}
105