Skip to content

Commit 0ec3529

Browse files
committed
Basic 'or' for checks
1 parent 5964c25 commit 0ec3529

File tree

4 files changed

+288
-3
lines changed

4 files changed

+288
-3
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2023 Stephan Pauxberger
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package com.blackbuild.klum.cast.checks;
25+
26+
import com.blackbuild.klum.cast.KlumCastValidator;
27+
28+
import java.lang.annotation.ElementType;
29+
import java.lang.annotation.Retention;
30+
import java.lang.annotation.RetentionPolicy;
31+
import java.lang.annotation.Target;
32+
33+
/**
34+
* Basic or-composition for checks. Due to the nature of the annotation definition, this is somewhat complicated.
35+
* <ul>
36+
* <li>OneCheckMustMatch must be set on an annotation that is designated a {@link com.blackbuild.klum.cast.KlumCastValidated}
37+
* (hence called 'aggregation annotation'</li>
38+
* <li>The aggregation annotation contains members of the type of other validation annotations</li>
39+
* <li>These usually have a default value.</li>
40+
* <li>The aggregation annotation must be the ultimate validation annotation, but must be included in another annotation</li>
41+
* <li>The Check of any annotation branch has no access to the annotation stack outside of the branch annotation</li>
42+
* </ul>
43+
* <p>Example:</p>
44+
* <pre><code>
45+
* {literal @}Target(ElementType.ANNOTATION_TYPE)
46+
* {literal @}Retention(RetentionPolicy.RUNTIME)
47+
* {literal @}KlumCastValidated
48+
* {literal @}OneCheckMustMatch
49+
* {literal @}interface ClosureOrSingleParameter {
50+
* NumberOfParameters oneParam() default {literal @}NumberOfParameters(1);
51+
* NeedsReturnType returnType() default {literal @}NeedsReturnType(Closure.class);
52+
* }
53+
* {literal @}Target([ElementType.METHOD, ElementType.TYPE])
54+
* {literal @}Retention(RetentionPolicy.RUNTIME)
55+
* {literal @}KlumCastValidated
56+
* {literal @}ClosureOrSingleParameter
57+
* {literal @}interface MyAnnotation {}
58+
* </code></pre>
59+
* In that case, it is sufficient for either the {@link com.blackbuild.klum.cast.checks.NeedsReturnType} or the
60+
* {@link com.blackbuild.klum.cast.checks.NumberOfParameters} check to succeed.
61+
*/
62+
@Target(ElementType.ANNOTATION_TYPE)
63+
@Retention(RetentionPolicy.RUNTIME)
64+
@KlumCastValidator("com.blackbuild.klum.cast.validation.OneCheckMustMatchCheck")
65+
public @interface OneCheckMustMatch {}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2023 Stephan Pauxberger
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package com.blackbuild.klum.cast.validation;
25+
26+
import com.blackbuild.klum.cast.checks.OneCheckMustMatch;
27+
import com.blackbuild.klum.cast.checks.impl.KlumCastCheck;
28+
import com.blackbuild.klum.cast.checks.impl.ValidationException;
29+
import org.codehaus.groovy.ast.AnnotatedNode;
30+
import org.codehaus.groovy.ast.AnnotationNode;
31+
32+
import java.lang.annotation.Annotation;
33+
import java.lang.reflect.Method;
34+
import java.util.Arrays;
35+
import java.util.Collections;
36+
import java.util.List;
37+
import java.util.Optional;
38+
39+
public class OneCheckMustMatchCheck extends KlumCastCheck<OneCheckMustMatch> {
40+
41+
private Annotation orBranchHolder;
42+
43+
@Override
44+
public void setAnnotationStack(List<OneCheckMustMatch> annotationStack) {
45+
if (annotationStack.size() < 3)
46+
throw new IllegalStateException("OneMustMatch annotation must be part of a validation annotation");
47+
48+
orBranchHolder = annotationStack.get(annotationStack.size() - 3);
49+
super.setAnnotationStack(annotationStack);
50+
}
51+
52+
@Override
53+
public Optional<ErrorMessage> check(AnnotationNode annotationToCheck, AnnotatedNode target) {
54+
boolean hasNoErrors = Arrays.stream(orBranchHolder.annotationType().getDeclaredMethods())
55+
.filter(method -> method.getReturnType().isAnnotation())
56+
.map(member -> executeSubCheck(member, annotationToCheck, target))
57+
.anyMatch(List::isEmpty);
58+
59+
if (hasNoErrors)
60+
return Optional.empty();
61+
else
62+
return Optional.of(new ErrorMessage("At least one of the checks of " + orBranchHolder.annotationType().getSimpleName() + " must match.", annotationToCheck));
63+
}
64+
65+
@Override
66+
protected void doCheck(AnnotationNode annotationToCheck, AnnotatedNode target) throws ValidationException {
67+
// unused
68+
}
69+
70+
private List<ErrorMessage> executeSubCheck(Method member, AnnotationNode annotationToCheck, AnnotatedNode target) {
71+
try {
72+
return doExecuteSubCheck((Annotation) member.invoke(orBranchHolder), annotationToCheck, target);
73+
} catch (Exception e) {
74+
return Collections.singletonList(new ErrorMessage("Could not get member value for " + member.getName() + ": " + e.getMessage(), annotationToCheck));
75+
}
76+
}
77+
78+
private List<ErrorMessage> doExecuteSubCheck(Annotation subAnnotation, AnnotationNode annotationToCheck, AnnotatedNode target) {
79+
ValidationHandler subCheck = new ValidationHandler(annotationToCheck, target);
80+
subCheck.handleSingleAnnotation(subAnnotation);
81+
return subCheck.getErrors();
82+
}
83+
}

