Skip to content

Commit 10ecec8

Browse files
SentryManrbygrave
andauthored
[utype] extract repeatable annotations (#95)
* [utype] extract repeatable annotations * Format and inline comments * Format generated code for TypeMirrorVisitor + spelling of fullyQualifiedName() --------- Co-authored-by: Rob Bygrave <robin.bygrave@gmail.com>
1 parent a549e18 commit 10ecec8

File tree

4 files changed

+65
-51
lines changed

4 files changed

+65
-51
lines changed

blackbox-test-prism/src/main/java/io/avaje/prisms/test/context/UTypeTester.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,9 @@ public class UTypeTester<T extends CommonInterface & CommonInterface2> {
2525
List<? extends Object> extendWild;
2626
List<?> wild;
2727

28+
@NotEmpty
29+
@NotEmpty
30+
List<@NotBlank.List({@NotBlank, @NotBlank}) String> repeatable;
31+
2832
void testVoid(@NotEmpty(groups = int[].class) int[] param) {}
2933
}

blackbox-test-prism/src/test/java/io/avaje/prisms/test/TestProcessor.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,9 @@
1212
import javax.annotation.processing.SupportedAnnotationTypes;
1313
import javax.lang.model.SourceVersion;
1414
import javax.lang.model.element.Element;
15-
import javax.lang.model.element.ExecutableElement;
1615
import javax.lang.model.element.TypeElement;
1716
import javax.lang.model.type.TypeKind;
1817
import javax.lang.model.util.ElementFilter;
19-
import javax.management.relation.InvalidRelationIdException;
2018

2119
@SupportedAnnotationTypes("io.avaje.prisms.test.TestAnnotation")
2220
public class TestProcessor extends AbstractProcessor {
@@ -114,6 +112,13 @@ private void typeUseTests(List<UType> typeUseFields) {
114112
var list = typeUseFields.get(1);
115113
assertThat(list.full()).isEqualTo("java.util.List<T>");
116114
assertThat(list.componentTypes().get(0).componentTypes().get(0).componentTypes()).hasSize(2);
115+
116+
final var repeatable = typeUseFields.get(5);
117+
assertThat(repeatable.annotations()).hasSize(2);
118+
assertThat(repeatable.param0().annotations()).hasSize(2);
119+
assertThat(repeatable.full())
120+
.isEqualTo(
121+
"@jakarta.validation.constraints.NotEmpty @jakarta.validation.constraints.NotEmpty java.util.List<@jakarta.validation.constraints.NotBlank @jakarta.validation.constraints.NotBlank java.lang.String>");
117122
}
118123

119124
private void intersectionTypes(TypeElement typeUse) {

prism-core/src/main/java/io/avaje/prism/internal/UTypeWriter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ public static void write(PrintWriter out, String packageName) {
155155
+ " }\n\n"
156156
+ " /** Compare whether the current full() type is identical to the given UType's full() type */\n"
157157
+ " @Override\n"
158-
+ " boolean equals(Object other);"
158+
+ " boolean equals(Object other);\n\n"
159159
+ "}");
160160
}
161161
}

prism-core/src/main/java/io/avaje/prism/internal/VisitorWriter.java

Lines changed: 53 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,38 @@
11
package io.avaje.prism.internal;
22

3+
import static io.avaje.prism.internal.APContext.jdkVersion;
4+
35
import java.io.PrintWriter;
46

57
public class VisitorWriter {
68
private VisitorWriter() {}
79

8-
public static void write(PrintWriter out, String packageName) {
10+
private static String compilerImports() {
11+
if (jdkVersion() >= 23 && APContext.previewEnabled()) {
12+
return "import module java.base;\n" + "import module java.compiler;\n";
13+
}
14+
return "import java.util.*;\n"
15+
+ "\n"
16+
+ "import javax.annotation.processing.Generated;\n"
17+
+ "import javax.lang.model.element.*;\n"
18+
+ "import javax.lang.model.type.*;\n"
19+
+ "import javax.lang.model.util.AbstractTypeVisitor9;\n";
20+
}
921

22+
public static void write(PrintWriter out, String packageName) {
1023
out.append(
1124
"package "
1225
+ packageName
1326
+ ";\n"
1427
+ "\n"
1528
+ "import static java.util.stream.Collectors.toSet;\n"
1629
+ "\n"
17-
+ "import java.util.ArrayList;\n"
18-
+ "import java.util.HashMap;\n"
19-
+ "import java.util.HashSet;\n"
20-
+ "import java.util.LinkedHashMap;\n"
21-
+ "import java.util.List;\n"
22-
+ "import java.util.Locale;\n"
23-
+ "import java.util.Map;\n"
24-
+ "import java.util.Objects;\n"
25-
+ "import java.util.Set;\n"
26-
+ "\n"
27-
+ "import javax.annotation.processing.Generated;\n"
28-
+ "import javax.lang.model.element.AnnotationMirror;\n"
29-
+ "import javax.lang.model.element.Element;\n"
30-
+ "import javax.lang.model.element.QualifiedNameable;\n"
31-
+ "import javax.lang.model.element.TypeElement;\n"
32-
+ "import javax.lang.model.type.ArrayType;\n"
33-
+ "import javax.lang.model.type.DeclaredType;\n"
34-
+ "import javax.lang.model.type.ErrorType;\n"
35-
+ "import javax.lang.model.type.ExecutableType;\n"
36-
+ "import javax.lang.model.type.*;\n"
37-
+ "import javax.lang.model.util.AbstractTypeVisitor9;\n"
30+
+ compilerImports()
3831
+ "\n"
3932
+ "@Generated(\"avaje-prism-generator\")\n"
40-
+ "class TypeMirrorVisitor extends AbstractTypeVisitor9<StringBuilder, StringBuilder>\n"
41-
+ " implements UType {\n"
33+
+ "class TypeMirrorVisitor extends AbstractTypeVisitor9<StringBuilder, StringBuilder> implements UType {\n"
4234
+ "\n"
4335
+ " private final int depth;\n"
44-
+ "\n"
4536
+ " private final boolean includeAnnotations;\n"
4637
+ "\n"
4738
+ " private final Map<TypeVariable, String> typeVariables;\n"
@@ -69,8 +60,7 @@ public static void write(PrintWriter out, String packageName) {
6960
+ " this(1, new HashMap<>(), true);\n"
7061
+ " }\n"
7162
+ "\n"
72-
+ " private TypeMirrorVisitor(\n"
73-
+ " int depth, Map<TypeVariable, String> typeVariables, boolean includeAnnotations) {\n"
63+
+ " private TypeMirrorVisitor(int depth, Map<TypeVariable, String> typeVariables, boolean includeAnnotations) {\n"
7464
+ " this.includeAnnotations = includeAnnotations;\n"
7565
+ " this.depth = depth;\n"
7666
+ " this.typeVariables = new HashMap<>();\n"
@@ -152,7 +142,6 @@ public static void write(PrintWriter out, String packageName) {
152142
+ " }\n"
153143
+ "\n"
154144
+ " private void child(TypeMirror ct, StringBuilder p, boolean setMain) {\n"
155-
+ "\n"
156145
+ " var child = new TypeMirrorVisitor(depth + 1, typeVariables, includeAnnotations);\n"
157146
+ " child.allTypes = allTypes;\n"
158147
+ " child.everyAnnotation = everyAnnotation;\n"
@@ -185,7 +174,6 @@ public static void write(PrintWriter out, String packageName) {
185174
+ " mainType = primitiveStr;\n"
186175
+ " }\n"
187176
+ " p.append(primitiveStr);\n"
188-
+ "\n"
189177
+ " return p;\n"
190178
+ " }\n"
191179
+ "\n"
@@ -222,8 +210,8 @@ public static void write(PrintWriter out, String packageName) {
222210
+ " @Override\n"
223211
+ " public StringBuilder visitDeclared(DeclaredType t, StringBuilder p) {\n"
224212
+ " kind = t.getKind();\n"
225-
+ " final String fqn = fullyQualfiedName(t, includeAnnotations);\n"
226-
+ " var trimmed = fullyQualfiedName(t, false);\n"
213+
+ " final String fqn = fullyQualifiedName(t, includeAnnotations);\n"
214+
+ " var trimmed = fullyQualifiedName(t, false);\n"
227215
+ " allTypes.add(ProcessorUtils.extractEnclosingFQN(trimmed));\n"
228216
+ "\n"
229217
+ " if (this.mainType == null) {\n"
@@ -246,17 +234,19 @@ public static void write(PrintWriter out, String packageName) {
246234
+ " return p;\n"
247235
+ " }\n"
248236
+ "\n"
249-
+ " String fullyQualfiedName(DeclaredType t, boolean includeAnnotations) {\n"
237+
+ " String fullyQualifiedName(DeclaredType t, boolean includeAnnotations) {\n"
250238
+ " final TypeElement element = (TypeElement) t.asElement();\n"
251-
+ " final var typeUseAnnotations = t.getAnnotationMirrors();\n"
239+
+ " final var directAnnotations = t.getAnnotationMirrors();\n"
252240
+ "\n"
253-
+ " if (typeUseAnnotations.isEmpty() || !includeAnnotations) {\n"
241+
+ " if (directAnnotations.isEmpty() || !includeAnnotations) {\n"
254242
+ " return element.getQualifiedName().toString();\n"
255243
+ " }\n"
244+
+ " final var annotations = extractAnnotations(directAnnotations);\n"
245+
+ "\n"
256246
+ " final StringBuilder sb = new StringBuilder();\n"
257247
+ " // if not too nested, write annotations before the fqn like @someAnnotation io.YourType\n"
258248
+ " if (depth < 3) {\n"
259-
+ " for (final var ta : typeUseAnnotations) {\n"
249+
+ " for (final var ta : annotations) {\n"
260250
+ " sb.append(ta.toString()).append(\" \");\n"
261251
+ " }\n"
262252
+ " }\n"
@@ -271,19 +261,41 @@ public static void write(PrintWriter out, String packageName) {
271261
+ "\n"
272262
+ " // if too nested, write annotations in the fqn like io.@someAnnotation YourType\n"
273263
+ " if (depth > 2) {\n"
274-
+ " for (final var ta : typeUseAnnotations) {\n"
264+
+ " for (final var ta : annotations) {\n"
275265
+ " sb.append(ta.toString()).append(\" \");\n"
276266
+ " }\n"
277267
+ " }\n"
278-
+ " for (final var ta : typeUseAnnotations) {\n"
268+
+ " sb.append(element.getSimpleName());\n"
269+
+ " return sb.toString();\n"
270+
+ " }\n"
271+
+ "\n"
272+
+ " private List<AnnotationMirror> extractAnnotations(final List<? extends AnnotationMirror> typeUseAnnotations) {\n"
273+
+ " var directAnnotations = new ArrayList<AnnotationMirror>();\n"
279274
+ "\n"
275+
+ " for (final var ta : typeUseAnnotations) {\n"
280276
+ " final TypeElement annotation = (TypeElement) ta.getAnnotationType().asElement();\n"
277+
+ " @SuppressWarnings(\"unchecked\")\n"
278+
+ " List<AnnotationMirror> repeatableNested =\n"
279+
+ " ta.getElementValues().entrySet().stream()\n"
280+
+ " .findAny()\n"
281+
+ " .filter(e -> \"value\".equals(e.getKey().getSimpleName().toString()))\n"
282+
+ " .map(e -> e.getValue().getValue())\n"
283+
+ " .filter(List.class::isInstance)\n"
284+
+ " .map(List.class::cast)\n"
285+
+ " .filter(e -> !e.isEmpty() && e.get(0) instanceof AnnotationValue)\n"
286+
+ " .map(e -> ((List<AnnotationMirror>) e))\n"
287+
+ " .orElse(List.of());\n"
288+
+ " if (!repeatableNested.isEmpty()) {\n"
289+
+ " directAnnotations.addAll(extractAnnotations(repeatableNested));\n"
290+
+ " continue;\n"
291+
+ " }\n"
292+
+ "\n"
281293
+ " allTypes.add(annotation.getQualifiedName().toString());\n"
282294
+ " annotations.add(ta);\n"
295+
+ " directAnnotations.add(ta);\n"
283296
+ " everyAnnotation.add(ta);\n"
284297
+ " }\n"
285-
+ " sb.append(element.getSimpleName());\n"
286-
+ " return sb.toString();\n"
298+
+ " return directAnnotations;\n"
287299
+ " }\n"
288300
+ "\n"
289301
+ " @Override\n"
@@ -296,22 +308,15 @@ public static void write(PrintWriter out, String packageName) {
296308
+ " @Override\n"
297309
+ " public StringBuilder visitTypeVariable(TypeVariable t, StringBuilder p) {\n"
298310
+ " kind = t.getKind();\n"
299-
+ " /*\n"
300-
+ " * Types can be recursive so we have to check if we have already done this type.\n"
301-
+ " */\n"
311+
+ " // Types can be recursive so we have to check if we have already done this type.\n"
302312
+ " final String previous = typeVariables.get(t);\n"
303-
+ "\n"
304313
+ " if (previous != null) {\n"
305314
+ " p.append(previous);\n"
306315
+ " return p;\n"
307316
+ " }\n"
308317
+ "\n"
309318
+ " final StringBuilder sb = new StringBuilder();\n"
310-
+ "\n"
311-
+ " /*\n"
312-
+ " * We do not have to print the upper and lower bound as those are defined usually\n"
313-
+ " * on the method.\n"
314-
+ " */\n"
319+
+ " // not printing the upper and lower bound as those are defined usually on the method\n"
315320
+ " if (includeAnnotations) {\n"
316321
+ " for (final var ta : t.getAnnotationMirrors()) {\n"
317322
+ " p.append(ta.toString()).append(\" \");\n"

0 commit comments

Comments
 (0)