org.testng.internal.reflect.ReflectionRecipes Maven / Gradle / Ivy
Show all versions of testng Show documentation
package org.testng.internal.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import org.testng.ITestContext;
import org.testng.ITestResult;
import org.testng.TestNGException;
import org.testng.annotations.NoInjection;
import org.testng.internal.RuntimeBehavior;
import org.testng.xml.XmlTest;
/**
* Utility class to handle reflection.
*
* @author Nitin Verma
*/
public final class ReflectionRecipes {
private static final Map, Class>> PRIMITIVE_MAPPING = new HashMap<>();
private static final Map, List>> ASSIGNABLE_MAPPING = new HashMap<>();
static {
initPrimitiveMapping();
initAssignableMapping();
}
private static void initPrimitiveMapping() {
PRIMITIVE_MAPPING.put(boolean.class, Boolean.class);
PRIMITIVE_MAPPING.put(byte.class, Byte.class);
PRIMITIVE_MAPPING.put(short.class, Short.class);
PRIMITIVE_MAPPING.put(int.class, Integer.class);
PRIMITIVE_MAPPING.put(long.class, Long.class);
PRIMITIVE_MAPPING.put(float.class, Float.class);
PRIMITIVE_MAPPING.put(double.class, Double.class);
PRIMITIVE_MAPPING.put(char.class, Character.class);
PRIMITIVE_MAPPING.put(void.class, Void.class);
}
private static void initAssignableMapping() {
ASSIGNABLE_MAPPING.put(
double.class,
Arrays.asList(
Float.class, Long.class, Integer.class, Short.class, Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(
float.class,
Arrays.asList(Long.class, Integer.class, Short.class, Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(
long.class, Arrays.asList(Integer.class, Short.class, Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(int.class, Arrays.asList(Short.class, Character.class, Byte.class));
ASSIGNABLE_MAPPING.put(short.class, Arrays.asList(Character.class, Byte.class));
}
private ReflectionRecipes() {
throw new TestNGException("Service is not meant to have instances");
}
/**
* Checks if an instance is an instance of the given class.
*
* @param reference reference class.
* @param object instance to be tested.
* @return is an instance-of or not
*/
public static boolean isInstanceOf(final Class> reference, final Object object) {
if (object == null) {
return !reference.isPrimitive();
}
boolean isInstanceOf;
final boolean directInstance = reference.isInstance(object);
if (!directInstance && reference.isPrimitive()) {
isInstanceOf = PRIMITIVE_MAPPING.get(reference).isInstance(object);
if (!isInstanceOf) {
isInstanceOf = ASSIGNABLE_MAPPING.get(reference).contains(object.getClass());
}
} else {
isInstanceOf = directInstance;
}
return isInstanceOf;
}
/**
* Checks a class instance for being a given interface or its implementation.
*
* @param reference reference interface instance.
* @param clazz class instance to be tested.
* @return would an instance of 'clazz' be an instance of reference interface.
*/
public static boolean isOrImplementsInterface(final Class> reference, final Class> clazz) {
boolean implementsInterface = false;
if (reference.isInterface()) {
if (reference.equals(clazz)) {
implementsInterface = true;
} else {
final Class>[] interfaces = clazz.getInterfaces();
for (final Class> interfaceClazz : interfaces) {
implementsInterface = interfaceClazz.equals(reference);
if (implementsInterface) break;
}
}
}
return implementsInterface;
}
/**
* Checks a class instance for being a given class or its sub-class.
*
* @param reference reference class instance.
* @param clazz class instance to be tested.
* @return would an instance of 'clazz' be an instance of reference class.
*/
public static boolean isOrExtends(final Class> reference, final Class> clazz) {
boolean extendsGiven = false;
if (clazz != null) {
if (!reference.isInterface()) {
if (reference.equals(clazz)) {
extendsGiven = true;
} else {
extendsGiven = isOrExtends(reference, clazz.getSuperclass());
}
}
}
return extendsGiven;
}
/**
* Extracts class instances from parameters.
*
* @param parameters an array of parameters.
* @return parameter types.
*/
public static Class>[] classesFromParameters(final Parameter[] parameters) {
final Class>[] classes = new Class>[parameters.length];
int i = 0;
for (final Parameter parameter : parameters) {
classes[i] = parameter.getType();
i++;
}
return classes;
}
/**
* Extracts method parameters.
*
* @param method any valid method.
* @return extracted method parameters.
*/
public static Parameter[] getMethodParameters(final Method method) {
if (method == null) {
return new Parameter[] {};
}
return method.getParameters();
}
/**
* Extracts constructor parameters.
*
* @param constructor any valid constructor.
* @return extracted constructor parameters.
*/
public static Parameter[] getConstructorParameters(final Constructor> constructor) {
if (constructor == null) {
return new Parameter[] {};
}
return constructor.getParameters();
}
/**
* @return matches or not
* @see #matchArrayEnding(Class[], Object[])
*/
public static boolean matchArrayEnding(final Parameter[] parameters, final Object[] param) {
return matchArrayEnding(classesFromParameters(parameters), param);
}
/**
* Matches an array of class instances to an array of instances having last class instance an
* array.
*
* Assuming upper case letters denote classes and corresponding lowercase its instances.
* Classes {A,B,C...}, instances {a,b,c1,c2} ==> check for {a,b,{c1,c2}} match or Classes
* {A,B,C[]}, instances {a,b,c1,c2} ==> check for {a,b,{c1,c2}} match both of the above cases
* are equivalent.
*
* @param classes array of class instances to check against.
* @param args instances to be verified.
* @return matches or not
*/
public static boolean matchArrayEnding(final Class>[] classes, final Object[] args) {
if (classes.length < 1) {
return false;
}
if (!classes[classes.length - 1].isArray()) {
return false;
}
boolean matching = true;
int i = 0;
if (classes.length <= args.length) {
for (final Class> clazz : classes) {
if (i >= classes.length - 1) {
break;
}
matching = ReflectionRecipes.isInstanceOf(clazz, args[i]);
i++;
if (!matching) break;
}
} else {
matching = false;
}
if (matching) {
final Class> componentType = classes[classes.length - 1].getComponentType();
for (; i < args.length; i++) {
matching = ReflectionRecipes.isInstanceOf(componentType, args[i]);
if (!matching) break;
}
}
return matching;
}
/**
* Matches an array of parameters to an array of instances.
*
* @return matches or not
* @see #exactMatch(Class[], Object[])
*/
public static boolean exactMatch(final Parameter[] parameters, final Object[] args) {
return exactMatch(classesFromParameters(parameters), args);
}
/**
* Matches an array of class instances to an array of instances.
*
* @param classes array of class instances to check against.
* @param args instances to be verified.
* @return matches or not
*/
public static boolean exactMatch(final Class>[] classes, final Object[] args) {
boolean matching = true;
if (classes.length == args.length) {
int i = 0;
for (final Class> clazz : classes) {
matching = ReflectionRecipes.isInstanceOf(clazz, args[i]);
i++;
if (!matching) break;
}
} else {
matching = false;
}
return matching;
}
/**
* Matches an array of parameters to an array of instances.
*
* @return matches or not
* @see #lenientMatch(Class[], Object[])
*/
public static boolean lenientMatch(final Parameter[] parameters, final Object[] args) {
return lenientMatch(classesFromParameters(parameters), args);
}
/**
* Matches an array of class instances to an array of instances. Such that {int, boolean, float}
* matches {int, boolean}
*
* @param classes array of class instances to check against.
* @param args instances to be verified.
* @return matches or not
*/
public static boolean lenientMatch(final Class>[] classes, final Object[] args) {
boolean matching = true;
int i = 0;
for (final Class> clazz : classes) {
matching = ReflectionRecipes.isInstanceOf(clazz, args[i]);
i++;
if (!matching) break;
}
return matching;
}
/**
* Omits 1. org.testng.ITestContext or its implementations from input array 2.
* org.testng.ITestResult or its implementations from input array 3. org.testng.xml.XmlTest or its
* implementations from input array 4. First method depending on filters.
*
*
An example would be Input: {ITestContext.class, int.class, Boolean.class, TestContext.class}
* Output: {int.class, Boolean.class}
*
* @param parameters array of parameter instances under question.
* @param filters filters to use.
* @return Injects free array of class instances.
*/
public static Parameter[] filter(
final Parameter[] parameters, final Set filters) {
boolean proceed = filters != null && !filters.isEmpty();
if (!proceed) {
return parameters;
}
boolean firstMethodFiltered = false;
final List filterList = new ArrayList<>(parameters.length);
for (final Parameter parameter : parameters) {
boolean omit = false;
for (final InjectableParameter injectableParameter : filters) {
omit = canInject(parameter, injectableParameter);
if (injectableParameter == InjectableParameter.CURRENT_TEST_METHOD) {
if (omit && !firstMethodFiltered) {
firstMethodFiltered = true;
} else {
omit = false;
}
}
if (omit) {
break;
}
}
if (!omit) {
filterList.add(parameter);
}
}
return filterList.toArray(new Parameter[0]);
}
/**
* Injects appropriate arguments.
*
* @param parameters array of parameter instances under question.
* @param filters filters to use.
* @param args user supplied arguments.
* @param injectionMethod current test method.
* @param context current test context.
* @param testResult on going test results.
* @return injected arguments.
*/
public static Object[] inject(
final Parameter[] parameters,
final Set filters,
final Object[] args,
final Method injectionMethod,
final ITestContext context,
final ITestResult testResult) {
return nativelyInject(parameters, filters, args, injectionMethod, context, testResult);
}
private static Object[] nativelyInject(
final Parameter[] parameters,
final Set filters,
final Object[] args,
final Object injectionMethod,
final ITestContext context,
final ITestResult testResult) {
if (filters == null || filters.isEmpty()) {
return args;
}
final ArrayList