com.tngtech.junit.dataprovider.resolver.DefaultDataProviderMethodResolver Maven / Gradle / Ivy
Show all versions of junit-dataprovider-core Show documentation
package com.tngtech.junit.dataprovider.resolver;
import static com.tngtech.junit.dataprovider.Preconditions.checkNotNull;
import static java.lang.Character.toUpperCase;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* Default implementation to resolve the dataprovider method for a given {@link DataProviderResolverContext}.
*
* The locations of the dataprovider are retrieved from given {@link DataProviderResolverContext#getLocations()}.
*
* The name of the dataprovider method can be explicitly set via
* {@link DataProviderResolverContext#getDataProviderName()} or derived by convention if name is equal to
* {@link DataProviderResolverContext#METHOD_NAME_TO_USE_CONVENTION}. Here are the applied strategies for dataprovider
* method name resolution:
*
* - Explicitly configured name using {@link DataProviderResolverContext#getDataProviderName()}. (no fallback if
* dataprovider could not be found)
* - Dataprovider method which name equals the test method name
* - Dataprovider method whereby prefix is replaced by one out of the following:
*
* Prefix replacement overview
*
* prefix
* replacement
*
*
* test
* dataProvider
*
*
* test
* data
*
*
*
* - Dataprovider method whereby additional prefix "dataProvider" or "data" is given. Also the first letter of the
* original test method name is uppercased, e.g. {@code shouldReturnTwoForOnePlusOne} corresponds to
* {@code dataProviderShouldReturnTwoForOnePlusOne}.
*
*
* Note: Dataproviders are also found if they are not public.
*/
public class DefaultDataProviderMethodResolver implements DataProviderMethodResolver {
/**
* {@inheritDoc}
*
* @see DefaultDataProviderMethodResolver
*/
@Override
public List resolve(DataProviderResolverContext context) {
checkNotNull(context, "'context' must not be null");
List dataProviderMethods = findAnnotatedMethods(context.getLocations(),
context.getDataProviderAnnotationClass());
List result = new ArrayList();
for (Method dataProviderMethod : dataProviderMethods) {
if (context.useDataProviderNameConvention()) {
if (isMatchingNameConvention(context.getTestMethod().getName(), dataProviderMethod.getName())) {
result.add(dataProviderMethod);
}
} else if (dataProviderMethod.getName().equals(context.getDataProviderName())) {
result.add(dataProviderMethod);
}
}
return result;
}
/**
* Searches methods annotated in the given locations one after another. Thereby all methods which are annotated with
* the given annotation are searched in the respective class hierarchy completely from the given location (= child
* class) down to {@link Object} (= parent class).
*
* Note:
*
* - Resulting methods are always sorted the same (see {@link #sorted(Method[])}).
* - Shadowing check is only applied location by location, not location overarching.
*
*
* @param locations denote where to search for methods
* @param annotationClass which found methods should contain
* @return the found methods or an empty list
*/
protected List findAnnotatedMethods(List> locations, Class extends Annotation> annotationClass) {
List result = new ArrayList();
for (Class> location : locations) {
List intermediateResult = new ArrayList();
Class> currentClass = location;
while (currentClass != null) {
for (Method method : sorted(currentClass.getDeclaredMethods())) {
Annotation foundAnnotation = method.getAnnotation(annotationClass);
if (foundAnnotation != null && !isMethodShadowedBy(method, intermediateResult)) {
intermediateResult.add(method);
}
}
currentClass = currentClass.getSuperclass();
}
result.addAll(intermediateResult);
}
return result;
}
protected boolean isMatchingNameConvention(String testMethodName, String dataProviderMethodName) {
if (dataProviderMethodName.equals(testMethodName)) {
return true;
} else if (dataProviderMethodName.equals(testMethodName.replaceAll("^test", "dataProvider"))) {
return true;
} else if (dataProviderMethodName.equals(testMethodName.replaceAll("^test", "data"))) {
return true;
} else if (dataProviderMethodName
.equals("dataProvider" + toUpperCase(testMethodName.charAt(0)) + testMethodName.substring(1))) {
return true;
} else if (dataProviderMethodName
.equals("data" + toUpperCase(testMethodName.charAt(0)) + testMethodName.substring(1))) {
return true;
}
return false;
}
/**
* Method sorter to get a predicatable order of dataproviders because of
* JDK-7023180 : Change in specified-to-be-unspecified ordering
* of getDeclaredMethods causes application problems.
*
* @param methods to be sorted
* @return a sorted copy of methods
*/
private Method[] sorted(Method[] methods) {
// Comparator for {@link Method}s based upon JUnit 4's {@code org.junit.internal.MethodSorter} implementation.
Comparator defaultSorter = new Comparator() {
@Override
public int compare(Method method1, Method method2) {
String name1 = method1.getName();
String name2 = method2.getName();
int comparison = Integer.compare(name1.hashCode(), name2.hashCode());
if (comparison == 0) {
comparison = name1.compareTo(name2);
if (comparison == 0) {
comparison = method1.toString().compareTo(method2.toString());
}
}
return comparison;
}
};
Method[] result = Arrays.copyOf(methods, methods.length);
Arrays.sort(result, defaultSorter);
return result;
}
private boolean isMethodShadowedBy(Method method, List methods) {
for (Method other : methods) {
if (!other.getName().equals(method.getName())) {
continue;
}
if (!Arrays.equals(other.getParameterTypes(), method.getParameterTypes())) {
continue;
}
return true;
}
return false;
}
}