package org.junit.internal.runners.rules;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.rules.MethodRule;
import org.junit.rules.TestRule;
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 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.
*
* <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.</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 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."));
}
}
}
}