1package org.junit.runners.model;
2
3import static java.lang.reflect.Modifier.isStatic;
4
5import java.lang.annotation.Annotation;
6import java.lang.reflect.Constructor;
7import java.lang.reflect.Field;
8import java.lang.reflect.Method;
9import java.util.ArrayList;
10import java.util.HashMap;
11import java.util.List;
12import java.util.Map;
13
14import org.junit.Assert;
15import org.junit.Before;
16import org.junit.BeforeClass;
17
18/**
19 * Wraps a class to be run, providing method validation and annotation searching
20 */
21public class TestClass {
22	private final Class<?> fClass;
23
24	private Map<Class<?>, List<FrameworkMethod>> fMethodsForAnnotations= new HashMap<Class<?>, List<FrameworkMethod>>();
25
26	private Map<Class<?>, List<FrameworkField>> fFieldsForAnnotations= new HashMap<Class<?>, List<FrameworkField>>();
27
28	/**
29	 * Creates a {@code TestClass} wrapping {@code klass}. Each time this
30	 * constructor executes, the class is scanned for annotations, which can be
31	 * an expensive process (we hope in future JDK's it will not be.) Therefore,
32	 * try to share instances of {@code TestClass} where possible.
33	 */
34	public TestClass(Class<?> klass) {
35		fClass= klass;
36		if (klass != null && klass.getConstructors().length > 1)
37			throw new IllegalArgumentException(
38					"Test class can only have one constructor");
39
40		for (Class<?> eachClass : getSuperClasses(fClass)) {
41			for (Method eachMethod : eachClass.getDeclaredMethods())
42				addToAnnotationLists(new FrameworkMethod(eachMethod),
43						fMethodsForAnnotations);
44			for (Field eachField : eachClass.getDeclaredFields())
45				addToAnnotationLists(new FrameworkField(eachField),
46						fFieldsForAnnotations);
47		}
48	}
49
50	private <T extends FrameworkMember<T>> void addToAnnotationLists(T member,
51			Map<Class<?>, List<T>> map) {
52		for (Annotation each : member.getAnnotations()) {
53			Class<? extends Annotation> type= each.annotationType();
54			List<T> members= getAnnotatedMembers(map, type);
55			if (member.isShadowedBy(members))
56				return;
57			if (runsTopToBottom(type))
58				members.add(0, member);
59			else
60				members.add(member);
61		}
62	}
63
64	/**
65	 * Returns, efficiently, all the non-overridden methods in this class and
66	 * its superclasses that are annotated with {@code annotationClass}.
67	 */
68	public List<FrameworkMethod> getAnnotatedMethods(
69			Class<? extends Annotation> annotationClass) {
70		return getAnnotatedMembers(fMethodsForAnnotations, annotationClass);
71	}
72
73	/**
74	 * Returns, efficiently, all the non-overridden fields in this class and its
75	 * superclasses that are annotated with {@code annotationClass}.
76	 */
77	public List<FrameworkField> getAnnotatedFields(
78			Class<? extends Annotation> annotationClass) {
79		return getAnnotatedMembers(fFieldsForAnnotations, annotationClass);
80	}
81
82	private <T> List<T> getAnnotatedMembers(Map<Class<?>, List<T>> map,
83			Class<? extends Annotation> type) {
84		if (!map.containsKey(type))
85			map.put(type, new ArrayList<T>());
86		return map.get(type);
87	}
88
89	private boolean runsTopToBottom(Class<? extends Annotation> annotation) {
90		return annotation.equals(Before.class)
91				|| annotation.equals(BeforeClass.class);
92	}
93
94	private List<Class<?>> getSuperClasses(Class<?> testClass) {
95		ArrayList<Class<?>> results= new ArrayList<Class<?>>();
96		Class<?> current= testClass;
97		while (current != null) {
98			results.add(current);
99			current= current.getSuperclass();
100		}
101		return results;
102	}
103
104	/**
105	 * Returns the underlying Java class.
106	 */
107	public Class<?> getJavaClass() {
108		return fClass;
109	}
110
111	/**
112	 * Returns the class's name.
113	 */
114	public String getName() {
115		if (fClass == null)
116			return "null";
117		return fClass.getName();
118	}
119
120	/**
121	 * Returns the only public constructor in the class, or throws an {@code
122	 * AssertionError} if there are more or less than one.
123	 */
124
125	public Constructor<?> getOnlyConstructor() {
126		Constructor<?>[] constructors= fClass.getConstructors();
127		Assert.assertEquals(1, constructors.length);
128		return constructors[0];
129	}
130
131	/**
132	 * Returns the annotations on this class
133	 */
134	public Annotation[] getAnnotations() {
135		if (fClass == null)
136			return new Annotation[0];
137		return fClass.getAnnotations();
138	}
139
140	public <T> List<T> getAnnotatedFieldValues(Object test,
141			Class<? extends Annotation> annotationClass, Class<T> valueClass) {
142		List<T> results= new ArrayList<T>();
143		for (FrameworkField each : getAnnotatedFields(annotationClass)) {
144			try {
145				Object fieldValue= each.get(test);
146				if (valueClass.isInstance(fieldValue))
147					results.add(valueClass.cast(fieldValue));
148			} catch (IllegalAccessException e) {
149				throw new RuntimeException(
150						"How did getFields return a field we couldn't access?", e);
151			}
152		}
153		return results;
154	}
155
156	public boolean isANonStaticInnerClass() {
157		return fClass.isMemberClass() && !isStatic(fClass.getModifiers());
158	}
159}
160