package org.testng.internal;
import java.lang.reflect.Executable;
import java.util.function.BiConsumer;
import org.testng.TestNGException;
import org.testng.annotations.IFactoryAnnotation;
import org.testng.annotations.IParametersAnnotation;
import org.testng.collections.Lists;
import org.testng.collections.Maps;
import org.testng.collections.Sets;
import org.testng.internal.annotations.IAnnotationFinder;
import org.testng.internal.reflect.ReflectionHelper;
import org.testng.xml.XmlClass;
import org.testng.xml.XmlSuite;
import org.testng.xml.XmlTest;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
public final class ClassHelper {
private static final List<ClassLoader> classLoaders = new Vector<>();
private static final String CLASS_HELPER = ClassHelper.class.getSimpleName();
private static int lastGoodRootIndex = -1;
private ClassHelper() {
}
public static void addClassLoader(final ClassLoader loader) {
classLoaders.add(loader);
}
static List<ClassLoader> appendContextualClassLoaders(List<ClassLoader> currentLoaders) {
List<ClassLoader> allClassLoaders = Lists.newArrayList();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
allClassLoaders.add(contextClassLoader);
}
allClassLoaders.addAll(currentLoaders);
return allClassLoaders;
}
public static Class<?> forName(final String className) {
List<ClassLoader> allClassLoaders = appendContextualClassLoaders(classLoaders);
for (ClassLoader classLoader : allClassLoaders) {
if (null == classLoader) {
continue;
}
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException | NoClassDefFoundError ex) {
if (classLoaders.isEmpty()) {
logClassNotFoundError(className, ex);
}
}
}
if (RuntimeBehavior.shouldSkipUsingCallerClassLoader()) {
return null;
}
try {
return Class.forName(className);
} catch (ClassNotFoundException cnfe) {
logClassNotFoundError(className, cnfe);
return null;
}
}
private static void logClassNotFoundError(String className, Throwable ex) {
Utils.log(
CLASS_HELPER,
2,
"Could not instantiate " + className + " : Class doesn't exist (" + ex.getMessage() + ")");
}
public static List<ConstructorOrMethod> findDeclaredFactoryMethods(
Class<?> cls, IAnnotationFinder finder) {
List<ConstructorOrMethod> result = new ArrayList<>();
BiConsumer<IFactoryAnnotation, Executable> consumer = (f, executable) -> {
if (f != null) {
ConstructorOrMethod factory = new ConstructorOrMethod(executable);
factory.setEnabled(f.getEnabled());
result.add(factory);
}
};
for (Method method : getAvailableMethods(cls)) {
IFactoryAnnotation f = finder.findAnnotation(method, IFactoryAnnotation.class);
consumer.accept(f, method);
}
for (Constructor constructor : cls.getDeclaredConstructors()) {
IFactoryAnnotation f = finder.findAnnotation(constructor, IFactoryAnnotation.class);
consumer.accept(f, constructor);
}
return result;
}
public static Set<Method> getAvailableMethods(Class<?> clazz) {
Map<String, Set<Method>> methods = Maps.newHashMap();
for (final Method declaredMethod : ReflectionHelper.getLocalMethods(clazz)) {
appendMethod(methods, declaredMethod);
}
Class<?> parent = clazz.getSuperclass();
if (null != parent) {
while (!Object.class.equals(parent)) {
Set<Map.Entry<String, Set<Method>>> extractedMethods =
extractMethods(clazz, parent, methods).entrySet();
for (Map.Entry<String, Set<Method>> extractedMethod : extractedMethods) {
Set<Method> m = methods.get(extractedMethod.getKey());
if (m == null) {
methods.put(extractedMethod.getKey(), extractedMethod.getValue());
} else {
m.addAll(extractedMethod.getValue());
}
}
parent = parent.getSuperclass();
}
}
Set<Method> returnValue = Sets.newHashSet();
for (Set<Method> each : methods.values()) {
returnValue.addAll(each);
}
return returnValue;
}
private static void appendMethod(Map<String, Set<Method>> methods, Method declaredMethod) {
Set<Method> declaredMethods =
methods.computeIfAbsent(declaredMethod.getName(), k -> Sets.newHashSet());
declaredMethods.add(declaredMethod);
}
private static Map<String, Set<Method>> (
Class<?> childClass, Class<?> clazz, Map<String, Set<Method>> collected) {
Map<String, Set<Method>> methods = Maps.newHashMap();
Method[] declaredMethods = clazz.getDeclaredMethods();
Package childPackage = childClass.getPackage();
Package classPackage = clazz.getPackage();
boolean isSamePackage = isSamePackage(childPackage, classPackage);
for (Method method : declaredMethods) {
if (canInclude(isSamePackage, method, collected)) {
appendMethod(methods, method);
}
}
return methods;
}
private static boolean canInclude(
boolean isSamePackage, Method method, Map<String, Set<Method>> collected) {
int methodModifiers = method.getModifiers();
boolean visible =
(Modifier.isPublic(methodModifiers) || Modifier.isProtected(methodModifiers))
|| (isSamePackage && !Modifier.isPrivate(methodModifiers));
boolean hasNoInheritanceTraits =
!isOverridden(method, collected) && !Modifier.isAbstract(methodModifiers);
return visible && hasNoInheritanceTraits;
}
private static boolean isSamePackage(Package childPackage, Package classPackage) {
boolean isSamePackage = false;
if ((null == childPackage) && (null == classPackage)) {
isSamePackage = true;
}
if ((null != childPackage) && (null != classPackage)) {
isSamePackage = childPackage.getName().equals(classPackage.getName());
}
return isSamePackage;
}
private static boolean isOverridden(Method method, Map<String, Set<Method>> methodsByName) {
Set<Method> collectedMethods = methodsByName.get(method.getName());
if (collectedMethods == null) {
return false;
}
Class<?> methodClass = method.getDeclaringClass();
Class<?>[] methodParams = method.getParameterTypes();
for (Method m : collectedMethods) {
Class<?>[] paramTypes = m.getParameterTypes();
if (methodClass.isAssignableFrom(m.getDeclaringClass())
&& methodParams.length == paramTypes.length) {
boolean sameParameters = true;
for (int i = 0; i < methodParams.length; i++) {
if (!methodParams[i].equals(paramTypes[i])) {
sameParameters = false;
break;
}
}
if (sameParameters) {
return true;
}
}
}
return false;
}
static Constructor<?> findAnnotatedConstructor(
IAnnotationFinder finder, Class<?> declaringClass) {
Constructor<?>[] constructors = declaringClass.getDeclaredConstructors();
for (Constructor<?> result : constructors) {
IParametersAnnotation parametersAnnotation =
finder.findAnnotation(result, IParametersAnnotation.class);
if (parametersAnnotation != null) {
String[] parameters = parametersAnnotation.getValue();
Class<?>[] parameterTypes = result.getParameterTypes();
if (parameters.length != parameterTypes.length) {
throw new TestNGException(
"Parameter count mismatch: "
+ result
+ "\naccepts "
+ parameterTypes.length
+ " parameters but the @Test annotation declares "
+ parameters.length);
}
return result;
}
IFactoryAnnotation factoryAnnotation =
finder.findAnnotation(result, IFactoryAnnotation.class);
if (factoryAnnotation != null) {
return result;
}
}
return null;
}
public static <T> T tryOtherConstructor(Class<T> declaringClass) {
T result;
try {
if (declaringClass.getModifiers() == 0) {
return null;
}
Constructor<T> ctor = declaringClass.getConstructor(String.class);
result = ctor.newInstance("Default test name");
} catch (Exception e) {
String message = e.getMessage();
if ((message == null) && (e.getCause() != null)) {
message = e.getCause().getMessage();
}
String error =
"Could not create an instance of class "
+ declaringClass
+ ((message != null) ? (": " + message) : "")
+ ".\nPlease make sure it has a constructor that accepts either a String or no parameter.";
throw new TestNGException(error);
}
return result;
}
public static Class<?> fileToClass(String file) {
Class<?> result = null;
if (!file.endsWith(".class") && !file.endsWith(".java")) {
if (file.startsWith("class ")) {
file = file.substring("class ".length());
}
result = ClassHelper.forName(file);
if (null == result) {
throw new TestNGException("Cannot load class from file: " + file);
}
return result;
}
int classIndex = file.lastIndexOf(".class");
if (-1 == classIndex) {
classIndex = file.lastIndexOf(".java");
}
String shortFileName = file.substring(0, classIndex);
String[] segments = shortFileName.split("[/\\\\]", -1);
if (-1 != lastGoodRootIndex) {
StringBuilder className = new StringBuilder(segments[lastGoodRootIndex]);
for (int i = lastGoodRootIndex + 1; i < segments.length; i++) {
className.append(".").append(segments[i]);
}
result = ClassHelper.forName(className.toString());
if (null != result) {
return result;
}
}
String className = "";
for (int i = segments.length - 1; i >= 0; i--) {
if (className.length() == 0) {
className = segments[i];
} else {
className = segments[i] + "." + className;
}
result = ClassHelper.forName(className);
if (null != result) {
lastGoodRootIndex = i;
break;
}
}
if (null == result) {
throw new TestNGException("Cannot load class from file: " + file);
}
return result;
}
public static XmlClass[] findClassesInSameTest(Class<?> cls, XmlSuite suite) {
Collection<XmlClass> vResult = Sets.newHashSet();
for (XmlTest test : suite.getTests()) {
vResult.addAll(findClassesInSameTest(cls, test));
}
return vResult.toArray(new XmlClass[0]);
}
private static Collection<XmlClass> findClassesInSameTest(Class<?> cls, XmlTest xmlTest) {
Collection<XmlClass> vResult = Sets.newHashSet();
String className = cls.getName();
for (XmlClass testClass : xmlTest.getXmlClasses()) {
if (testClass.getName().equals(className)) {
vResult.addAll(xmlTest.getXmlClasses());
break;
}
}
return vResult;
}
}