Java tutorial
/* * Copyright 2008,2009 Toni Menzel * Copyright 2008 Alin Dreghiciu * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package org.ops4j.pax.exam.junit.extender.impl.internal; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Before; import org.osgi.framework.BundleContext; import static org.ops4j.lang.NullArgumentException.*; import org.ops4j.pax.exam.Inject; import org.ops4j.pax.exam.junit.extender.CallableTestMethod; /** * {@link Callable} implementation. * * @author Toni Menzel (tonit) * @author Alin Dreghiciu (adreghiciu@gmail.com) * @since May 29, 2008 */ class CallableTestMethodImpl implements CallableTestMethod { /** * Logger. */ private static final Log LOG = LogFactory.getLog(CallableTestMethodImpl.class); /** * Bundle context of the bundle containing the test class (cannot be null). */ private BundleContext m_bundleContext; /** * Test class name (cannot be null or empty). */ private final String m_testClassName; /** * Test method name (cannot be null or empty). */ private final String m_testMethodName; /** * Constructor. * * @param bundleContext bundle context of the bundle containing the test class (cannot be null) * @param testClassName test class name (cannot be null or empty) * @param testMethodName test method name (cannot be null or empty) * * @throws IllegalArgumentException - If bundle context is null * - If test class name is null or empty * - If test method name is null or empty */ CallableTestMethodImpl(final BundleContext bundleContext, final String testClassName, final String testMethodName) { validateNotNull(bundleContext, "Bundle context"); validateNotEmpty(testClassName, true, "Test class name"); validateNotEmpty(testMethodName, true, "Test method name"); m_bundleContext = bundleContext; m_testClassName = testClassName; m_testMethodName = testMethodName; } /** * {@inheritDoc} */ public void call() throws ClassNotFoundException, InstantiationException, IllegalAccessException, InvocationTargetException { final Class testClass = m_bundleContext.getBundle().loadClass(m_testClassName); int encountered = 0; for (final Method testMethod : testClass.getMethods()) { if (testMethod.getName().equals(m_testMethodName)) { injectContextAndInvoke(testClass.newInstance(), testMethod); encountered++; } } if (encountered == 0) { throw new RuntimeException( " test " + m_testMethodName + " not found in test class " + testClass.getName()); } } /** * Invokes the bundle context (if possible and required) and executes the test method. * * @param testInstance an instance of the test class * @param testMethod test method * * @throws IllegalAccessException - Re-thrown from reflection invokation * @throws InvocationTargetException - Re-thrown from reflection invokation */ private void injectContextAndInvoke(final Object testInstance, final Method testMethod) throws IllegalAccessException, InvocationTargetException { final Class<?>[] paramTypes = testMethod.getParameterTypes(); injectFieldInstances(testInstance.getClass(), testInstance); boolean cleanup = false; try { runBefores(testInstance); // if there is only one param and is of type BundleContext we inject it, otherwise just call // this means that if there are actual params the call will fail, but that is okay as it will be reported back if (paramTypes.length == 1 && paramTypes[0].isAssignableFrom(BundleContext.class)) { testMethod.invoke(testInstance, m_bundleContext); } else { testMethod.invoke(testInstance); } cleanup = true; runAfters(testInstance); } finally { if (!cleanup) { try { runAfters(testInstance); } catch (Throwable throwable) { LOG.warn("Got the exception when calling the runAfters. [Exception]: " + throwable); } } } } /** * Run all methods annotated with {@link Before}. * * @param testInstance an instance of the test class (cannot be null) * * @throws IllegalAccessException - Re-thrown from reflection invokation * @throws InvocationTargetException - Re-thrown from reflection invokation */ private void runBefores(final Object testInstance) throws IllegalAccessException, InvocationTargetException { for (final Method beforeMethod : getAnnotatedMethods(testInstance.getClass(), Before.class)) { final Class<?>[] paramTypes = beforeMethod.getParameterTypes(); if (paramTypes.length == 1 && paramTypes[0].isAssignableFrom(BundleContext.class)) { beforeMethod.invoke(testInstance, m_bundleContext); } else { beforeMethod.invoke(testInstance); } } } /** * Run all methods annotated with {@link After}. * * @param testInstance an instance of the test class (cannot be null) * * @throws IllegalAccessException - Re-thrown from reflection invokation * @throws InvocationTargetException - Re-thrown from reflection invokation */ private void runAfters(final Object testInstance) throws IllegalAccessException, InvocationTargetException { for (final Method afterMethod : getAnnotatedMethods(testInstance.getClass(), After.class)) { final Class<?>[] paramTypes = afterMethod.getParameterTypes(); if (paramTypes.length == 1 && paramTypes[0].isAssignableFrom(BundleContext.class)) { afterMethod.invoke(testInstance, m_bundleContext); } else { afterMethod.invoke(testInstance); } } } /** * Injects instances into fields found in testInstance and its superclases. * * @param clazz * @param inst */ private void injectFieldInstances(Class clazz, Object inst) throws IllegalAccessException { if (clazz.getSuperclass() != null) { injectFieldInstances(clazz.getSuperclass(), inst); } for (Field field : clazz.getDeclaredFields()) { setIfMatching(inst, field, m_bundleContext); } } /** * @param testInstance object instance where you found field * @param field field that is going to be set * @param o target value of field */ private void setIfMatching(Object testInstance, Field field, Object o) throws IllegalAccessException { if (isInjectionField(field) && isMatchingType(field, o.getClass())) { field.setAccessible(true); field.set(testInstance, o); } } /** * Just checks if type of field is a assignable from clazz. * * @param field * @param clazz */ private boolean isMatchingType(Field field, Class clazz) { boolean result = field.getType().isAssignableFrom(clazz); LOG.debug("Trying to match " + field.getType() + " with injection " + clazz.getName() + ": " + result); return result; } /** * Tests if the given field has the {@link @Inject} annotation. * Due to some osgi quirks, currently direct getAnnotation( Inject.class ) does not work..:( * * @param field field to be tested * * @return trze if it has the Inject annotation. Otherwise false. */ public boolean isInjectionField(Field field) { // Usually, this should be enough. if (field.getAnnotation(Inject.class) != null) { return true; } else { // the above one fails in some cases currently (returns null) while annotation is there. // So this is a fallback: for (Annotation annot : field.getAnnotations()) { if (annot.annotationType().getName().equals(Inject.class.getName())) { return true; } } } return false; } /** * Find all methods marked with a specific annotation. * * @param testClass class to be inspected * @param annotationClass annotation class to be found * * @return list of annotated methods (cannot be null) */ public List<Method> getAnnotatedMethods(final Class testClass, final Class<? extends Annotation> annotationClass) { final List<Method> results = new ArrayList<Method>(); for (final Class<?> clazz : getSuperClasses(testClass)) { final Method[] methods = clazz.getDeclaredMethods(); for (final Method method : methods) { final Annotation annotation = method.getAnnotation(annotationClass); if (annotation != null) { results.add(method); } } } return results; } /** * Finds all superclasses of a certain class including itself. * * @param testClass class whom superclasses should be found * * @return list of superclasses (cannot be null) */ private List<Class<?>> getSuperClasses(final Class<?> testClass) { final ArrayList<Class<?>> results = new ArrayList<Class<?>>(); Class<?> current = testClass; while (current != null) { results.add(current); current = current.getSuperclass(); } return results; } }