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