• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package junitparams.internal;
2 
3 import java.lang.annotation.Annotation;
4 import java.lang.reflect.Array;
5 import java.math.BigDecimal;
6 
7 import org.junit.runners.model.FrameworkMethod;
8 import org.junit.runners.model.Statement;
9 
10 import junitparams.converters.ConversionFailedException;
11 import junitparams.converters.ConvertParam;
12 import junitparams.converters.ParamAnnotation;
13 import junitparams.converters.ParamConverter;
14 
15 /**
16  * JUnit invoker for parameterised test methods
17  *
18  * @author Pawel Lipinski
19  */
20 public class InvokeParameterisedMethod extends Statement {
21 
22     private final Object[] params;
23     private final FrameworkMethod testMethod;
24     private final Object testClass;
25 
InvokeParameterisedMethod(FrameworkMethod testMethod, Object testClass, Object params)26     public InvokeParameterisedMethod(FrameworkMethod testMethod, Object testClass, Object params) {
27         this.testMethod = testMethod;
28         this.testClass = testClass;
29         try {
30             if (params instanceof String)
31                 this.params = castParamsFromString((String) params);
32             else {
33                 this.params = castParamsFromObjects(params);
34             }
35         } catch (ConversionFailedException e) {
36             throw new RuntimeException(e);
37         }
38     }
39 
castParamsFromString(String params)40     private Object[] castParamsFromString(String params) throws ConversionFailedException {
41         Object[] columns = null;
42         try {
43             columns = Utils.splitAtCommaOrPipe(params);
44             columns = castParamsUsingConverters(columns);
45         } catch (RuntimeException e) {
46             new IllegalArgumentException("Cannot parse parameters. Did you use ',' or '|' as column separator? "
47                     + params, e).printStackTrace();
48         }
49 
50         return columns;
51     }
52 
castParamsFromObjects(Object params)53     private Object[] castParamsFromObjects(Object params) throws ConversionFailedException {
54         Object[] paramset = Utils.safelyCastParamsToArray(params);
55 
56         try {
57             return castParamsUsingConverters(paramset);
58         } catch (ConversionFailedException e) {
59             throw e;
60         } catch (Exception e) {
61             Class<?>[] typesOfParameters = createArrayOfTypesOf(paramset);
62             Object resultParam = createObjectOfExpectedTypeBasedOnParams(paramset, typesOfParameters);
63             return new Object[]{resultParam};
64         }
65     }
66 
createObjectOfExpectedTypeBasedOnParams(Object[] paramset, Class<?>[] typesOfParameters)67     private Object createObjectOfExpectedTypeBasedOnParams(Object[] paramset, Class<?>[] typesOfParameters) {
68         Object resultParam;
69 
70         try {
71             if (testMethod.getMethod().getParameterTypes()[0].isArray()) {
72                 resultParam = Array.newInstance(typesOfParameters[0], paramset.length);
73                 for (int i = 0; i < paramset.length; i++) {
74                     ((Object[]) resultParam)[i] = paramset[i];
75                 }
76             } else {
77                 resultParam = testMethod.getMethod().getParameterTypes()[0].getConstructor(typesOfParameters).newInstance(paramset);
78             }
79         } catch (Exception e) {
80             throw new IllegalStateException("While trying to create object of class " + testMethod.getMethod().getParameterTypes()[0]
81                     + " could not find constructor with arguments matching (type-wise) the ones given in parameters.", e);
82         }
83         return resultParam;
84     }
85 
createArrayOfTypesOf(Object[] paramset)86     private Class<?>[] createArrayOfTypesOf(Object[] paramset) {
87         Class<?>[] parametersBasedOnValues = new Class<?>[paramset.length];
88         for (int i = 0; i < paramset.length; i++) {
89             parametersBasedOnValues[i] = paramset[i].getClass();
90         }
91         return parametersBasedOnValues;
92     }
93 
castParamsUsingConverters(Object[] columns)94     private Object[] castParamsUsingConverters(Object[] columns) throws ConversionFailedException {
95         Class<?>[] expectedParameterTypes = testMethod.getMethod().getParameterTypes();
96 
97         if (testMethodParamsHasVarargs(columns, expectedParameterTypes)) {
98             columns = columnsWithVarargs(columns, expectedParameterTypes);
99         }
100 
101         Annotation[][] parameterAnnotations = testMethod.getMethod().getParameterAnnotations();
102         verifySameSizeOfArrays(columns, expectedParameterTypes);
103         columns = castAllParametersToProperTypes(columns, expectedParameterTypes, parameterAnnotations);
104         return columns;
105     }
106 
columnsWithVarargs(Object[] columns, Class<?>[] expectedParameterTypes)107     private Object[] columnsWithVarargs(Object[] columns, Class<?>[] expectedParameterTypes) {
108         Object[] allParameters = standardParameters(columns, expectedParameterTypes);
109         allParameters[allParameters.length - 1] = varargsParameters(columns, expectedParameterTypes);
110         return allParameters;
111     }
112 
varargsParameters(Object[] columns, Class<?>[] expectedParameterTypes)113     private Object[] varargsParameters(Object[] columns, Class<?>[] expectedParameterTypes) {
114         Class<?> varArgType = expectedParameterTypes[expectedParameterTypes.length - 1].getComponentType();
115         Object[] varArgsParameters = (Object[]) Array.newInstance(varArgType, columns.length - expectedParameterTypes.length + 1);
116         for (int i = 0; i < varArgsParameters.length; i++) {
117             varArgsParameters[i] = columns[i + expectedParameterTypes.length - 1];
118         }
119         return varArgsParameters;
120     }
121 
standardParameters(Object[] columns, Class<?>[] expectedParameterTypes)122     private Object[] standardParameters(Object[] columns, Class<?>[] expectedParameterTypes) {
123         Object[] standardParameters = new Object[expectedParameterTypes.length];
124         for (int i = 0; i < standardParameters.length - 1; i++) {
125             standardParameters[i] = columns[i];
126         }
127         return standardParameters;
128     }
129 
testMethodParamsHasVarargs(Object[] columns, Class<?>[] expectedParameterTypes)130     private boolean testMethodParamsHasVarargs(Object[] columns, Class<?>[] expectedParameterTypes) {
131         int last = expectedParameterTypes.length - 1;
132         if (columns[last] == null) {
133             return false;
134         }
135         return expectedParameterTypes.length <= columns.length
136                 && expectedParameterTypes[last].isArray()
137                 && expectedParameterTypes[last].getComponentType().equals(columns[last].getClass());
138     }
139 
castAllParametersToProperTypes(Object[] columns, Class<?>[] expectedParameterTypes, Annotation[][] parameterAnnotations)140     private Object[] castAllParametersToProperTypes(Object[] columns, Class<?>[] expectedParameterTypes,
141                                                     Annotation[][] parameterAnnotations) throws ConversionFailedException {
142         Object[] result = new Object[columns.length];
143 
144         for (int i = 0; i < columns.length; i++) {
145             if (parameterAnnotations[i].length == 0)
146                 result[i] = castParameterDirectly(columns[i], expectedParameterTypes[i]);
147             else
148                 result[i] = castParameterUsingConverter(columns[i], parameterAnnotations[i]);
149         }
150 
151         return result;
152     }
153 
castParameterUsingConverter(Object param, Annotation[] annotations)154     private Object castParameterUsingConverter(Object param, Annotation[] annotations) throws ConversionFailedException {
155         for (Annotation annotation : annotations) {
156             if (ParamAnnotation.matches(annotation)) {
157                 return ParamAnnotation.convert(annotation, param);
158             }
159             if (annotation.annotationType().isAssignableFrom(ConvertParam.class)) {
160                 Class<? extends ParamConverter<?>> converterClass = ((ConvertParam) annotation).value();
161                 String options = ((ConvertParam) annotation).options();
162                 try {
163                     return converterClass.newInstance().convert(param, options);
164                 } catch (ConversionFailedException e) {
165                     throw e;
166                 } catch (Exception e) {
167                     throw new RuntimeException("Your ParamConverter class must have a public no-arg constructor!", e);
168                 }
169             }
170         }
171         return param;
172     }
173 
174     @SuppressWarnings("unchecked")
castParameterDirectly(Object object, Class clazz)175     private Object castParameterDirectly(Object object, Class clazz) {
176         if (object == null || clazz.isInstance(object) || (!(object instanceof String) && clazz.isPrimitive()))
177             return object;
178         if (clazz.isEnum())
179             return (Enum.valueOf(clazz, (String) object));
180         if (clazz.isAssignableFrom(String.class))
181             return object.toString();
182         if (clazz.isAssignableFrom(Class.class))
183             try {
184                 return Class.forName((String) object);
185             } catch (ClassNotFoundException e) {
186                 throw new IllegalArgumentException("Parameter class (" + object + ") not found", e);
187             }
188         if (clazz.isAssignableFrom(Integer.TYPE) || clazz.isAssignableFrom(Integer.class))
189             return Integer.parseInt((String) object);
190         if (clazz.isAssignableFrom(Short.TYPE) || clazz.isAssignableFrom(Short.class))
191             return Short.parseShort((String) object);
192         if (clazz.isAssignableFrom(Long.TYPE) || clazz.isAssignableFrom(Long.class))
193             return Long.parseLong((String) object);
194         if (clazz.isAssignableFrom(Float.TYPE) || clazz.isAssignableFrom(Float.class))
195             return Float.parseFloat((String) object);
196         if (clazz.isAssignableFrom(Double.TYPE) || clazz.isAssignableFrom(Double.class))
197             return Double.parseDouble((String) object);
198         if (clazz.isAssignableFrom(Boolean.TYPE) || clazz.isAssignableFrom(Boolean.class))
199             return Boolean.parseBoolean((String) object);
200         if (clazz.isAssignableFrom(Character.TYPE) || clazz.isAssignableFrom(Character.class))
201             return object.toString().charAt(0);
202         if (clazz.isAssignableFrom(Byte.TYPE) || clazz.isAssignableFrom(Byte.class))
203             return Byte.parseByte((String) object);
204         if (clazz.isAssignableFrom(BigDecimal.class))
205             return new BigDecimal((String) object);
206         throw new IllegalArgumentException("Parameter type (" + clazz.getName() + ") cannot be handled!" +
207                 " Only primitive types, BigDecimals and Strings can be used.");
208     }
209 
verifySameSizeOfArrays(Object[] columns, Class<?>[] parameterTypes)210     private void verifySameSizeOfArrays(Object[] columns, Class<?>[] parameterTypes) {
211         if (parameterTypes.length != columns.length)
212             throw new IllegalArgumentException(
213                     "Number of parameters inside @Parameters annotation doesn't match the number of test method parameters.\nThere are "
214                             + columns.length + " parameters in annotation, while there's " + parameterTypes.length + " parameters in the "
215                             + testMethod.getName() + " method.");
216     }
217 
218     @Override
evaluate()219     public void evaluate() throws Throwable {
220         testMethod.invokeExplosively(testClass, params == null ? new Object[]{params} : params);
221     }
222 
223 }
224