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 new file mode 100644 index 0000000..88671f3 --- /dev/null +++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/Filter.java @@ -0,0 +1,89 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Stephan Pauxberger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.blackbuild.klum.cast; + +import org.codehaus.groovy.ast.AnnotatedNode; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Designates a member as a filter. Filters are used to filter the list of possible targets for a + * check. The check is only performed if the filter method returns true. Usually, this will be done using + * a default method. + *

The filter can be of the following types:

+ * + * + *

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 { + + 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 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-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/AnnotationHelper.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/AnnotationHelper.java new file mode 100644 index 0000000..85f6d9d --- /dev/null +++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/AnnotationHelper.java @@ -0,0 +1,58 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Stephan Pauxberger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.blackbuild.klum.cast.checks.impl; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class AnnotationHelper { + private AnnotationHelper() {} + + + public static Stream getValuesOfMembersAnnotatedWith(Annotation annotation, Class memberAnnotationType) { + return getValuesOfMembersAnnotatedWith(annotation, memberAnnotationType, it -> true); + } + + public static Stream getValuesOfMembersAnnotatedWith(Annotation annotation, Class memberAnnotationType, Predicate filter) { + return Stream.of(annotation.annotationType().getDeclaredMethods()) + .filter(m -> hasMatchingAnnotation(m, memberAnnotationType, filter)) + .map(m -> invokeMemberMethod(annotation, m)); + } + + private static boolean hasMatchingAnnotation(Method member, Class annotationType, Predicate filter) { + T memberAnnotation = member.getAnnotation(annotationType); + return memberAnnotation != null && filter.test(memberAnnotation); + } + + private static Object invokeMemberMethod(Annotation annotation, Method m) { + try { + return m.invoke(annotation); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + +} diff --git a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/KlumCastCheck.java b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/KlumCastCheck.java index 862a6b3..036243b 100644 --- a/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/KlumCastCheck.java +++ b/klum-cast-annotations/src/main/java/com/blackbuild/klum/cast/checks/impl/KlumCastCheck.java @@ -31,6 +31,7 @@ import org.jetbrains.annotations.Nullable; import java.lang.annotation.Annotation; +import java.util.List; import java.util.Optional; /** @@ -48,13 +49,12 @@ public abstract class KlumCastCheck { @Nullable protected T controlAnnotation; @Nullable protected String memberName; @NotNull protected KlumCastValidator klumCastValidator; + @NotNull protected List annotationStack; - public void setControlAnnotation(@Nullable T controlAnnotation) { - this.controlAnnotation = controlAnnotation; - } - - public void setKlumCastValidatorAnnotation(@NotNull KlumCastValidator validatorAnnotation) { - this.klumCastValidator = validatorAnnotation; + public void setAnnotationStack(List annotationStack) { + this.annotationStack = annotationStack.subList(0, annotationStack.size() - 1); + klumCastValidator = (KlumCastValidator) annotationStack.get(annotationStack.size() - 1); + controlAnnotation = annotationStack.size() > 1 ? annotationStack.get(annotationStack.size() - 2) : null; } public void setMemberName(@Nullable String memberName) { 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 new file mode 100644 index 0000000..3b6a637 --- /dev/null +++ b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/FilterHandler.java @@ -0,0 +1,107 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2023 Stephan Pauxberger + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.blackbuild.klum.cast.validation; + +import com.blackbuild.klum.cast.Filter; +import com.blackbuild.klum.cast.checks.impl.AnnotationHelper; +import org.codehaus.groovy.ast.AnnotatedNode; + +import java.lang.annotation.Annotation; +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 + * match. + * + * @param annotation the annotation to check + * @param target the target node to validate against + * @return {@code true} if the annotation is valid for the target, {@code false} otherwise + */ + public static boolean isValidFor(Annotation annotation, AnnotatedNode target) { + return AnnotationHelper.getValuesOfMembersAnnotatedWith(annotation, Filter.class) + .map(v -> createFrom(v, target)) + .allMatch(f -> f.isValidFor(target)); + } + + private static Filter.Function createFrom(Object memberValue, AnnotatedNode target) { + if (memberValue instanceof Class) { + Class filterClass = (Class) memberValue; + if (Filter.Function.class.isAssignableFrom(filterClass)) { + return doCreateFrom(filterClass); + } else { + throw new IllegalStateException("Filter class " + filterClass.getName() + " does not implement " + Filter.Function.class.getName()); + } + } else if (memberValue instanceof String) { + return doCreateFrom((String) memberValue, target); + } else if (memberValue instanceof ElementType[]) { + return doCreateFrom((ElementType[]) memberValue); + } else { + throw new IllegalStateException("Filter value must be a class, a string or an array of ElementType"); + } + } + + private static Filter.Function doCreateFrom(ElementType[] memberValue) { + return new ElementTypeFilter(memberValue); + } + + private static Filter.Function doCreateFrom(String memberValue, AnnotatedNode target) { + try { + return doCreateFrom(Class.forName(memberValue, true, AstSupport.getTargetClassLoader(target))); + } catch (ClassNotFoundException e) { + throw new IllegalStateException("Could not load filter class " + memberValue, e); + } + } + + private static Filter.Function doCreateFrom(Class filterClass) { + if (!Filter.Function.class.isAssignableFrom(filterClass)) + throw new IllegalStateException("Filter class " + filterClass.getName() + " does not implement " + Filter.Function.class.getName()); + + try { + return (Filter.Function) filterClass.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + static class ElementTypeFilter extends Filter.Function { + private final ElementType[] elementTypes; + + ElementTypeFilter(ElementType[] elementTypes) { + this.elementTypes = elementTypes; + } + + @Override + public boolean isValidFor(AnnotatedNode target) { + return AstSupport.matchesOneOf(elementTypes, target); + } + } + +} diff --git a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/ValidationHandler.java b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/ValidationHandler.java index 8aa7381..1154284 100644 --- a/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/ValidationHandler.java +++ b/klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/ValidationHandler.java @@ -33,7 +33,10 @@ import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; public class ValidationHandler { @@ -42,7 +45,7 @@ public class ValidationHandler { private final List errors = new ArrayList<>(); private String currentMember; - private Annotation currentAnnotation; + private List annotationStack = new ArrayList<>(); public enum Status { VALIDATED } @@ -98,16 +101,17 @@ private void doValidate(AnnotatedElement annotationOrMethod) { } private void handleSingleAnnotation(Annotation annotation) { - if (annotation instanceof KlumCastValidator) { - executeValidator((KlumCastValidator) annotation); - } else if (isValidated(annotation)) { - Annotation previousAnnotation = currentAnnotation; - try { - currentAnnotation = annotation; + if (!FilterHandler.isValidFor(annotation, target)) + return; + try { + annotationStack.add(annotation); + if (annotation instanceof KlumCastValidator) { + executeValidator((KlumCastValidator) annotation); + } else if (isValidated(annotation)) { doValidate(annotation.annotationType()); - } finally { - currentAnnotation = previousAnnotation; } + } finally { + annotationStack.remove(annotationStack.size() - 1); } } @@ -127,8 +131,7 @@ private void executeValidator(KlumCastValidator validator) { if (!KlumCastCheck.class.isAssignableFrom(type)) throw new IllegalStateException("Class " + validator.value() + " is not a KlumCastCheck."); KlumCastCheck check = (KlumCastCheck) InvokerHelper.invokeNoArgumentsConstructorOf(type); - check.setKlumCastValidatorAnnotation(validator); - check.setControlAnnotation(currentAnnotation); + check.setAnnotationStack(Collections.unmodifiableList(annotationStack)); check.setMemberName(currentMember); check.check(annotationToValidate, target).ifPresent(errors::add); } catch (ClassNotFoundException e) { 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) + } }