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