001package org.junit.runner.manipulation;
002
003import java.lang.reflect.Constructor;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Collections;
007import java.util.List;
008import java.util.Random;
009
010import org.junit.runner.Description;
011import org.junit.runner.OrderWith;
012
013/**
014 * Reorders tests. An {@code Ordering} can reverse the order of tests, sort the
015 * order or even shuffle the order.
016 *
017 * <p>In general you will not need to use a <code>Ordering</code> directly.
018 * Instead, use {@link org.junit.runner.Request#orderWith(Ordering)}.
019 *
020 * @since 4.13
021 */
022public abstract class Ordering {
023    private static final String CONSTRUCTOR_ERROR_FORMAT
024            = "Ordering class %s should have a public constructor with signature "
025                    + "%s(Ordering.Context context)";
026
027    /**
028     * Creates an {@link Ordering} that shuffles the items using the given
029     * {@link Random} instance.
030     */
031    public static Ordering shuffledBy(final Random random) {
032        return new Ordering() {
033            @Override
034            boolean validateOrderingIsCorrect() {
035                return false;
036            }
037
038            @Override
039            protected List<Description> orderItems(Collection<Description> descriptions) {
040                List<Description> shuffled = new ArrayList<Description>(descriptions);
041                Collections.shuffle(shuffled, random);
042                return shuffled;
043            }
044        };
045    }
046
047    /**
048     * Creates an {@link Ordering} from the given factory class. The class must have a public no-arg
049     * constructor.
050     *
051     * @param factoryClass class to use to create the ordering
052     * @param annotatedTestClass test class that is annotated with {@link OrderWith}.
053     * @throws InvalidOrderingException if the instance could not be created
054     */
055    public static Ordering definedBy(
056            Class<? extends Ordering.Factory> factoryClass, Description annotatedTestClass)
057            throws InvalidOrderingException {
058        if (factoryClass == null) {
059            throw new NullPointerException("factoryClass cannot be null");
060        }
061        if (annotatedTestClass == null) {
062            throw new NullPointerException("annotatedTestClass cannot be null");
063        }
064
065        Ordering.Factory factory;
066        try {
067            Constructor<? extends Ordering.Factory> constructor = factoryClass.getConstructor();
068            factory = constructor.newInstance();
069        } catch (NoSuchMethodException e) {
070            throw new InvalidOrderingException(String.format(
071                    CONSTRUCTOR_ERROR_FORMAT,
072                    getClassName(factoryClass),
073                    factoryClass.getSimpleName()));
074        } catch (Exception e) {
075            throw new InvalidOrderingException(
076                    "Could not create ordering for " + annotatedTestClass, e);
077        }
078        return definedBy(factory, annotatedTestClass);
079    }
080
081    /**
082     * Creates an {@link Ordering} from the given factory.
083     *
084     * @param factory factory to use to create the ordering
085     * @param annotatedTestClass test class that is annotated with {@link OrderWith}.
086     * @throws InvalidOrderingException if the instance could not be created
087     */
088    public static Ordering definedBy(
089            Ordering.Factory factory, Description annotatedTestClass)
090            throws InvalidOrderingException {
091        if (factory == null) {
092            throw new NullPointerException("factory cannot be null");
093        }
094        if (annotatedTestClass == null) {
095            throw new NullPointerException("annotatedTestClass cannot be null");
096        }
097
098        return factory.create(new Ordering.Context(annotatedTestClass));
099    }
100
101    private static String getClassName(Class<?> clazz) {
102        String name = clazz.getCanonicalName();
103        if (name == null) {
104            return clazz.getName();
105        }
106        return name;
107    }
108
109    /**
110     * Order the tests in <code>target</code> using this ordering.
111     *
112     * @throws InvalidOrderingException if ordering does something invalid (like remove or add
113     * children)
114     */
115    public void apply(Object target) throws InvalidOrderingException {
116        /*
117         * Note that some subclasses of Ordering override apply(). The Sorter
118         * subclass of Ordering overrides apply() to apply the sort (this is
119         * done because sorting is more efficient than ordering).
120         */
121        if (target instanceof Orderable) {
122            Orderable orderable = (Orderable) target;
123            orderable.order(new Orderer(this));
124        }
125    }
126
127    /**
128     * Returns {@code true} if this ordering could produce invalid results (i.e.
129     * if it could add or remove values).
130     */
131    boolean validateOrderingIsCorrect() {
132        return true;
133    }
134
135    /**
136     * Implemented by sub-classes to order the descriptions.
137     *
138     * @return descriptions in order
139     */
140    protected abstract List<Description> orderItems(Collection<Description> descriptions);
141
142    /** Context about the ordering being applied. */
143    public static class Context {
144        private final Description description;
145
146        /**
147         * Gets the description for the top-level target being ordered.
148         */
149        public Description getTarget() {
150            return description;
151        }
152
153        private Context(Description description) {
154            this.description = description;
155        }
156    }
157
158    /**
159     * Factory for creating {@link Ordering} instances.
160     *
161     * <p>For a factory to be used with {@code @OrderWith} it needs to have a public no-arg
162     * constructor.
163     */
164    public interface Factory {
165        /**
166         * Creates an Ordering instance using the given context. Implementations
167         * of this method that do not need to use the context can return the
168         * same instance every time.
169         */
170        Ordering create(Context context);
171    }
172}