diff --git a/README.md b/README.md
index 07c5bc1..6980823 100644
--- a/README.md
+++ b/README.md
@@ -119,11 +119,11 @@ class HueBase extends BaseStation {
The Annotations in the above example all have to follow additional placement rules that need to be checked by the AST-Transformation consuming them:
-- Field.members is only valid on Collections and Maps
-- Key and Owner annotations are only valid on classes annotated with @DSL
-- @Key is only valid on String fields
-- @Owner is only valid on fields or on single argument methods
-- @DSL.stripSuffix is only valid on non-final classes
+- `@Field.members` is only valid on Collections and Maps
+- `@Key` and `@Owner` annotations are only valid on classes annotated with `@DSL`
+- `@Key` is only valid on String fields
+- `@Owner` is only valid on fields or on single argument methods
+- `@DSL.stripSuffix` is only valid on non-final classes
# Usage
@@ -285,8 +285,26 @@ class NameMustMatchCheck extends KlumCastCheck {
}
}
```
+### Filtering Checks
-Additionally, the method `isValidFor(AnnotatedNode target)` can be overridden quickly skip the check if necessary.
+Filters can be set to determine that a check is only valid in certain condition. This can be done in three ways:
+
+#### KlumCastCheck.isValidFor(AnnotatedNode target)
+By overriding the `isValidFor(AnnotatedNode target)` method, custom checks can be implemented directly in the check implementation.
+
+### KlumCastValidator.validFor()
+
+The KlumCastValidator Annotation has a member `validFor` of type `ElementType[]`. Only if the annotated target is one of the listed types here, the Check is executed.
+
+### @Filter annotation-members on annotations
+
+By annotation a member of an annotation with `@Filter`, that member becomes a filter for its annotation chain. Filter members can either be
+
+- An `ElementType[]`, in which case the filter behaves exactly as `KlumCastValidator.validFor`
+- A Class object containing a subclass of `Filter.Function` which acts as a custom filter
+- A String containing the fully qualified Class-name of the filter implementation
+
+Note that in order for a check to be executed, all checks of the annotation chain must match.
## Check as inner class
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/Filter.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/Filter.java
index 5f0f633..3166de9 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/Filter.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/Filter.java
@@ -14,17 +14,54 @@
* The filter can be of the following types:
*
* - {@link ElementType[]}: The check is executed if the annotated element is one of the given types
- * - {@link Class>}: a filter implementation
+ * - {@link Filter.Function}: the type of a filter implementation
* - {@link String}: The fully qualified classname of the filter implementation
*
*
+ * In order for a check to be executed, all Filter checks must pass.
*/
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Filter {
- @FunctionalInterface
- interface Function {
- boolean isValidFor(AnnotatedNode target);
+ abstract class Function {
+ protected Filter annotation;
+
+ public abstract boolean isValidFor(AnnotatedNode target);
+
+ public void setAnnotation(Filter annotation) {
+ this.annotation = annotation;
+ }
+ }
+
+ /** Default filter that always matches */
+ class All extends Function {
+ @Override
+ public boolean isValidFor(AnnotatedNode target) {
+ return true;
+ }
+ }
+
+ /** Filter that matches if the annotated element is a method */
+ class Methods extends Function {
+ @Override
+ public boolean isValidFor(AnnotatedNode target) {
+ return target instanceof org.codehaus.groovy.ast.MethodNode;
+ }
+ }
+
+ /** Filter that matches if the annotated element is a field */
+ class Fields extends Function {
+ @Override
+ public boolean isValidFor(AnnotatedNode target) {
+ return target instanceof org.codehaus.groovy.ast.FieldNode;
+ }
+ }
+ /** Filter that matches if the annotated element is a Class */
+ class Classes extends Function {
+ @Override
+ public boolean isValidFor(AnnotatedNode target) {
+ return target instanceof org.codehaus.groovy.ast.ClassNode;
+ }
}
}
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/KlumCastValidator.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/KlumCastValidator.java
index 1e547cb..a2936c1 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/KlumCastValidator.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/KlumCastValidator.java
@@ -30,6 +30,9 @@
/**
* Meta-Annotation that defines the validator to validate the usage of the annotated annotation.
* Either value or type must be set, but not both.
+ *
+ * The validFor parameter can be used to restrict the usage of the validator to certain elements.
+ *
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@@ -53,6 +56,12 @@
*/
String[] parameters() default {};
+ /**
+ * The elements the validator is valid for.
+ * @return the elements the validator is valid for.
+ */
+ @Filter Class extends Filter.Function> validFor() default Filter.All.class;
+
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface List {
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/MustBeStatic.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/MustBeStatic.java
index 2300303..668e9a9 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/MustBeStatic.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/MustBeStatic.java
@@ -23,6 +23,7 @@
*/
package com.blackbuild.klum.cast.checks;
+import com.blackbuild.klum.cast.Filter;
import com.blackbuild.klum.cast.KlumCastValidator;
import com.blackbuild.klum.cast.checks.impl.KlumCastCheck;
import org.codehaus.groovy.ast.AnnotatedNode;
@@ -40,9 +41,8 @@
*/
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
-@KlumCastValidator(type = MustBeStatic.Check.class)
+@KlumCastValidator(type = MustBeStatic.Check.class, validFor = Filter.Methods.class)
public @interface MustBeStatic {
-
class Check extends KlumCastCheck {
@Override
@@ -50,11 +50,6 @@ protected void doCheck(AnnotationNode annotationToCheck, AnnotatedNode target) {
if (!((MethodNode) target).isStatic())
throw new IllegalStateException("Annotation " + annotationToCheck.getClassNode().getName() + " must be placed on a static method.");
}
-
- @Override
- protected boolean isValidFor(AnnotatedNode target) {
- return target instanceof MethodNode;
- }
}
}
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsOneOf.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsOneOf.java
index 06e61d2..94cb25a 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsOneOf.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsOneOf.java
@@ -23,6 +23,7 @@
*/
package com.blackbuild.klum.cast.checks;
+import com.blackbuild.klum.cast.Filter;
import com.blackbuild.klum.cast.KlumCastValidator;
import java.lang.annotation.*;
@@ -44,7 +45,7 @@
/**
* If set, the annotation is only checked when placed on one of the given types.
*/
- ElementType[] whenOn() default {};
+ @Filter ElementType[] whenOn() default {};
/**
* If set to true, exactly one of the given members must be set. If set to false, more than one member can be set.
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsReturnType.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsReturnType.java
index b3ca61e..7209ef9 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsReturnType.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NeedsReturnType.java
@@ -23,6 +23,7 @@
*/
package com.blackbuild.klum.cast.checks;
+import com.blackbuild.klum.cast.Filter;
import com.blackbuild.klum.cast.KlumCastValidator;
import java.lang.annotation.ElementType;
@@ -36,7 +37,7 @@
*/
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
-@KlumCastValidator("com.blackbuild.klum.cast.checks.impl.NeedsReturnTypeCheck")
+@KlumCastValidator(value = "com.blackbuild.klum.cast.checks.impl.NeedsReturnTypeCheck", validFor = Filter.Methods.class)
public @interface NeedsReturnType {
Class>[] value();
}
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NumberOfParameters.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NumberOfParameters.java
index 0e4014e..bcf79da 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NumberOfParameters.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/NumberOfParameters.java
@@ -23,6 +23,7 @@
*/
package com.blackbuild.klum.cast.checks;
+import com.blackbuild.klum.cast.Filter;
import com.blackbuild.klum.cast.KlumCastValidator;
import com.blackbuild.klum.cast.checks.impl.KlumCastCheck;
import org.codehaus.groovy.ast.AnnotatedNode;
@@ -39,7 +40,7 @@
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
-@KlumCastValidator(type = NumberOfParameters.Check.class)
+@KlumCastValidator(type = NumberOfParameters.Check.class, validFor = Filter.Methods.class)
public @interface NumberOfParameters {
/**
* The number of parameters the annotated method must have.
@@ -51,11 +52,9 @@ class Check extends KlumCastCheck {
@Override
protected void doCheck(AnnotationNode annotationToCheck, AnnotatedNode target) {
- if (target instanceof MethodNode) {
- MethodNode methodNode = (MethodNode) target;
- if (methodNode.getParameters().length != controlAnnotation.value())
- throw new RuntimeException("Method " + methodNode.getName() + " must have " + controlAnnotation.value() + " parameters.");
- }
+ MethodNode methodNode = (MethodNode) target;
+ if (methodNode.getParameters().length != controlAnnotation.value())
+ throw new RuntimeException("Method " + methodNode.getName() + " must have " + controlAnnotation.value() + " parameters.");
}
}
}
diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/ParameterTypes.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/ParameterTypes.java
index cb57b97..dd91ab8 100644
--- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/ParameterTypes.java
+++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/ParameterTypes.java
@@ -23,6 +23,7 @@
*/
package com.blackbuild.klum.cast.checks;
+import com.blackbuild.klum.cast.Filter;
import com.blackbuild.klum.cast.KlumCastValidator;
import java.lang.annotation.ElementType;
@@ -37,7 +38,7 @@
*/
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
-@KlumCastValidator("com.blackbuild.klum.cast.checks.impl.ParameterTypesCheck")
+@KlumCastValidator(value = "com.blackbuild.klum.cast.checks.impl.ParameterTypesCheck", validFor = Filter.Methods.class)
public @interface ParameterTypes {
Class>[] value();
boolean strict() default false;
diff --git a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsOneOfCheck.java b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsOneOfCheck.java
index e0f3a0d..324bf02 100644
--- a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsOneOfCheck.java
+++ b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsOneOfCheck.java
@@ -24,7 +24,6 @@
package com.blackbuild.klum.cast.checks.impl;
import com.blackbuild.klum.cast.checks.NeedsOneOf;
-import com.blackbuild.klum.cast.validation.AstSupport;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
@@ -41,9 +40,4 @@ protected void doCheck(AnnotationNode annotationToCheck, AnnotatedNode target) {
if (!controlAnnotation.exclusive() && matchingMembers.isEmpty())
throw new RuntimeException("At least one of " + Arrays.asList(controlAnnotation.value()) + " must be set");
}
-
- @Override
- protected boolean isValidFor(AnnotatedNode target) {
- return AstSupport.matchesOneOf(controlAnnotation.whenOn(), target);
- }
}
diff --git a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsReturnTypeCheck.java b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsReturnTypeCheck.java
index 99a0221..8b3c75d 100644
--- a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsReturnTypeCheck.java
+++ b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/NeedsReturnTypeCheck.java
@@ -37,9 +37,4 @@ protected void doCheck(AnnotationNode annotationToCheck, AnnotatedNode target) {
if (Arrays.stream(controlAnnotation.value()).map(ClassHelper::make).noneMatch(r -> AstSupport.isAssignable(actualReturnType, r)))
throw new IllegalStateException("Method " + ((MethodNode) target).getName() + " must return one of " + Arrays.toString(controlAnnotation.value()) + ".");
}
-
- @Override
- protected boolean isValidFor(AnnotatedNode target) {
- return target instanceof MethodNode;
- }
}
diff --git a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/ParameterTypesCheck.java b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/ParameterTypesCheck.java
index 432fa8a..3e7325d 100644
--- a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/ParameterTypesCheck.java
+++ b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/checks/impl/ParameterTypesCheck.java
@@ -58,10 +58,4 @@ protected void doCheck(AnnotationNode annotationToCheck, AnnotatedNode target) {
));
}
}
-
- @Override
- protected boolean isValidFor(AnnotatedNode target) {
- return target instanceof MethodNode;
- }
-
}
diff --git a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/FilterHandler.java b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/FilterHandler.java
index 6c25481..d0fd585 100644
--- a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/FilterHandler.java
+++ b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/FilterHandler.java
@@ -8,8 +8,11 @@
import java.lang.annotation.ElementType;
import java.lang.reflect.InvocationTargetException;
+/** Varios methods for handling filters. */
public class FilterHandler {
+ private FilterHandler() {}
+
/**
* Determines if the given annotation is valid for the given target. This is done by checking all
* members on the target annotated with {@link Filter}. The annotation is only valid if all filters
@@ -25,7 +28,7 @@ public static boolean isValidFor(Annotation annotation, AnnotatedNode target) {
.allMatch(f -> f.isValidFor(target));
}
- public static Filter.Function createFrom(Object memberValue, AnnotatedNode target) {
+ private static Filter.Function createFrom(Object memberValue, AnnotatedNode target) {
if (memberValue instanceof Class) {
Class> filterClass = (Class>) memberValue;
if (Filter.Function.class.isAssignableFrom(filterClass)) {
@@ -65,10 +68,10 @@ private static Filter.Function doCreateFrom(Class> filterClass) {
}
}
- public static class ElementTypeFilter implements Filter.Function {
+ static class ElementTypeFilter extends Filter.Function {
private final ElementType[] elementTypes;
- public ElementTypeFilter(ElementType[] elementTypes) {
+ ElementTypeFilter(ElementType[] elementTypes) {
this.elementTypes = elementTypes;
}
diff --git a/klum-cast-compile/src/test/groovy/com/blackbuild/klum/cast/checks/MustBeStaticTest.groovy b/klum-cast-compile/src/test/groovy/com/blackbuild/klum/cast/checks/MustBeStaticTest.groovy
index c3923d9..9de7d19 100644
--- a/klum-cast-compile/src/test/groovy/com/blackbuild/klum/cast/checks/MustBeStaticTest.groovy
+++ b/klum-cast-compile/src/test/groovy/com/blackbuild/klum/cast/checks/MustBeStaticTest.groovy
@@ -28,7 +28,8 @@ import org.codehaus.groovy.control.MultipleCompilationErrorsException
class MustBeStaticTest extends AstSpec {
- def "Static method works"() {
+ @Override
+ def setup() {
given:
createClass '''
@Target([ElementType.METHOD, ElementType.TYPE])
@@ -37,6 +38,9 @@ class MustBeStaticTest extends AstSpec {
@MustBeStatic
@interface MyAnnotation {}
'''
+ }
+
+ def "Static method works"() {
when:
createClass '''
class MyClass {
@@ -57,4 +61,16 @@ class MyClass {
then:
thrown(MultipleCompilationErrorsException)
}
+
+ def "mustBeStatic ignores classes"() {
+ when:
+ createClass '''
+@MyAnnotation
+class MyClass {
+ static List myMethod() { null }
+ }'''
+
+ then:
+ notThrown(MultipleCompilationErrorsException)
+ }
}