001package org.junit.runners.model;
002
003import static java.lang.reflect.Modifier.isStatic;
004
005import java.lang.annotation.Annotation;
006import java.lang.reflect.Constructor;
007import java.lang.reflect.Field;
008import java.lang.reflect.Method;
009import java.util.ArrayList;
010import java.util.HashMap;
011import java.util.List;
012import java.util.Map;
013
014import org.junit.Assert;
015import org.junit.Before;
016import org.junit.BeforeClass;
017import org.junit.internal.MethodSorter;
018
019/**
020 * Wraps a class to be run, providing method validation and annotation searching
021 *
022 * @since 4.5
023 */
024public class TestClass {
025    private final Class<?> fClass;
026
027    private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations = new HashMap<Class<?>, List<FrameworkMethod>>();
028
029    private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations = new HashMap<Class<?>, List<FrameworkField>>();
030
031    /**
032     * Creates a {@code TestClass} wrapping {@code klass}. Each time this
033     * constructor executes, the class is scanned for annotations, which can be
034     * an expensive process (we hope in future JDK's it will not be.) Therefore,
035     * try to share instances of {@code TestClass} where possible.
036     */
037    public TestClass(Class<?> klass) {
038        fClass = klass;
039        if (klass != null && klass.getConstructors().length > 1) {
040            throw new IllegalArgumentException(
041                    "Test class can only have one constructor");
042        }
043
044        for (Class<?> eachClass : getSuperClasses(fClass)) {
045            for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {
046                addToAnnotationLists(new FrameworkMethod(eachMethod),
047                        fMethodsForAnnotations);
048            }
049            for (Field eachField : eachClass.getDeclaredFields()) {
050                addToAnnotationLists(new FrameworkField(eachField),
051                        fFieldsForAnnotations);
052            }
053        }
054    }
055
056    private <T extends FrameworkMember<T>> void addToAnnotationLists(T member,
057            Map<Class<?>, List<T>> map) {
058        for (Annotation each : member.getAnnotations()) {
059            Class<? extends Annotation> type = each.annotationType();
060            List<T> members = getAnnotatedMembers(map, type);
061            if (member.isShadowedBy(members)) {
062                return;
063            }
064            if (runsTopToBottom(type)) {
065                members.add(0, member);
066            } else {
067                members.add(member);
068            }
069        }
070    }
071
072    /**
073     * Returns, efficiently, all the non-overridden methods in this class and
074     * its superclasses that are annotated with {@code annotationClass}.
075     */
076    public List<FrameworkMethod> getAnnotatedMethods(
077            Class<? extends Annotation> annotationClass) {
078        return getAnnotatedMembers(fMethodsForAnnotations, annotationClass);
079    }
080
081    /**
082     * Returns, efficiently, all the non-overridden fields in this class and its
083     * superclasses that are annotated with {@code annotationClass}.
084     */
085    public List<FrameworkField> getAnnotatedFields(
086            Class<? extends Annotation> annotationClass) {
087        return getAnnotatedMembers(fFieldsForAnnotations, annotationClass);
088    }
089
090    private <T> List<T> getAnnotatedMembers(Map<Class<?>, List<T>> map,
091            Class<? extends Annotation> type) {
092        if (!map.containsKey(type)) {
093            map.put(type, new ArrayList<T>());
094        }
095        return map.get(type);
096    }
097
098    private boolean runsTopToBottom(Class<? extends Annotation> annotation) {
099        return annotation.equals(Before.class)
100                || annotation.equals(BeforeClass.class);
101    }
102
103    private List<Class<?>> getSuperClasses(Class<?> testClass) {
104        ArrayList<Class<?>> results = new ArrayList<Class<?>>();
105        Class<?> current = testClass;
106        while (current != null) {
107            results.add(current);
108            current = current.getSuperclass();
109        }
110        return results;
111    }
112
113    /**
114     * Returns the underlying Java class.
115     */
116    public Class<?> getJavaClass() {
117        return fClass;
118    }
119
120    /**
121     * Returns the class's name.
122     */
123    public String getName() {
124        if (fClass == null) {
125            return "null";
126        }
127        return fClass.getName();
128    }
129
130    /**
131     * Returns the only public constructor in the class, or throws an {@code
132     * AssertionError} if there are more or less than one.
133     */
134
135    public Constructor<?> getOnlyConstructor() {
136        Constructor<?>[] constructors = fClass.getConstructors();
137        Assert.assertEquals(1, constructors.length);
138        return constructors[0];
139    }
140
141    /**
142     * Returns the annotations on this class
143     */
144    public Annotation[] getAnnotations() {
145        if (fClass == null) {
146            return new Annotation[0];
147        }
148        return fClass.getAnnotations();
149    }
150
151    public <T> List<T> getAnnotatedFieldValues(Object test,
152            Class<? extends Annotation> annotationClass, Class<T> valueClass) {
153        List<T> results = new ArrayList<T>();
154        for (FrameworkField each : getAnnotatedFields(annotationClass)) {
155            try {
156                Object fieldValue = each.get(test);
157                if (valueClass.isInstance(fieldValue)) {
158                    results.add(valueClass.cast(fieldValue));
159                }
160            } catch (IllegalAccessException e) {
161                throw new RuntimeException(
162                        "How did getFields return a field we couldn't access?", e);
163            }
164        }
165        return results;
166    }
167
168    public <T> List<T> getAnnotatedMethodValues(Object test,
169            Class<? extends Annotation> annotationClass, Class<T> valueClass) {
170        List<T> results = new ArrayList<T>();
171        for (FrameworkMethod each : getAnnotatedMethods(annotationClass)) {
172            try {
173                Object fieldValue = each.invokeExplosively(test, new Object[]{});
174                if (valueClass.isInstance(fieldValue)) {
175                    results.add(valueClass.cast(fieldValue));
176                }
177            } catch (Throwable e) {
178                throw new RuntimeException(
179                        "Exception in " + each.getName(), e);
180            }
181        }
182        return results;
183    }
184
185    public boolean isANonStaticInnerClass() {
186        return fClass.isMemberClass() && !isStatic(fClass.getModifiers());
187    }
188}