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 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 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 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 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 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 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 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 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 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 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 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 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") 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 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 219 public void evaluate() throws Throwable { 220 testMethod.invokeExplosively(testClass, params == null ? new Object[]{params} : params); 221 } 222 223 } 224