001package org.junit.runners.model;
002
003import java.util.ArrayList;
004import java.util.HashSet;
005import java.util.List;
006import java.util.Set;
007
008import org.junit.internal.runners.ErrorReportingRunner;
009import org.junit.runner.Runner;
010
011/**
012 * A RunnerBuilder is a strategy for constructing runners for classes.
013 *
014 * Only writers of custom runners should use <code>RunnerBuilder</code>s.  A custom runner class with a constructor taking
015 * a <code>RunnerBuilder</code> parameter will be passed the instance of <code>RunnerBuilder</code> used to build that runner itself.
016 * For example,
017 * imagine a custom runner that builds suites based on a list of classes in a text file:
018 *
019 * <pre>
020 * \@RunWith(TextFileSuite.class)
021 * \@SuiteSpecFile("mysuite.txt")
022 * class MySuite {}
023 * </pre>
024 *
025 * The implementation of TextFileSuite might include:
026 *
027 * <pre>
028 * public TextFileSuite(Class testClass, RunnerBuilder builder) {
029 *   // ...
030 *   for (String className : readClassNames())
031 *     addRunner(builder.runnerForClass(Class.forName(className)));
032 *   // ...
033 * }
034 * </pre>
035 *
036 * @see org.junit.runners.Suite
037 * @since 4.5
038 */
039public abstract class RunnerBuilder {
040    private final Set<Class<?>> parents = new HashSet<Class<?>>();
041
042    /**
043     * Override to calculate the correct runner for a test class at runtime.
044     *
045     * @param testClass class to be run
046     * @return a Runner
047     * @throws Throwable if a runner cannot be constructed
048     */
049    public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
050
051    /**
052     * Always returns a runner, even if it is just one that prints an error instead of running tests.
053     *
054     * @param testClass class to be run
055     * @return a Runner
056     */
057    public Runner safeRunnerForClass(Class<?> testClass) {
058        try {
059            return runnerForClass(testClass);
060        } catch (Throwable e) {
061            return new ErrorReportingRunner(testClass, e);
062        }
063    }
064
065    Class<?> addParent(Class<?> parent) throws InitializationError {
066        if (!parents.add(parent)) {
067            throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
068        }
069        return parent;
070    }
071
072    void removeParent(Class<?> klass) {
073        parents.remove(klass);
074    }
075
076    /**
077     * Constructs and returns a list of Runners, one for each child class in
078     * {@code children}.  Care is taken to avoid infinite recursion:
079     * this builder will throw an exception if it is requested for another
080     * runner for {@code parent} before this call completes.
081     */
082    public List<Runner> runners(Class<?> parent, Class<?>[] children)
083            throws InitializationError {
084        addParent(parent);
085
086        try {
087            return runners(children);
088        } finally {
089            removeParent(parent);
090        }
091    }
092
093    public List<Runner> runners(Class<?> parent, List<Class<?>> children)
094            throws InitializationError {
095        return runners(parent, children.toArray(new Class<?>[0]));
096    }
097
098    private List<Runner> runners(Class<?>[] children) {
099        ArrayList<Runner> runners = new ArrayList<Runner>();
100        for (Class<?> each : children) {
101            Runner childRunner = safeRunnerForClass(each);
102            if (childRunner != null) {
103                runners.add(childRunner);
104            }
105        }
106        return runners;
107    }
108}