package org.testng.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.testng.IClass;
import org.testng.IInstanceInfo;
import org.testng.ITestContext;
import org.testng.ITestObjectFactory;
import org.testng.TestNGException;
import org.testng.annotations.IAnnotation;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.testng.internal.annotations.AnnotationHelper;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlTest;
import static org.testng.internal.ClassHelper.getAvailableMethods;
/**
* This class creates an ITestClass from a test class.
*
* @author <a href="mailto:cedric@beust.com">Cedric Beust</a>
*/
public class TestNGClassFinder extends BaseClassFinder {
private final ITestContext m_testContext;
private final Map<Class<?>, List<Object>> m_instanceMap = Maps.newHashMap();
public TestNGClassFinder(ClassInfoMap cim,
XmlTest xmlTest,
IConfiguration configuration,
ITestContext testContext)
{
this(cim, Maps.<Class<?>, List<Object>>newHashMap(), xmlTest, configuration, testContext);
}
public TestNGClassFinder(ClassInfoMap cim,
Map<Class<?>, List<Object>> instanceMap,
XmlTest xmlTest,
IConfiguration configuration,
ITestContext testContext)
{
m_testContext = testContext;
if (instanceMap == null) {
throw new IllegalArgumentException("instanceMap must not be null");
}
IAnnotationFinder annotationFinder = configuration.getAnnotationFinder();
ITestObjectFactory objectFactory = configuration.getObjectFactory();
// Find all the new classes and their corresponding instances
Set<Class<?>> allClasses= cim.getClasses();
//very first pass is to find ObjectFactory, can't create anything else until then
if(objectFactory == null) {
objectFactory = new ObjectFactoryImpl();
outer:
for (Class<?> cls : allClasses) {
try {
if (null != cls) {
Method[] ms;
try {
ms = cls.getMethods();
} catch (NoClassDefFoundError e) {
// https://github.com/cbeust/testng/issues/602
Utils.log("TestNGClassFinder", 5, "[WARN] Can't link and determine methods of " + cls + "(" + e.getMessage() + ")");
ms = new Method[0];
}
for (Method m : ms) {
IAnnotation a = annotationFinder.findAnnotation(m,
org.testng.annotations.IObjectFactoryAnnotation.class);
if (null != a) {
if (!ITestObjectFactory.class.isAssignableFrom(m.getReturnType())) {
throw new TestNGException("Return type of " + m + " is not IObjectFactory");
}
try {
Object instance = cls.newInstance();
if (m.getParameterTypes().length > 0 && m.getParameterTypes()[0].equals(ITestContext.class)) {
objectFactory = (ITestObjectFactory) m.invoke(instance, testContext);
} else {
objectFactory = (ITestObjectFactory) m.invoke(instance);
}
break outer;
}
catch (Exception ex) {
throw new TestNGException("Error creating object factory: " + cls,
ex);
}
}
}
}
} catch (NoClassDefFoundError e) {
Utils.log("[TestNGClassFinder]", 1, "Unable to read methods on class " + cls.getName()
+ " - unable to resolve class reference " + e.getMessage());
for (XmlClass xmlClass : xmlTest.getXmlClasses()) {
if (xmlClass.loadClasses() && xmlClass.getName().equals(cls.getName())) {
throw e;
}
}
}
}
}
for(Class<?> cls : allClasses) {
if (null == cls) {
Utils.log("TestNGClassFinder", 5, "[WARN] FOUND NULL CLASS");
continue;
}
if(isTestNGClass(cls, annotationFinder)) {
List<Object> allInstances = instanceMap.get(cls);
Object thisInstance = (allInstances != null && !allInstances.isEmpty()) ? allInstances.get(0) : null;
// If annotation class and instances are abstract, skip them
if ((null == thisInstance) && Modifier.isAbstract(cls.getModifiers())) {
Utils.log("", 5, "[WARN] Found an abstract class with no valid instance attached: " + cls);
continue;
}
if((null == thisInstance) && cls.isAnonymousClass()) {
Utils.log("", 5, "[WARN] Found an anonymous class with no valid instance attached" + cls);
continue;
}
IClass ic= findOrCreateIClass(m_testContext, cls, cim.getXmlClass(cls), thisInstance,
xmlTest, annotationFinder, objectFactory);
if(null != ic) {
putIClass(cls, ic);
List<ConstructorOrMethod> factoryMethods = ClassHelper.findDeclaredFactoryMethods(cls, annotationFinder);
for (ConstructorOrMethod factoryMethod : factoryMethods) {
if (factoryMethod.getEnabled()) {
Object[] theseInstances = ic.getInstances(false);
if (theseInstances.length == 0) {
theseInstances = ic.getInstances(true);
}
Object instance = theseInstances.length != 0 ? theseInstances[0] : null;
FactoryMethod fm = new FactoryMethod(
factoryMethod,
instance,
xmlTest,
annotationFinder,
m_testContext, objectFactory);
ClassInfoMap moreClasses = new ClassInfoMap();
// If the factory returned IInstanceInfo, get the class from it,
// otherwise, just call getClass() on the returned instances
int i = 0;
for (Object o : fm.invoke()) {
if (o == null) {
throw new TestNGException("The factory " + fm + " returned a null instance" +
"at index " + i);
}
Class<?> oneMoreClass;
if(IInstanceInfo.class.isAssignableFrom(o.getClass())) {
IInstanceInfo<?> ii = (IInstanceInfo) o;
addInstance(ii);
oneMoreClass = ii.getInstanceClass();
} else {
addInstance(o);
oneMoreClass = o.getClass();
}
if(!classExists(oneMoreClass)) {
moreClasses.addClass(oneMoreClass);
}
i++;
}
if(!moreClasses.isEmpty()) {
TestNGClassFinder finder =
new TestNGClassFinder(moreClasses,
m_instanceMap,
xmlTest,
configuration,
m_testContext);
for(IClass ic2 : finder.findTestClasses()) {
putIClass(ic2.getRealClass(), ic2);
}
}
}
}
} // null != ic
} // if not TestNG class
else {
Utils.log("TestNGClassFinder", 3, "SKIPPING CLASS " + cls + " no TestNG annotations found");
}
} // for
//
// Add all the instances we found to their respective IClasses
//
for(Map.Entry<Class<?>, List<Object>> entry : m_instanceMap.entrySet()) {
Class<?> clazz = entry.getKey();
for(Object instance : entry.getValue()) {
IClass ic= getIClass(clazz);
if(null != ic) {
ic.addInstance(instance);
}
}
}
}
/**
* @return true if this class contains TestNG annotations (either on itself
* or on a superclass).
*/
private static boolean isTestNGClass(Class<?> c, IAnnotationFinder annotationFinder) {
Class<?> cls = c;
try {
for(Class<? extends IAnnotation> annotation : AnnotationHelper.getAllAnnotations()) {
for (cls = c; cls != null; cls = cls.getSuperclass()) {
// Try on the methods
for (Method m : getAvailableMethods(cls)) {
IAnnotation ma= annotationFinder.findAnnotation(m, annotation);
if(null != ma) {
return true;
}
}
// Try on the class
IAnnotation a= annotationFinder.findAnnotation(cls, annotation);
if(null != a) {
return true;
}
// Try on the constructors
for (Constructor ctor : cls.getConstructors()) {
IAnnotation ca= annotationFinder.findAnnotation(ctor, annotation);
if(null != ca) {
return true;
}
}
}
}
return false;
} catch (NoClassDefFoundError e) {
Utils.log("[TestNGClassFinder]", 1,
"Unable to read methods on class " + cls.getName()
+ " - unable to resolve class reference " + e.getMessage());
return false;
}
}
// IInstanceInfo<T> should be replaced by IInstanceInfo<?> but eclipse complains against it: https://github.com/cbeust/testng/issues/1070
private <T> void addInstance(IInstanceInfo<T> ii) {
addInstance(ii.getInstanceClass(), ii.getInstance());
}
private void addInstance(Object o) {
addInstance(o.getClass(), o);
}
// Class<S> should be replaced by Class<? extends T> but java doesn't fail as expected: https://github.com/cbeust/testng/issues/1070
private <T, S extends T> void addInstance(Class<S> clazz, T instance) {
List<Object> instances = m_instanceMap.get(clazz);
if (instances == null) {
instances = Lists.newArrayList();
m_instanceMap.put(clazz, instances);
}
instances.add(instance);
}
}