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