    001    package org.junit.runners.parameterized;
    002
    003    import java.lang.annotation.Annotation;
    004    import java.lang.reflect.Field;
    005    import java.util.List;
    006
    007    import org.junit.internal.runners.statements.RunAfters;
    008    import org.junit.internal.runners.statements.RunBefores;
    009    import org.junit.runner.RunWith;
    010    import org.junit.runner.notification.RunNotifier;
    011    import org.junit.runners.BlockJUnit4ClassRunner;
    012    import org.junit.runners.Parameterized;
    013    import org.junit.runners.Parameterized.Parameter;
    014    import org.junit.runners.model.FrameworkField;
    015    import org.junit.runners.model.FrameworkMethod;
    016    import org.junit.runners.model.InitializationError;
    017    import org.junit.runners.model.Statement;
    018
    019    /**
    020     * A {@link BlockJUnit4ClassRunner} with parameters support. Parameters can be
    021     * injected via constructor or into annotated fields.
    022     */
    023    public class BlockJUnit4ClassRunnerWithParameters extends
    024            BlockJUnit4ClassRunner {
    025        private enum InjectionType {
    026            CONSTRUCTOR, FIELD
    027        }
    028
    029        private final Object[] parameters;
    030
    031        private final String name;
    032
    033        public BlockJUnit4ClassRunnerWithParameters(TestWithParameters test)
    034                throws InitializationError {
    035            super(test.getTestClass());
    036            parameters = test.getParameters().toArray(
    037                    new Object[test.getParameters().size()]);
    038            name = test.getName();
    039        }
    040
    041        @Override
    042        public Object createTest() throws Exception {
    043            InjectionType injectionType = getInjectionType();
    044            switch (injectionType) {
    045                case CONSTRUCTOR:
    046                    return createTestUsingConstructorInjection();
    047                case FIELD:
    048                    return createTestUsingFieldInjection();
    049                default:
    050                    throw new IllegalStateException("The injection type "
    051                            + injectionType + " is not supported.");
    052            }
    053        }
    054
    055        private Object createTestUsingConstructorInjection() throws Exception {
    056            return getTestClass().getOnlyConstructor().newInstance(parameters);
    057        }
    058
    059        private Object createTestUsingFieldInjection() throws Exception {
    060            List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
    061            if (annotatedFieldsByParameter.size() != parameters.length) {
    062                throw new Exception(
    063                        "Wrong number of parameters and @Parameter fields."
    064                                + " @Parameter fields counted: "
    065                                + annotatedFieldsByParameter.size()
    066                                + ", available parameters: " + parameters.length
    067                                + ".");
    068            }
    069            Object testClassInstance = getTestClass().getJavaClass().newInstance();
    070            for (FrameworkField each : annotatedFieldsByParameter) {
    071                Field field = each.getField();
    072                Parameter annotation = field.getAnnotation(Parameter.class);
    073                int index = annotation.value();
    074                try {
    075                    field.set(testClassInstance, parameters[index]);
    076                } catch (IllegalAccessException e) {
    077                    IllegalAccessException wrappedException = new IllegalAccessException(
    078                            "Cannot set parameter '" + field.getName()
    079                                    + "'. Ensure that the field '" + field.getName()
    080                                    + "' is public.");
    081                    wrappedException.initCause(e);
    082                    throw wrappedException;
    083                } catch (IllegalArgumentException iare) {
    084                    throw new Exception(getTestClass().getName()
    085                            + ": Trying to set " + field.getName()
    086                            + " with the value " + parameters[index]
    087                            + " that is not the right type ("
    088                            + parameters[index].getClass().getSimpleName()
    089                            + " instead of " + field.getType().getSimpleName()
    090                            + ").", iare);
    091                }
    092            }
    093            return testClassInstance;
    094        }
    095
    096        @Override
    097        protected String getName() {
    098            return name;
    099        }
    100
    101        @Override
    102        protected String testName(FrameworkMethod method) {
    103            return method.getName() + getName();
    104        }
    105
    106        @Override
    107        protected void validateConstructor(List<Throwable> errors) {
    108            validateOnlyOneConstructor(errors);
    109            if (getInjectionType() != InjectionType.CONSTRUCTOR) {
    110                validateZeroArgConstructor(errors);
    111            }
    112        }
    113
    114        @Override
    115        protected void validateFields(List<Throwable> errors) {
    116            super.validateFields(errors);
    117            if (getInjectionType() == InjectionType.FIELD) {
    118                List<FrameworkField> annotatedFieldsByParameter = getAnnotatedFieldsByParameter();
    119                int[] usedIndices = new int[annotatedFieldsByParameter.size()];
    120                for (FrameworkField each : annotatedFieldsByParameter) {
    121                    int index = each.getField().getAnnotation(Parameter.class)
    122                            .value();
    123                    if (index < 0 || index > annotatedFieldsByParameter.size() - 1) {
    124                        errors.add(new Exception("Invalid @Parameter value: "
    125                                + index + ". @Parameter fields counted: "
    126                                + annotatedFieldsByParameter.size()
    127                                + ". Please use an index between 0 and "
    128                                + (annotatedFieldsByParameter.size() - 1) + "."));
    129                    } else {
    130                        usedIndices[index]++;
    131                    }
    132                }
    133                for (int index = 0; index < usedIndices.length; index++) {
    134                    int numberOfUse = usedIndices[index];
    135                    if (numberOfUse == 0) {
    136                        errors.add(new Exception("@Parameter(" + index
    137                                + ") is never used."));
    138                    } else if (numberOfUse > 1) {
    139                        errors.add(new Exception("@Parameter(" + index
    140                                + ") is used more than once (" + numberOfUse + ")."));
    141                    }
    142                }
    143            }
    144        }
    145
    146        @Override
    147        protected Statement classBlock(RunNotifier notifier) {
    148            Statement statement = childrenInvoker(notifier);
    149            statement = withBeforeParams(statement);
    150            statement = withAfterParams(statement);
    151            return statement;
    152        }
    153
    154        private Statement withBeforeParams(Statement statement) {
    155            List<FrameworkMethod> befores = getTestClass()
    156                    .getAnnotatedMethods(Parameterized.BeforeParam.class);
    157            return befores.isEmpty() ? statement : new RunBeforeParams(statement, befores);
    158        }
    159
    160        private class RunBeforeParams extends RunBefores {
    161            RunBeforeParams(Statement next, List<FrameworkMethod> befores) {
    162                super(next, befores, null);
    163            }
    164
    165            @Override
    166            protected void invokeMethod(FrameworkMethod method) throws Throwable {
    167                int paramCount = method.getMethod().getParameterTypes().length;
    168                method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters);
    169            }
    170        }
    171
    172        private Statement withAfterParams(Statement statement) {
    173            List<FrameworkMethod> afters = getTestClass()
    174                    .getAnnotatedMethods(Parameterized.AfterParam.class);
    175            return afters.isEmpty() ? statement : new RunAfterParams(statement, afters);
    176        }
    177
    178        private class RunAfterParams extends RunAfters {
    179            RunAfterParams(Statement next, List<FrameworkMethod> afters) {
    180                super(next, afters, null);
    181            }
    182
    183            @Override
    184            protected void invokeMethod(FrameworkMethod method) throws Throwable {
    185                int paramCount = method.getMethod().getParameterTypes().length;
    186                method.invokeExplosively(null, paramCount == 0 ? (Object[]) null : parameters);
    187            }
    188        }
    189
    190        @Override
    191        protected Annotation[] getRunnerAnnotations() {
    192            Annotation[] allAnnotations = super.getRunnerAnnotations();
    193            Annotation[] annotationsWithoutRunWith = new Annotation[allAnnotations.length - 1];
    194            int i = 0;
    195            for (Annotation annotation: allAnnotations) {
    196                if (!annotation.annotationType().equals(RunWith.class)) {
    197                    annotationsWithoutRunWith[i] = annotation;
    198                    ++i;
    199                }
    200            }
    201            return annotationsWithoutRunWith;
    202        }
    203
    204        private List<FrameworkField> getAnnotatedFieldsByParameter() {
    205            return getTestClass().getAnnotatedFields(Parameter.class);
    206        }
    207
    208        private InjectionType getInjectionType() {
    209            if (fieldsAreAnnotated()) {
    210                return InjectionType.FIELD;
    211            } else {
    212                return InjectionType.CONSTRUCTOR;
    213            }
    214        }
    215
    216        private boolean fieldsAreAnnotated() {
    217            return !getAnnotatedFieldsByParameter().isEmpty();
    218        }
    219    }