Home | History | Annotate | Download | only in internal
      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