diff options
Diffstat (limited to 'src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java')
-rw-r--r-- | src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java | 344 |
1 files changed, 266 insertions, 78 deletions
diff --git a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java index 23f921b..36de4f1 100644 --- a/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java +++ b/src/main/java/org/junit/internal/runners/rules/RuleMemberValidator.java @@ -1,91 +1,279 @@ package org.junit.internal.runners.rules; -import java.lang.annotation.Annotation; -import java.util.List; import org.junit.ClassRule; import org.junit.Rule; +import org.junit.rules.MethodRule; import org.junit.rules.TestRule; -import org.junit.runners.model.FrameworkField; +import org.junit.runners.model.FrameworkMember; import org.junit.runners.model.TestClass; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + /** - * A RuleFieldValidator validates the rule fields of a - * {@link TestClass}. All reasons for rejecting the + * A RuleMemberValidator validates the rule fields/methods of a + * {@link org.junit.runners.model.TestClass}. All reasons for rejecting the * {@code TestClass} are written to a list of errors. * - * There are two slightly different validators. The {@link #CLASS_RULE_VALIDATOR} + * <p>There are four slightly different validators. The {@link #CLASS_RULE_VALIDATOR} * validates fields with a {@link ClassRule} annotation and the - * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation. + * {@link #RULE_VALIDATOR} validates fields with a {@link Rule} annotation.</p> + * + * <p>The {@link #CLASS_RULE_METHOD_VALIDATOR} + * validates methods with a {@link ClassRule} annotation and the + * {@link #RULE_METHOD_VALIDATOR} validates methods with a {@link Rule} annotation.</p> */ -public enum RuleMemberValidator { - /** - * Validates fields with a {@link ClassRule} annotation. - */ - CLASS_RULE_VALIDATOR(ClassRule.class, true), - /** - * Validates fields with a {@link Rule} annotation. - */ - RULE_VALIDATOR(Rule.class, false); - - private final Class<? extends Annotation> fAnnotation; - - private final boolean fOnlyStaticFields; - - private RuleMemberValidator(Class<? extends Annotation> annotation, - boolean onlyStaticFields) { - this.fAnnotation= annotation; - this.fOnlyStaticFields= onlyStaticFields; - } - - /** - * Validate the {@link TestClass} and adds reasons - * for rejecting the class to a list of errors. - * @param target the {@code TestClass} to validate. - * @param errors the list of errors. - */ - public void validate(TestClass target, List<Throwable> errors) { - List<FrameworkField> fields= target.getAnnotatedFields(fAnnotation); - for (FrameworkField each : fields) - validateField(each, errors); - } - - private void validateField(FrameworkField field, List<Throwable> errors) { - optionallyValidateStatic(field, errors); - validatePublic(field, errors); - validateTestRuleOrMethodRule(field, errors); - } - - private void optionallyValidateStatic(FrameworkField field, - List<Throwable> errors) { - if (fOnlyStaticFields && !field.isStatic()) - addError(errors, field, "must be static."); - } - - private void validatePublic(FrameworkField field, List<Throwable> errors) { - if (!field.isPublic()) - addError(errors, field, "must be public."); - } - - private void validateTestRuleOrMethodRule(FrameworkField field, - List<Throwable> errors) { - if (!isMethodRule(field) && !isTestRule(field)) - addError(errors, field, "must implement MethodRule or TestRule."); - } - - private boolean isTestRule(FrameworkField target) { - return TestRule.class.isAssignableFrom(target.getType()); - } - - @SuppressWarnings("deprecation") - private boolean isMethodRule(FrameworkField target) { - return org.junit.rules.MethodRule.class.isAssignableFrom(target - .getType()); - } - - private void addError(List<Throwable> errors, FrameworkField field, - String suffix) { - String message= "The @" + fAnnotation.getSimpleName() + " '" - + field.getName() + "' " + suffix; - errors.add(new Exception(message)); - } +public class RuleMemberValidator { + /** + * Validates fields with a {@link ClassRule} annotation. + */ + public static final RuleMemberValidator CLASS_RULE_VALIDATOR = + classRuleValidatorBuilder() + .withValidator(new DeclaringClassMustBePublic()) + .withValidator(new MemberMustBeStatic()) + .withValidator(new MemberMustBePublic()) + .withValidator(new FieldMustBeATestRule()) + .build(); + /** + * Validates fields with a {@link Rule} annotation. + */ + public static final RuleMemberValidator RULE_VALIDATOR = + testRuleValidatorBuilder() + .withValidator(new MemberMustBeNonStaticOrAlsoClassRule()) + .withValidator(new MemberMustBePublic()) + .withValidator(new FieldMustBeARule()) + .build(); + /** + * Validates methods with a {@link ClassRule} annotation. + */ + public static final RuleMemberValidator CLASS_RULE_METHOD_VALIDATOR = + classRuleValidatorBuilder() + .forMethods() + .withValidator(new DeclaringClassMustBePublic()) + .withValidator(new MemberMustBeStatic()) + .withValidator(new MemberMustBePublic()) + .withValidator(new MethodMustBeATestRule()) + .build(); + + /** + * Validates methods with a {@link Rule} annotation. + */ + public static final RuleMemberValidator RULE_METHOD_VALIDATOR = + testRuleValidatorBuilder() + .forMethods() + .withValidator(new MemberMustBeNonStaticOrAlsoClassRule()) + .withValidator(new MemberMustBePublic()) + .withValidator(new MethodMustBeARule()) + .build(); + + private final Class<? extends Annotation> annotation; + private final boolean methods; + private final List<RuleValidator> validatorStrategies; + + RuleMemberValidator(Builder builder) { + this.annotation = builder.annotation; + this.methods = builder.methods; + this.validatorStrategies = builder.validators; + } + + /** + * Validate the {@link org.junit.runners.model.TestClass} and adds reasons + * for rejecting the class to a list of errors. + * + * @param target the {@code TestClass} to validate. + * @param errors the list of errors. + */ + public void validate(TestClass target, List<Throwable> errors) { + List<? extends FrameworkMember<?>> members = methods ? target.getAnnotatedMethods(annotation) + : target.getAnnotatedFields(annotation); + + for (FrameworkMember<?> each : members) { + validateMember(each, errors); + } + } + + private void validateMember(FrameworkMember<?> member, List<Throwable> errors) { + for (RuleValidator strategy : validatorStrategies) { + strategy.validate(member, annotation, errors); + } + } + + private static Builder classRuleValidatorBuilder() { + return new Builder(ClassRule.class); + } + + private static Builder testRuleValidatorBuilder() { + return new Builder(Rule.class); + } + + private static class Builder { + private final Class<? extends Annotation> annotation; + private boolean methods; + private final List<RuleValidator> validators; + + private Builder(Class<? extends Annotation> annotation) { + this.annotation = annotation; + this.methods = false; + this.validators = new ArrayList<RuleValidator>(); + } + + Builder forMethods() { + methods = true; + return this; + } + + Builder withValidator(RuleValidator validator) { + validators.add(validator); + return this; + } + + RuleMemberValidator build() { + return new RuleMemberValidator(this); + } + } + + private static boolean isRuleType(FrameworkMember<?> member) { + return isMethodRule(member) || isTestRule(member); + } + + private static boolean isTestRule(FrameworkMember<?> member) { + return TestRule.class.isAssignableFrom(member.getType()); + } + + private static boolean isMethodRule(FrameworkMember<?> member) { + return MethodRule.class.isAssignableFrom(member.getType()); + } + + /** + * Encapsulates a single piece of validation logic, used to determine if {@link org.junit.Rule} and + * {@link org.junit.ClassRule} annotations have been used correctly + */ + interface RuleValidator { + /** + * Examine the given member and add any violations of the strategy's validation logic to the given list of errors + * @param member The member (field or member) to examine + * @param annotation The type of rule annotation on the member + * @param errors The list of errors to add validation violations to + */ + void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors); + } + + /** + * Requires the validated member to be non-static + */ + private static final class MemberMustBeNonStaticOrAlsoClassRule implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + boolean isMethodRuleMember = isMethodRule(member); + boolean isClassRuleAnnotated = (member.getAnnotation(ClassRule.class) != null); + + // We disallow: + // - static MethodRule members + // - static @Rule annotated members + // - UNLESS they're also @ClassRule annotated + // Note that MethodRule cannot be annotated with @ClassRule + if (member.isStatic() && (isMethodRuleMember || !isClassRuleAnnotated)) { + String message; + if (isMethodRule(member)) { + message = "must not be static."; + } else { + message = "must not be static or it must be annotated with @ClassRule."; + } + errors.add(new ValidationError(member, annotation, message)); + } + } + } + + /** + * Requires the member to be static + */ + private static final class MemberMustBeStatic implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!member.isStatic()) { + errors.add(new ValidationError(member, annotation, + "must be static.")); + } + } + } + + /** + * Requires the member's declaring class to be public + */ + private static final class DeclaringClassMustBePublic implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isDeclaringClassPublic(member)) { + errors.add(new ValidationError(member, annotation, + "must be declared in a public class.")); + } + } + + private boolean isDeclaringClassPublic(FrameworkMember<?> member) { + return Modifier.isPublic(member.getDeclaringClass().getModifiers()); + } + } + + /** + * Requires the member to be public + */ + private static final class MemberMustBePublic implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!member.isPublic()) { + errors.add(new ValidationError(member, annotation, + "must be public.")); + } + } + } + + /** + * Requires the member is a field implementing {@link org.junit.rules.MethodRule} or {@link org.junit.rules.TestRule} + */ + private static final class FieldMustBeARule implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isRuleType(member)) { + errors.add(new ValidationError(member, annotation, + "must implement MethodRule or TestRule.")); + } + } + } + + /** + * Require the member to return an implementation of {@link org.junit.rules.MethodRule} or + * {@link org.junit.rules.TestRule} + */ + private static final class MethodMustBeARule implements RuleValidator { + public void validate(FrameworkMember<?> member, Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isRuleType(member)) { + errors.add(new ValidationError(member, annotation, + "must return an implementation of MethodRule or TestRule.")); + } + } + } + + /** + * Require the member to return an implementation of {@link org.junit.rules.TestRule} + */ + private static final class MethodMustBeATestRule implements RuleValidator { + public void validate(FrameworkMember<?> member, + Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isTestRule(member)) { + errors.add(new ValidationError(member, annotation, + "must return an implementation of TestRule.")); + } + } + } + + /** + * Requires the member is a field implementing {@link org.junit.rules.TestRule} + */ + private static final class FieldMustBeATestRule implements RuleValidator { + + public void validate(FrameworkMember<?> member, + Class<? extends Annotation> annotation, List<Throwable> errors) { + if (!isTestRule(member)) { + errors.add(new ValidationError(member, annotation, + "must implement TestRule.")); + } + } + } } |