klum-cast-compile/src/main/java/com/blackbuild/klum/cast/validation/ValidationHandler.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@ public class ValidationHandler {
4545
private final List<KlumCastCheck.ErrorMessage> errors = new ArrayList<>();
4646
private String currentMember;
4747

48-
private List<Annotation> annotationStack = new ArrayList<>();
48+
private final List<Annotation> annotationStack = new ArrayList<>();
4949

5050
public enum Status { VALIDATED }
5151

5252
public static final String METADATA_KEY = ValidationHandler.class.getName();
5353

54-
private ValidationHandler(AnnotationNode annotationToValidate, AnnotatedNode target) {
54+
ValidationHandler(AnnotationNode annotationToValidate, AnnotatedNode target) {
5555
this.annotationToValidate = annotationToValidate;
5656
this.target = target;
5757
}
@@ -100,7 +100,7 @@ private void doValidate(AnnotatedElement annotationOrMethod) {
100100
.forEach(this::handleSingleAnnotation);
101101
}
102102

103-
private void handleSingleAnnotation(Annotation annotation) {
103+
void handleSingleAnnotation(Annotation annotation) {
104104
if (!FilterHandler.isValidFor(annotation, target))
105105
return;
106106
try {
@@ -138,4 +138,8 @@ private void executeValidator(KlumCastValidator validator) {
138138
throw new IllegalStateException("Could not create instance of KlumCastCheck " + validator.value(), e);
139139
}
140140
}
141+
142+
List<KlumCastCheck.ErrorMessage> getErrors() {
143+
return errors;
144+
}
141145
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2023 Stephan Pauxberger
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package com.blackbuild.klum.cast.checks
25+
26+
import com.blackbuild.klum.cast.validation.AstSpec
27+
import org.codehaus.groovy.control.MultipleCompilationErrorsException
28+
29+
class BooleanOperationsTest extends AstSpec {
30+
31+
def "all branches fail means the check fails"() {
32+
given:
33+
createClass '''import com.blackbuild.klum.cast.KlumCastValidated
34+
import com.blackbuild.klum.cast.validation.DummyFailAnnotation
35+
36+
import java.lang.annotation.Annotation
37+
import java.lang.annotation.ElementType
38+
import java.lang.annotation.Target
39+
40+
@Target(ElementType.ANNOTATION_TYPE)
41+
@Retention(RetentionPolicy.RUNTIME)
42+
@KlumCastValidated
43+
@OneCheckMustMatch
44+
@interface NonePass {
45+
DummyFailAnnotation fail() default @DummyFailAnnotation;
46+
DummyFailAnnotation fail2() default @DummyFailAnnotation;
47+
DummyFailAnnotation fail3() default @DummyFailAnnotation;
48+
}
49+
@Target([ElementType.METHOD, ElementType.TYPE])
50+
@Retention(RetentionPolicy.RUNTIME)
51+
@KlumCastValidated
52+
@NonePass
53+
@interface MyAnnotation {}
54+
'''
55+
56+
when: "one branch passes"
57+
createClass '''
58+
@MyAnnotation
59+
class MyClass {}
60+
'''
61+
then:
62+
thrown(MultipleCompilationErrorsException)
63+
}
64+
65+
def "one branch of an Or check is sufficient to pass"() {
66+
given:
67+
createClass '''import com.blackbuild.klum.cast.validation.*
68+
69+
import java.lang.annotation.ElementType
70+
import java.lang.annotation.Target
71+
72+
@Target([ElementType.ANNOTATION_TYPE])
73+
@Retention(RetentionPolicy.RUNTIME)
74+
@KlumCastValidated
75+
@OneCheckMustMatch
76+
@interface OnePass {
77+
DummyFailAnnotation fail() default @DummyFailAnnotation;
78+
DummyFailAnnotation fail2() default @DummyFailAnnotation;
79+
DummyPassAnnotation pass() default @DummyPassAnnotation;
80+
}
81+
82+
@Target([ElementType.METHOD, ElementType.TYPE])
83+
@Retention(RetentionPolicy.RUNTIME)
84+
@KlumCastValidated
85+
@OnePass
86+
@interface MyAnnotation {}
87+
'''
88+
89+
when: "one branch passes"
90+
createClass '''
91+
@MyAnnotation
92+
class MyClass {}
93+
'''
94+
then:
95+
noExceptionThrown()
96+
}
97+
98+
def "multiple branches of an Or check pass"() {
99+
given:
100+
createClass '''import com.blackbuild.klum.cast.validation.*
101+
102+
import java.lang.annotation.ElementType
103+
import java.lang.annotation.Target
104+
105+
@Target(ElementType.ANNOTATION_TYPE)
106+
@Retention(RetentionPolicy.RUNTIME)
107+
@KlumCastValidated
108+
@OneCheckMustMatch
109+
@interface TwoPass {
110+
DummyFailAnnotation fail() default @DummyFailAnnotation;
111+
DummyPassAnnotation pass2() default @DummyPassAnnotation;
112+
DummyPassAnnotation pass() default @DummyPassAnnotation;
113+
}
114+
@Target([ElementType.METHOD, ElementType.TYPE])
115+
@Retention(RetentionPolicy.RUNTIME)
116+
@KlumCastValidated
117+
@TwoPass
118+
@interface MyAnnotation {}
119+
120+
'''
121+
122+
when: "one branch passes"
123+
createClass '''
124+
@MyAnnotation
125+
class MyClass {}
126+
'''
127+
then:
128+
noExceptionThrown()
129+
}
130+
131+
132+
133+
}

0 commit comments

Comments
 (0)