001package org.junit.runners.model;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.InvocationTargetException;
005import java.lang.reflect.Method;
006import java.lang.reflect.Type;
007import java.util.List;
008
009import org.junit.internal.runners.model.ReflectiveCallable;
010
011/**
012 * Represents a method on a test class to be invoked at the appropriate point in
013 * test execution. These methods are usually marked with an annotation (such as
014 * {@code @Test}, {@code @Before}, {@code @After}, {@code @BeforeClass},
015 * {@code @AfterClass}, etc.)
016 *
017 * @since 4.5
018 */
019public class FrameworkMethod extends FrameworkMember<FrameworkMethod> {
020    private final Method method;
021
022    /**
023     * Returns a new {@code FrameworkMethod} for {@code method}
024     */
025    public FrameworkMethod(Method method) {
026        if (method == null) {
027            throw new NullPointerException(
028                    "FrameworkMethod cannot be created without an underlying method.");
029        }
030        this.method = method;
031
032        if (isPublic()) {
033            // This method could be a public method in a package-scope base class
034            try {
035                method.setAccessible(true);
036            } catch (SecurityException  e) {
037                // We may get an IllegalAccessException when we try to call the method
038            }
039        }
040    }
041
042    /**
043     * Returns the underlying Java method
044     */
045    public Method getMethod() {
046        return method;
047    }
048
049    /**
050     * Returns the result of invoking this method on {@code target} with
051     * parameters {@code params}. {@link InvocationTargetException}s thrown are
052     * unwrapped, and their causes rethrown.
053     */
054    public Object invokeExplosively(final Object target, final Object... params)
055            throws Throwable {
056        return new ReflectiveCallable() {
057            @Override
058            protected Object runReflectiveCall() throws Throwable {
059                return method.invoke(target, params);
060            }
061        }.run();
062    }
063
064    /**
065     * Returns the method's name
066     */
067    @Override
068    public String getName() {
069        return method.getName();
070    }
071
072    /**
073     * Adds to {@code errors} if this method:
074     * <ul>
075     * <li>is not public, or
076     * <li>takes parameters, or
077     * <li>returns something other than void, or
078     * <li>is static (given {@code isStatic is false}), or
079     * <li>is not static (given {@code isStatic is true}).
080     * </ul>
081     */
082    public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) {
083        validatePublicVoid(isStatic, errors);
084        if (method.getParameterTypes().length != 0) {
085            errors.add(new Exception("Method " + method.getName() + " should have no parameters"));
086        }
087    }
088
089
090    /**
091     * Adds to {@code errors} if this method:
092     * <ul>
093     * <li>is not public, or
094     * <li>returns something other than void, or
095     * <li>is static (given {@code isStatic is false}), or
096     * <li>is not static (given {@code isStatic is true}).
097     * </ul>
098     */
099    public void validatePublicVoid(boolean isStatic, List<Throwable> errors) {
100        if (isStatic() != isStatic) {
101            String state = isStatic ? "should" : "should not";
102            errors.add(new Exception("Method " + method.getName() + "() " + state + " be static"));
103        }
104        if (!isPublic()) {
105            errors.add(new Exception("Method " + method.getName() + "() should be public"));
106        }
107        if (method.getReturnType() != Void.TYPE) {
108            errors.add(new Exception("Method " + method.getName() + "() should be void"));
109        }
110    }
111
112    @Override
113    protected int getModifiers() {
114        return method.getModifiers();
115    }
116
117    /**
118     * Returns the return type of the method
119     */
120    public Class<?> getReturnType() {
121        return method.getReturnType();
122    }
123
124    /**
125     * Returns the return type of the method
126     */
127    @Override
128    public Class<?> getType() {
129        return getReturnType();
130    }
131
132    /**
133     * Returns the class where the method is actually declared
134     */
135    @Override
136    public Class<?> getDeclaringClass() {
137        return method.getDeclaringClass();
138    }
139
140    public void validateNoTypeParametersOnArgs(List<Throwable> errors) {
141        new NoGenericTypeParametersValidator(method).validate(errors);
142    }
143
144    @Override
145    public boolean isShadowedBy(FrameworkMethod other) {
146        if (!other.getName().equals(getName())) {
147            return false;
148        }
149        if (other.getParameterTypes().length != getParameterTypes().length) {
150            return false;
151        }
152        for (int i = 0; i < other.getParameterTypes().length; i++) {
153            if (!other.getParameterTypes()[i].equals(getParameterTypes()[i])) {
154                return false;
155            }
156        }
157        return true;
158    }
159
160    @Override
161    boolean isBridgeMethod() {
162        return method.isBridge();
163    }
164
165    @Override
166    public boolean equals(Object obj) {
167        if (!FrameworkMethod.class.isInstance(obj)) {
168            return false;
169        }
170        return ((FrameworkMethod) obj).method.equals(method);
171    }
172
173    @Override
174    public int hashCode() {
175        return method.hashCode();
176    }
177
178    /**
179     * Returns true if this is a no-arg method that returns a value assignable
180     * to {@code type}
181     *
182     * @deprecated This is used only by the Theories runner, and does not
183     *             use all the generic type info that it ought to. It will be replaced
184     *             with a forthcoming ParameterSignature#canAcceptResultOf(FrameworkMethod)
185     *             once Theories moves to junit-contrib.
186     */
187    @Deprecated
188    public boolean producesType(Type type) {
189        return getParameterTypes().length == 0 && type instanceof Class<?>
190                && ((Class<?>) type).isAssignableFrom(method.getReturnType());
191    }
192
193    private Class<?>[] getParameterTypes() {
194        return method.getParameterTypes();
195    }
196
197    /**
198     * Returns the annotations on this method
199     */
200    public Annotation[] getAnnotations() {
201        return method.getAnnotations();
202    }
203
204    /**
205     * Returns the annotation of type {@code annotationType} on this method, if
206     * one exists.
207     */
208    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
209        return method.getAnnotation(annotationType);
210    }
211
212    @Override
213    public String toString() {
214        return method.toString();
215    }
216}