16
16
17
17
package io .spring .initializr .generator .language ;
18
18
19
+ import java .util .Collection ;
19
20
import java .util .LinkedHashMap ;
20
21
import java .util .Map ;
21
22
import java .util .function .Consumer ;
22
23
import java .util .stream .Stream ;
23
24
24
25
import io .spring .initializr .generator .language .Annotation .Builder ;
25
26
27
+ import org .springframework .util .LinkedMultiValueMap ;
28
+ import org .springframework .util .MultiValueMap ;
29
+
26
30
/**
27
- * A container for {@linkplain Annotation annotations} defined on an annotated element.
31
+ * A container for annotations defined on an annotated element.
32
+ * <p>
33
+ * Supports both single and repeatable annotations. Single annotations can be customized
34
+ * even after they have been added.
28
35
*
29
36
* @author Stephane Nicoll
30
- * @author Sijun Yang
37
+ * @author Moritz Halbritter
31
38
*/
32
39
public class AnnotationContainer {
33
40
34
- private final Map <String , Builder > annotations ;
41
+ private final Map <ClassName , Builder > singleAnnotations ;
42
+
43
+ private final MultiValueMap <ClassName , Builder > repeatableAnnotations ;
35
44
36
45
public AnnotationContainer () {
37
- this (new LinkedHashMap <>());
46
+ this (new LinkedHashMap <>(), new LinkedMultiValueMap <>() );
38
47
}
39
48
40
- private AnnotationContainer (Map <String , Builder > annotations ) {
41
- this .annotations = annotations ;
49
+ private AnnotationContainer (Map <ClassName , Builder > singleAnnotations ,
50
+ MultiValueMap <ClassName , Builder > repeatableAnnotations ) {
51
+ this .singleAnnotations = singleAnnotations ;
52
+ this .repeatableAnnotations = repeatableAnnotations ;
42
53
}
43
54
44
55
/**
45
56
* Specify if this container is empty.
46
57
* @return {@code true} if no annotation is registered
47
58
*/
48
59
public boolean isEmpty () {
49
- return this .annotations .isEmpty ();
60
+ return this .singleAnnotations . isEmpty () && this . repeatableAnnotations .isEmpty ();
50
61
}
51
62
52
63
/**
53
- * Specify if this container has an annotation with the specified {@link ClassName}.
64
+ * Specify if this container has an annotation with the specified class name.
65
+ * Considers both single and repeatable annotations.
54
66
* @param className the class name of an annotation
55
67
* @return {@code true} if the annotation with the specified class name exists
56
68
*/
57
69
public boolean has (ClassName className ) {
58
- return this .annotations .containsKey (className . getCanonicalName () );
70
+ return this .singleAnnotations .containsKey (className ) || this . repeatableAnnotations . containsKey ( className );
59
71
}
60
72
61
73
/**
62
- * Specify if this instance contains an annotation with the specified name.
63
- * @param name the name of the annotation
64
- * @return {@code true} if an annotation with the specified name is present, otherwise
65
- * {@code false}
74
+ * Whether this container has a single annotation with the specified class name.
75
+ * @param className the class name of an annotation
76
+ * @return whether this container has the annotation
66
77
*/
67
- public boolean has ( String name ) {
68
- return this .annotations .containsKey (name );
78
+ public boolean hasSingle ( ClassName className ) {
79
+ return this .singleAnnotations .containsKey (className );
69
80
}
70
81
71
82
/**
72
- * Return the {@link Annotation annotations}.
73
- * @return the annotations
83
+ * Whether this container has repeatable annotations with the specified class name.
84
+ * @param className the class name of an annotation
85
+ * @return whether this container has the annotation
74
86
*/
75
- public Stream < Annotation > values ( ) {
76
- return this .annotations . values (). stream (). map ( Builder :: build );
87
+ public boolean hasRepeatable ( ClassName className ) {
88
+ return this .repeatableAnnotations . containsKey ( className );
77
89
}
78
90
79
91
/**
80
- * Add an {@link Annotation} with the specific name and {@link Consumer} to customize
81
- * it. If an annotation with that name already exists, the consumer can be used to
82
- * further tune its attributes.
83
- * @param name the name of the annotation
84
- * @param className the class name of the annotation
85
- * @param annotation a {@link Consumer} to further configure the annotation
92
+ * Return the annotations. Returns both single and repeatable annotations.
93
+ * @return the annotations
86
94
*/
87
- public void add ( String name , ClassName className , Consumer < Builder > annotation ) {
88
- Builder builder = this . annotations . computeIfAbsent ( name , ( key ) -> new Builder ( className ));
89
- if ( annotation != null ) {
90
- annotation . accept ( builder );
91
- }
95
+ public Stream < Annotation > values ( ) {
96
+ return Stream
97
+ . concat ( this . singleAnnotations . values (). stream (),
98
+ this . repeatableAnnotations . values (). stream (). flatMap ( Collection :: stream ))
99
+ . map ( Builder :: build );
92
100
}
93
101
94
102
/**
95
- * Add an {@link Annotation} with the specific name. Does nothing If an annotation
96
- * with that name already exists.
97
- * @param name the name of the annotation
98
- * @param className the class name of the annotation
103
+ * Add a single annotation with the specified class name. Does nothing If the
104
+ * annotation has already been added.
105
+ * @param className the class name of an annotation
106
+ * @deprecated in favor of {@link #addSingle(ClassName)} and
107
+ * {@link #addRepeatable(ClassName)}
99
108
*/
100
- public void add (String name , ClassName className ) {
101
- add (name , className , null );
109
+ @ Deprecated (forRemoval = true )
110
+ public void add (ClassName className ) {
111
+ add (className , null );
102
112
}
103
113
104
114
/**
@@ -107,42 +117,132 @@ public void add(String name, ClassName className) {
107
117
* to further tune attributes.
108
118
* @param className the class name of an annotation
109
119
* @param annotation a {@link Consumer} to customize the {@link Annotation}
120
+ * @deprecated in favor of {@link #addSingle(ClassName, Consumer)} and
121
+ * {@link #addRepeatable(ClassName)}
110
122
*/
123
+ @ Deprecated (forRemoval = true )
111
124
public void add (ClassName className , Consumer <Builder > annotation ) {
112
- add (className .getCanonicalName (), className , annotation );
125
+ if (hasRepeatable (className )) {
126
+ throw new IllegalArgumentException (
127
+ "%s has already been used for repeatable annotations" .formatted (className ));
128
+ }
129
+ Builder builder = this .singleAnnotations .computeIfAbsent (className , (key ) -> new Builder (className ));
130
+ if (annotation != null ) {
131
+ annotation .accept (builder );
132
+ }
113
133
}
114
134
115
135
/**
116
- * Add a single {@link Annotation} with the specified class name. Does nothing If the
117
- * annotation has already been added.
136
+ * Add a single annotation.
118
137
* @param className the class name of an annotation
138
+ * @return whether the annotation has been added
139
+ * @throws IllegalStateException if the annotation has already been used for
140
+ * repeatable annotations
119
141
*/
120
- public void add (ClassName className ) {
121
- add (className .getCanonicalName (), className , null );
142
+ public boolean addSingle (ClassName className ) {
143
+ return addSingle (className , null );
144
+ }
145
+
146
+ /**
147
+ * Add a single annotation with the specified class name. If the annotation already
148
+ * exists, this method does nothing.
149
+ * @param className the class name of an annotation
150
+ * @param annotation a {@link Consumer} to customize the annotation
151
+ * @return whether the annotation has been added
152
+ * @throws IllegalStateException if the annotation has already been used for
153
+ * repeatable annotations
154
+ */
155
+ public boolean addSingle (ClassName className , Consumer <Builder > annotation ) {
156
+ if (hasSingle (className )) {
157
+ return false ;
158
+ }
159
+ if (hasRepeatable (className )) {
160
+ throw new IllegalStateException ("%s has already been used for repeatable annotations" .formatted (className ));
161
+ }
162
+ Builder builder = new Builder (className );
163
+ if (annotation != null ) {
164
+ annotation .accept (builder );
165
+ }
166
+ this .singleAnnotations .put (className , builder );
167
+ return true ;
122
168
}
123
169
124
170
/**
125
- * Remove the annotation with the specified {@link ClassName}.
171
+ * Customize a single annotation if it exists. This method does nothing if the
172
+ * annotation doesn't exist.
173
+ * @param className the class name of an annotation
174
+ * @param customizer the customizer for the annotation
175
+ */
176
+ public void customizeSingle (ClassName className , Consumer <Builder > customizer ) {
177
+ Builder builder = this .singleAnnotations .get (className );
178
+ if (builder != null ) {
179
+ customizer .accept (builder );
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Add a repeatable annotation.
185
+ * @param className the class name of an annotation
186
+ * @throws IllegalStateException if the annotation has already been added as a single
187
+ * annotation
188
+ */
189
+ public void addRepeatable (ClassName className ) {
190
+ addRepeatable (className , null );
191
+ }
192
+
193
+ /**
194
+ * Add a repeatable annotation.
195
+ * @param className the class name of an annotation
196
+ * @param annotation a {@link Consumer} to customize the annotation
197
+ * @throws IllegalStateException if the annotation has already been added as a single
198
+ * annotation
199
+ */
200
+ public void addRepeatable (ClassName className , Consumer <Builder > annotation ) {
201
+ if (hasSingle (className )) {
202
+ throw new IllegalStateException ("%s has already been added as a single annotation" .formatted (className ));
203
+ }
204
+ Builder builder = new Builder (className );
205
+ if (annotation != null ) {
206
+ annotation .accept (builder );
207
+ }
208
+ this .repeatableAnnotations .add (className , builder );
209
+ }
210
+
211
+ /**
212
+ * Remove the annotation with the specified classname from either the single or the
213
+ * repeatable annotations.
126
214
* @param className the class name of the annotation
127
215
* @return {@code true} if such an annotation exists, {@code false} otherwise
128
216
*/
129
217
public boolean remove (ClassName className ) {
130
- return this .annotations .remove (className . getCanonicalName () ) != null ;
218
+ return this .singleAnnotations .remove (className ) != null || this . repeatableAnnotations . remove ( className ) != null ;
131
219
}
132
220
133
221
/**
134
- * Remove the annotation with the specified name.
135
- * @param name the name of the annotation to remove
136
- * @return {@code true} if the annotation was removed, {@code false} otherwise
222
+ * Remove a single with the specified classname.
223
+ * @param className the class name of an annotation
224
+ * @return whether the annotation has been removed
225
+ */
226
+ public boolean removeSingle (ClassName className ) {
227
+ return this .singleAnnotations .remove (className ) != null ;
228
+ }
229
+
230
+ /**
231
+ * Remove all repeatable annotations with the specified classname.
232
+ * @param className the class name of an annotation
233
+ * @return whether any annotation has been removed
137
234
*/
138
- public boolean remove ( String name ) {
139
- return this .annotations .remove (name ) != null ;
235
+ public boolean removeAllRepeatable ( ClassName className ) {
236
+ return this .repeatableAnnotations .remove (className ) != null ;
140
237
}
141
238
142
239
public AnnotationContainer deepCopy () {
143
- Map <String , Builder > copy = new LinkedHashMap <>();
144
- this .annotations .forEach ((name , builder ) -> copy .put (name , new Builder (builder )));
145
- return new AnnotationContainer (copy );
240
+ Map <ClassName , Builder > singleAnnotations = new LinkedHashMap <>();
241
+ this .singleAnnotations .forEach ((className , builder ) -> singleAnnotations .put (className , new Builder (builder )));
242
+ MultiValueMap <ClassName , Builder > repeatableAnnotations = new LinkedMultiValueMap <>();
243
+ this .repeatableAnnotations .forEach ((className , builders ) -> builders
244
+ .forEach ((builder ) -> repeatableAnnotations .add (className , new Builder (builder ))));
245
+ return new AnnotationContainer (singleAnnotations , repeatableAnnotations );
146
246
}
147
247
148
248
}
0 commit comments