You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@@ -7,6 +7,63 @@ Check those annotations with style!
7
7
8
8
KlumCast is validator for annotation placement for Groovy based schemas. It allows to conveniently validate AST driving annotations before the actual transformation is performed and thus helps keep the transformation code clean.
9
9
10
+
## For whom is KlumCast?
11
+
12
+
KlumCast is for you if you are writing a Groovy AST transformation that is driven by annotations.
13
+
14
+
It is also relevant for extenders of an existing framework, for example a Layer3 approach with KlumDSL can hugely benefit from the usage of KlumCast.
15
+
16
+
## Basic example
17
+
18
+
Lets consider a an annotation that creates convenience methods to fill add entries to collection fields:
19
+
20
+
```groovy
21
+
class MyClass {
22
+
@AutoAdd
23
+
List<String> names
24
+
}
25
+
```
26
+
27
+
Based on this setup, the following code would be generated:
28
+
29
+
```groovy
30
+
void addName(String name) {
31
+
if (names == null) names = []
32
+
names.add(name)
33
+
}
34
+
35
+
void addNames(Collection<String> names) {
36
+
if (this.names == null) this.names = []
37
+
this.names.addAll(names)
38
+
}
39
+
40
+
void addNames(String... names) {
41
+
if (this.names == null) this.names = []
42
+
this.names.addAll(names)
43
+
}
44
+
```
45
+
46
+
A Groovy AST-Transformation generating the above code would be easy to implement.
47
+
However, before actually creating the methods, it must be assured, that the field to be transformed is actually a collection. Otherwise, compilation errors or worse, runtime errors would occur.
48
+
49
+
Making this check is easy to implement, but it would clutter the transformation code as well as the javadoc of the annotation. KlumCast allows to move this check to a separate class, which is then called by the AST-Transformation. It also includes a couple of preconfigured validations that can be used out of the box.
50
+
51
+
Note that annotation, checks and transformation can be written in Java as well as in Groovy, only the actual model schema - and of course the model itself - needs to be written in Groovy.
52
+
53
+
```java
54
+
importjava.lang.annotation.Retention;
55
+
importjava.lang.annotation.Target;
56
+
57
+
@Target(ElementType.FIELD)
58
+
@Retention(RetentionPolicy.RUNTIME)
59
+
@KlumCastValidated
60
+
@NeedsType(Collection)
61
+
@interfaceAutoAdd {}
62
+
```
63
+
64
+
This is a lot cleaner and easier to understand, as well as less effort in implementing the transformation.
65
+
66
+
10
67
# Quick explainer: models, schemas and AST transformations
11
68
12
69
Since KlumCast derived from Klum-AST, it is a fitting example to explain the key concepts of KlumCast.
@@ -71,7 +128,20 @@ The Annotations in the above example all have to follow additional placement rul
71
128
72
129
# Usage
73
130
74
-
In order to use KlumCast on an annotation, the validation to check needs to be annotated with `@KlumCastValidated` as well as the actual validation annotations as in the example above
131
+
## Basic usage: use provided validations
132
+
133
+
### Dependencies
134
+
135
+
KlumCast ist split into two modules: klum-cast-annotations and klum-cast-compile. The annotations module contains the preconfigured validations as well as the base class for custom validations. The compile module contains the AST-Transformation that is applied to the schema.
136
+
137
+
The annotations module must be present at runtime (since the validation transformation needs access to the compiled classes of the annotations). However,
138
+
the compile module only needs to be present during the compilation of the schema, but not during the compilation of the model itself, i.e. it should be a compileOnly dependency in Gradle or an optional dependency in Maven. Since the compile module contains a global AST-Transformation, it would have a slight impact on the compilation time of the model, so it should be avoided to have it present during the compilation of the model.
139
+
140
+
If a project is split into the usual three modules (annotations, ast and runtime), the klumcast-annotations module should be a regular dependency of the annotations module (`compile` for Maven, `api` or `implementation` for Gradle), while the klumcast-compile module should be a compileOnly dependency of the ast module (`optional` for Maven, `compileOnly` for Gradle). See KlumAST for an example.
141
+
142
+
### Declaring validations
143
+
144
+
In order to use KlumCast on an annotation, the validation to check needs to be annotated with `@KlumCastValidated` as well as the actual validation annotations as in the example above. The `@KlumCastValidated` annotation is only used to mark the annotation as validation target and is not used by the AST-Transformation itself. It allows the validation transformation to easily detect the annotations to be processed.
75
145
76
146
```java
77
147
@Target([ElementType.FIELD, ElementType.METHOD])
@@ -84,70 +154,152 @@ public @interface Field {
84
154
}
85
155
```
86
156
87
-
Note that the validations to be annotated can be implemented in Java or Groovy, but place where the annotation is used needs to be Groovy, since the AST transformation is only applied to Groovy code.
157
+
Note that the validations to be annotated can be implemented in Java or Groovy, but the place where the annotation is used needs to be Groovy, since the AST transformation is only applied to Groovy code.
88
158
89
-
In order for the validation to be applied, the KlumCast library needs to be present during compile time of schema, but it does not need to be present during the compilation/loading of the model (i.e. the compileOnly in case of Gradle or optional in case of Maven).
159
+
### Member annotations
90
160
91
-
# Validation annotations
161
+
KlumCast also supports validation annotations placed on members of the validated annotation, which makes syntax more concise.
92
162
93
-
The following validation annotations are available:
163
+
For example, the `@Validate` annotation of KlumAST can be placed on classes as well as on fields and methods. Depending on the placement, only specific members of the annotation are valid:
KlumCast includes a couple of validations that can be used out of the box:
183
+
184
+
#### @ClassNeedsAnnotation
96
185
97
186
Checks if the class the annotated element is part of is annotated with the given annotation.
98
187
99
-
## @NumberOfParameters
188
+
####@NumberOfParameters
100
189
101
190
Checks that the annotated methods has exactly the given number of parameters. Note that if the annotated element is no method, the validation is ignored.
102
191
103
-
## @AllowedMembers
192
+
#### @MustBeStatic
193
+
194
+
If the validated annotation is placed on a method, the method must be static. Has no effect if the annotation is placed on any other element.
195
+
196
+
#### @MutuallyExclusive
197
+
198
+
Designates that the annotated members are mutually exclusive, i.e. only one of them can be set at the same time.
199
+
200
+
#### @NeedsReturnType
201
+
202
+
Checks that the annotated method has the given return type. Note that if the annotated element is no method, the validation is ignored.
203
+
204
+
#### @ParameterTypes
205
+
206
+
Forces the parameters of an annotated method to be of the given type. Note that if the annotated element is no method, the validation is ignored.
207
+
208
+
#### @UniquePerClass
209
+
210
+
The annotation must only be used once per class.
211
+
212
+
#### @AlsoNeeds
213
+
214
+
The AlsoNeeds annotation is used to specify that a certain annotation member should be used together with one or more specific annotation members. It can only be used on annotation members.
215
+
216
+
#### @NotTogetherWith
217
+
218
+
The NotTogetherWith annotation is used to specify that a certain annotation member should not be used together with one or more specific annotation members. It can only be used on annotation members.
219
+
220
+
#### @NotOn
104
221
105
-
Can be used multiple times for various targets. Checks for the matching target that only the given members of the annotation are set or that some annotations are forbidden.
222
+
The NotOn annotation is used to specify that a certain annotation member should not be used on a specific element type. It can only be used on annotation members.
106
223
107
-
#Custom validations
224
+
#### @OnlyOn
108
225
109
-
Custom validations consist of two elements, the annotation and a validator class. The annotation needs to be annotated with `@KlumCastValidated` and the validator class needs to extends `KlumCastCheck`.
226
+
The OnlyOn annotation is used to specify that a certain annotation member should only be used on a specific element type. It can only be used on annotation members.
110
227
111
-
## The annotation
228
+
## Nested annotations
112
229
113
-
The annotation needs to be of Runtime retention and target only Annotations. It is annotated with `@KlumCastValidator` which points to the classname of the validator class.
230
+
Validation annotations can themselves be aggregations of multiple annotations. This is useful if a combination of validations is used multiple times. Or to give
231
+
an annotation a domain specific name.
232
+
233
+
Note that Target and Retention annotations are omitted in the examples below for brevity.
234
+
235
+
```groovy
236
+
@KlumCastValidated
237
+
@ClassNeedsAnnotation(DSL)
238
+
@interface NeedsDslClass {}
239
+
240
+
@KlumCastValidated
241
+
@NumberOfParameters(1)
242
+
@NeedsReturnType(Void)
243
+
@interface SetterLike {}
244
+
```
245
+
## Custom validations
246
+
247
+
Custom validation can be declared using the `@KlumCastValidator` annotation. This annotation points to the class implementing the validation. The class must extend `KlumCastCheck` and have the actual validator annotation as type parameter. This allows for easy parametrizing of the validator.
248
+
249
+
So usually, a custom validation consist of two elements, the control annotation (which is eventually placed on the target annotation to mark it as validated) and a validator class. The control annotation needs to be annotated with `@KlumCastValidator` and the validator class needs to extend `KlumCastCheck`.
250
+
251
+
## The control annotation
252
+
253
+
The control annotation needs to be of Runtime retention and target only Annotations. It is annotated with `@KlumCastValidator` which points to the classname or type of the validator class. The annotation should have a clear name and contain further members to parametrize the validator.
114
254
115
255
```groovy
116
256
@Target(ElementType.ANNOTATION_TYPE)
117
257
@Retention(RetentionPolicy.RUNTIME)
118
-
@KlumCastValidator("my.MyValidator")
119
-
@interface MyAnnotation {
258
+
@KlumCastValidator("my.NameMustMatchCheck")
259
+
@interface NameMustMatch {
120
260
String value()
121
261
}
122
262
```
123
263
124
-
## the validator class
264
+
## The validator class
265
+
266
+
The validator class extends `KlumCastCheck` and have the annotation as Type-Parameter. The actual check is usually implemented by the `doCheck` method, which has access to the following information:
267
+
268
+
- the control annotation (as annotation object, `NameMustMatch` in the example above (if the KlumCastValidatior annotation is placed directly on the annotation to be validated, this can be null)
269
+
- the `KlumCastValidator` annotation (which can have an additional String array to further parametrize the validator), in the example above this would be in instance of `@KlumCastValidator("my.NameMustMatch")`
270
+
- the annotation target, i.e. the annotated element itself as an `AnnotatedNode` instance
271
+
- the annotation to validate, i.e. the annotation that is annotated with the control annotation, as an `AnnotationNode` instance
272
+
- if the control annotation is placed on a member of the annotation to validate, that member's name as a String
125
273
126
-
The validator class extends `KlumCastCheck` and have the annotation as Type-Parameter. The `doCheck` method receives the AST node of the annotated element and the annotation itself. Any exception thrown inside this method is converted to a Compilation Error.
274
+
The last to elements are Groovy-Compiler AST-Nodes.
275
+
276
+
The `doCheck` should perform necessary validations and eventually either return or throw an exception. If an exception is thrown, it is converted to a compilation error.
127
277
128
278
```groovy
129
-
class MyValidator extends KlumCastCheck<MyAnnotation> {
279
+
class NameMustMatchCheck extends KlumCastCheck<NameMustMatch> {
throw new IllegalStateException("must not be placed on Foos")
282
+
if (!target.getText().startsWith(controlAnnotation.value())) {
283
+
throw new IllegalStateException("Target element must start with ${controlAnnotation.value()}")
134
284
}
135
285
}
136
286
}
137
287
```
138
288
289
+
Additionally, the method `isValidFor(AnnotatedNode target)` can be overridden quickly skip the check if necessary.
290
+
139
291
## Check as inner class
140
292
141
293
For convenience, the validator class can be implemented as inner class to the annotation itself. In that case, the alternative syntax using `type` instead of `value` is useful.
@@ -157,4 +309,7 @@ For convenience, the validator class can be implemented as inner class to the an
157
309
}
158
310
159
311
}
160
-
```
312
+
```
313
+
314
+
Note that annotations and KlumCastValidator annotations can be freely mixed, i.e. a control annotation can have multiple KlumCastValidator annotations as well as multiple control annotations (which themselves can have multiple KlumCastValidator annotations or even more control annotations). Just remember that annotations neither having
315
+
`KlumCastValidated` nor `KlumCastValidator` are ignored by the AST-Transformation.
0 commit comments