org.testng.internal.ClassHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of testng Show documentation
Show all versions of testng Show documentation
Testing framework for Java
package org.testng.internal;
import java.lang.reflect.Executable;
import java.util.function.BiConsumer;
import org.testng.IClass;
import org.testng.IMethodSelector;
import org.testng.IObjectFactory;
import org.testng.ITestObjectFactory;
import org.testng.TestNGException;
import org.testng.TestRunner;
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.junit.IJUnitTestRunner;
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;
/** Utility class for different class manipulations. */
public final class ClassHelper {
/** The additional class loaders to find classes in. */
private static final List classLoaders = new Vector<>();
private static final String CLASS_HELPER = ClassHelper.class.getSimpleName();
/**
* When given a file name to form a class name, the file name is parsed and divided into segments.
* For example, "c:/java/classes/com/foo/A.class" would be divided into 6 segments {"C:" "java",
* "classes", "com", "foo", "A"}. The first segment actually making up the class name is [3]. This
* value is saved in lastGoodRootIndex so that when we parse the next file name, we will try 3
* right away. If 3 fails we will take the long approach. This is just a optimization cache value.
*/
private static int lastGoodRootIndex = -1;
/** Hide constructor. */
private ClassHelper() {
// Hide Constructor
}
/** Add a class loader to the searchable loaders. */
public static void addClassLoader(final ClassLoader loader) {
classLoaders.add(loader);
}
/**
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static T newInstance(Class clazz) {
return InstanceCreator.newInstance(clazz);
}
/**
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static T newInstanceOrNull(Class clazz) {
return InstanceCreator.newInstanceOrNull(clazz);
}
/**
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static T newInstance(Constructor constructor, Object... parameters) {
return InstanceCreator.newInstance(constructor, parameters);
}
static List appendContextualClassLoaders(List currentLoaders) {
List allClassLoaders = Lists.newArrayList();
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
allClassLoaders.add(contextClassLoader);
}
allClassLoaders.addAll(currentLoaders);
return allClassLoaders;
}
/**
* Tries to load the specified class using the context ClassLoader or if none, than from the
* default ClassLoader. This method differs from the standard class loading methods in that it
* does not throw an exception if the class is not found but returns null instead.
*
* @param className the class name to be loaded.
* @return the class or null if the class is not found.
*/
public static Class forName(final String className) {
List allClassLoaders = appendContextualClassLoaders(classLoaders);
for (ClassLoader classLoader : allClassLoaders) {
if (null == classLoader) {
continue;
}
try {
return classLoader.loadClass(className);
} catch (ClassNotFoundException ex) {
// With additional class loaders, it is legitimate to ignore ClassNotFoundException
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, Exception ex) {
Utils.log(
CLASS_HELPER,
2,
"Could not instantiate " + className + " : Class doesn't exist (" + ex.getMessage() + ")");
}
/**
* For the given class, returns the method annotated with @Factory or null if none is found.
* This method does not search up the superclass hierarchy. If more than one method is @Factory
* annotated, a TestNGException is thrown.
*
* @param cls The class to search for the @Factory annotation.
* @param finder The finder (JDK 1.4 or JDK 5.0+) use to search for the annotation.
* @return the @Factory methods
*/
//Code smell: The javadocs for this method says that if more than one Factory method is found
//an exception is thrown. But the method is not doing that. This needs more investigation for
//regression side effects before being addressed.
public static List findDeclaredFactoryMethods(
Class cls, IAnnotationFinder finder) {
List result = new ArrayList<>();
BiConsumer 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;
}
/**
* Extract all callable methods of a class and all its super (keeping in mind the Java access
* rules).
*/
public static Set getAvailableMethods(Class clazz) {
Map> 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>> extractedMethods =
extractMethods(clazz, parent, methods).entrySet();
for (Map.Entry> extractedMethod : extractedMethods) {
Set m = methods.get(extractedMethod.getKey());
if (m == null) {
methods.put(extractedMethod.getKey(), extractedMethod.getValue());
} else {
m.addAll(extractedMethod.getValue());
}
}
parent = parent.getSuperclass();
}
}
Set returnValue = Sets.newHashSet();
for (Set each : methods.values()) {
returnValue.addAll(each);
}
return returnValue;
}
/**
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static IJUnitTestRunner createTestRunner(TestRunner runner) {
return IJUnitTestRunner.createTestRunner(runner);
}
private static void appendMethod(Map> methods, Method declaredMethod) {
Set declaredMethods =
methods.computeIfAbsent(declaredMethod.getName(), k -> Sets.newHashSet());
declaredMethods.add(declaredMethod);
}
private static Map> extractMethods(
Class childClass, Class clazz, Map> collected) {
Map> 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> 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> methodsByName) {
Set 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;
}
/**
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static IMethodSelector createSelector(org.testng.xml.XmlMethodSelector selector) {
return InstanceCreator.createSelector(selector);
}
/**
* Create an instance for the given class.
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static Object createInstance(
Class declaringClass,
Map, IClass> classes,
XmlTest xmlTest,
IAnnotationFinder finder,
ITestObjectFactory objectFactory,
boolean create) {
return InstanceCreator.createInstance(declaringClass, classes, xmlTest, finder, objectFactory, create, "");
}
/**
* @deprecated - This method is deprecated as of TestNG 7.0.0
*/
@Deprecated
public static Object createInstance1(
Class declaringClass,
Map, IClass> classes,
XmlTest xmlTest,
IAnnotationFinder finder,
IObjectFactory factory,
boolean create
) {
return InstanceCreator.createInstanceUsingObjectFactory(declaringClass, classes, xmlTest, finder, factory, create, "");
}
/** Find the best constructor given the parameters found on the annotation */
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 tryOtherConstructor(Class declaringClass) {
T result;
try {
// Special case for inner classes
if (declaringClass.getModifiers() == 0) {
return null;
}
Constructor 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;
}
/**
* Returns the Class object corresponding to the given name. The name may be of the following
* form:
*
*
* - A class name: "org.testng.TestNG"
*
- A class file name: "/testng/src/org/testng/TestNG.class"
*
- A class source name: "d:\testng\src\org\testng\TestNG.java"
*
*
* @param file the class name.
* @return the class corresponding to the name specified.
*/
public static Class fileToClass(String file) {
Class result = null;
if (!file.endsWith(".class") && !file.endsWith(".java")) {
// Doesn't end in .java or .class, assume it's a class name
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");
}
// Transforms the file name into a class name.
// Remove the ".class" or ".java" extension.
String shortFileName = file.substring(0, classIndex);
// Split file name into segments. For example "c:/java/classes/com/foo/A"
String[] segments = shortFileName.split("[/\\\\]", -1);
//
// Check if the last good root index works for this one. For example, if the previous
// name was "c:/java/classes/com/foo/A.class" then lastGoodRootIndex is 3 and we
// try to make a class name ignoring the first lastGoodRootIndex segments (3). This
// will succeed rapidly if the path is the same as the one from the previous name.
//
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;
}
}
//
// We haven't found a good root yet, start by resolving the class from the end segment
// and work our way up. For example, if we start with "c:/java/classes/com/foo/A"
// we'll start by resolving "A", then "foo.A", then "com.foo.A" until something
// resolves. When it does, we remember the path we are at as "lastGoodRoodIndex".
//
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;
}
/**
* @param cls - The class to look for.
* @param suite - The {@link XmlSuite} whose <test> tags needs to be searched in.
* @return - All the {@link XmlClass} objects that share the same <test> tag as the class.
*/
public static XmlClass[] findClassesInSameTest(Class cls, XmlSuite suite) {
Collection vResult = Sets.newHashSet();
for (XmlTest test : suite.getTests()) {
vResult.addAll(findClassesInSameTest(cls, test));
}
return vResult.toArray(new XmlClass[0]);
}
private static Collection findClassesInSameTest(Class cls, XmlTest xmlTest) {
Collection vResult = Sets.newHashSet();
String className = cls.getName();
for (XmlClass testClass : xmlTest.getXmlClasses()) {
if (testClass.getName().equals(className)) {
// Found it, add all the classes in this test in the result
vResult.addAll(xmlTest.getXmlClasses());
break;
}
}
return vResult;
}
}