package org.testng.internal;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.testng.DataProviderHolder;
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.annotations.IObjectFactoryAnnotation;
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 static org.testng.internal.ClassHelper.getAvailableMethods;
public class TestNGClassFinder extends BaseClassFinder {
private static final String PREFIX = "[TestNGClassFinder]";
private final ITestContext m_testContext;
private final Map<Class<?>, List<Object>> m_instanceMap = Maps.newHashMap();
private final DataProviderHolder holder;
private final ITestObjectFactory objectFactory;
private final IAnnotationFinder annotationFinder;
private String m_factoryCreationFailedMessage = null;
public String getFactoryCreationFailedMessage() {
return m_factoryCreationFailedMessage;
}
public TestNGClassFinder(
ClassInfoMap cim,
Map<Class<?>, List<Object>> instanceMap,
IConfiguration configuration,
ITestContext testContext, DataProviderHolder holder) {
if (instanceMap == null) {
throw new IllegalArgumentException("instanceMap must not be null");
}
m_testContext = testContext;
this.holder = holder;
annotationFinder = configuration.getAnnotationFinder();
Set<Class<?>> allClasses = cim.getClasses();
if (configuration.getObjectFactory() == null) {
objectFactory = createObjectFactory(allClasses);
} else {
objectFactory = configuration.getObjectFactory();
}
for (Class<?> cls : allClasses) {
processClass(cim, instanceMap, configuration, cls);
}
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);
}
}
}
}
private void processClass(
ClassInfoMap cim,
Map<Class<?>, List<Object>> instanceMap,
IConfiguration configuration,
Class<?> cls) {
if (null == cls) {
Utils.log(PREFIX, 5, "[WARN] FOUND NULL CLASS");
return;
}
if (isNotTestNGClass(cls, annotationFinder)) {
Utils.log(PREFIX, 3, "SKIPPING CLASS " + cls + " no TestNG annotations found");
return;
}
List<Object> allInstances = instanceMap.get(cls);
Object thisInstance =
(allInstances != null && !allInstances.isEmpty()) ? allInstances.get(0) : null;
if ((null == thisInstance) && Modifier.isAbstract(cls.getModifiers())) {
Utils.log("", 5, "[WARN] Found an abstract class with no valid instance attached: " + cls);
return;
}
if ((null == thisInstance) && cls.isAnonymousClass()) {
Utils.log("", 5, "[WARN] Found an anonymous class with no valid instance attached" + cls);
return;
}
IClass ic =
findOrCreateIClass(
m_testContext,
cls,
cim.getXmlClass(cls),
thisInstance,
annotationFinder,
objectFactory);
if (ic == null) {
return;
}
putIClass(cls, ic);
List<ConstructorOrMethod> factoryMethods =
ClassHelper.findDeclaredFactoryMethods(cls, annotationFinder);
for (ConstructorOrMethod factoryMethod : factoryMethods) {
processMethod(configuration, ic, factoryMethod);
}
}
private void processMethod(
IConfiguration configuration, IClass ic, ConstructorOrMethod factoryMethod) {
if (!factoryMethod.getEnabled()) {
return;
}
ClassInfoMap moreClasses = processFactory(ic, factoryMethod);
if (moreClasses.isEmpty()) {
return;
}
TestNGClassFinder finder =
new TestNGClassFinder(
moreClasses, m_instanceMap, configuration, m_testContext, new DataProviderHolder());
for (IClass ic2 : finder.findTestClasses()) {
putIClass(ic2.getRealClass(), ic2);
}
}
private static boolean excludeFactory(FactoryMethod fm, ITestContext ctx) {
return fm.getGroups().length != 0
&& ctx.getCurrentXmlTest().getExcludedGroups().containsAll(Arrays.asList(fm.getGroups()));
}
private ClassInfoMap processFactory(IClass ic, ConstructorOrMethod factoryMethod) {
Object[] theseInstances = ic.getInstances(false);
Object instance = theseInstances.length != 0 ? theseInstances[0] : null;
FactoryMethod fm =
new FactoryMethod(
factoryMethod,
instance,
annotationFinder,
m_testContext,
objectFactory, holder);
ClassInfoMap moreClasses = new ClassInfoMap();
if (excludeFactory(fm, m_testContext)) {
return moreClasses;
}
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;
Object objToInspect = IParameterInfo.embeddedInstance(o);
if (IInstanceInfo.class.isAssignableFrom(objToInspect.getClass())) {
IInstanceInfo<?> ii = (IInstanceInfo) objToInspect;
addInstance(ii);
oneMoreClass = ii.getInstanceClass();
} else {
addInstance(o);
oneMoreClass = objToInspect.getClass();
}
if (!classExists(oneMoreClass)) {
moreClasses.addClass(oneMoreClass);
}
i++;
}
this.m_factoryCreationFailedMessage = fm.getFactoryCreationFailedMessage();
return moreClasses;
}
private ITestObjectFactory createObjectFactory(Set<Class<?>> allClasses) {
ITestObjectFactory objectFactory;
objectFactory = new ObjectFactoryImpl();
for (Class<?> cls : allClasses) {
try {
if (cls == null) {
continue;
}
Method[] ms;
try {
ms = cls.getMethods();
} catch (NoClassDefFoundError e) {
Utils.log(
PREFIX,
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, IObjectFactoryAnnotation.class);
if (a == null) {
continue;
}
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, m_testContext);
} else {
objectFactory = (ITestObjectFactory) m.invoke(instance);
}
return objectFactory;
} catch (Exception ex) {
throw new TestNGException("Error creating object factory: " + cls, ex);
}
}
} catch (NoClassDefFoundError e) {
Utils.log(
PREFIX,
1,
"Unable to read methods on class "
+ cls.getName()
+ " - unable to resolve class reference "
+ e.getMessage());
for (XmlClass xmlClass : m_testContext.getCurrentXmlTest().getXmlClasses()) {
if (xmlClass.loadClasses() && xmlClass.getName().equals(cls.getName())) {
throw e;
}
}
}
}
return objectFactory;
}
private static boolean isNotTestNGClass(Class<?> c, IAnnotationFinder annotationFinder) {
return (!isTestNGClass(c, annotationFinder));
}
private static boolean isTestNGClass(Class<?> c, IAnnotationFinder annotationFinder) {
Class<?> cls = c;
boolean result = false;
try {
for (Class<? extends IAnnotation> annotation : AnnotationHelper.getAllAnnotations()) {
for (cls = c; cls != null; cls = cls.getSuperclass()) {
for (Method m : getAvailableMethods(cls)) {
IAnnotation ma = annotationFinder.findAnnotation(cls, m, annotation);
if (null != ma) {
result = true;
}
}
IAnnotation a = annotationFinder.findAnnotation(cls, annotation);
if (null != a) {
result = true;
}
for (Constructor ctor : cls.getConstructors()) {
IAnnotation ca = annotationFinder.findAnnotation(ctor, annotation);
if (null != ca) {
result = true;
}
}
}
}
return result;
} catch (NoClassDefFoundError e) {
Utils.log(
PREFIX,
1,
"Unable to read methods on class "
+ cls.getName()
+ " - unable to resolve class reference "
+ e.getMessage());
return false;
}
}
private <T> void addInstance(IInstanceInfo<T> ii) {
addInstance(ii.getInstanceClass(), ii.getInstance());
}
private void addInstance(Object o) {
addInstance(IParameterInfo.embeddedInstance(o).getClass(), o);
}
private <T, S extends T> void addInstance(Class<S> clazz, T instance) {
List<Object> instances = m_instanceMap.computeIfAbsent(clazz, key -> Lists.newArrayList());
instances.add(instance);
}
}