From cba6919d6a391491b333bbd737a8d6cb6c6845df Mon Sep 17 00:00:00 2001 From: Tracey Yoshima Date: Thu, 14 Dec 2023 03:06:27 -0700 Subject: [PATCH] Added support for annotations in fully qualified type names. (#3818) * Added support for annotations in fully qualified type names. * Add `J.AnnotatedType#getAllAnnotations()` Also removed some code from `J.MethodDeclaration#getAllAnnotations()` and `J.VariableDeclarations#getAllAnnotations()` as that returned type annotations rather than annotations on the variable / method. --------- Co-authored-by: Knut Wannheden --- .../ReloadableJava11ParserVisitor.java | 46 ++++++++++++++- .../ReloadableJava17ParserVisitor.java | 45 +++++++++++++- .../ReloadableJava21ParserVisitor.java | 46 ++++++++++++++- .../java/ReloadableJava8ParserVisitor.java | 45 +++++++++++++- .../openrewrite/java/tree/AnnotationTest.java | 59 +++++++++++++++++++ .../java/org/openrewrite/java/tree/J.java | 21 +++++-- 6 files changed, 246 insertions(+), 16 deletions(-) diff --git a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java index cfd3a3fafaa..37e70b6c3cb 100644 --- a/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java +++ b/rewrite-java-11/src/main/java/org/openrewrite/java/isolated/ReloadableJava11ParserVisitor.java @@ -1237,11 +1237,51 @@ public J visitTypeCast(TypeCastTree node, Space fmt) { convert(node.getType(), t -> sourceBefore(")"))), convert(node.getExpression())); } - @Override public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) { - return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, convertAll(node.getAnnotations()), - convert(node.getUnderlyingType())); + Map annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations(annotationPosTable), + annotationPosTable.isEmpty() ? convert(node.getUnderlyingType()) : annotatedTypeTree(node.getUnderlyingType(), annotationPosTable)); + } + + private List leadingAnnotations(Map annotationPosTable) { + List annotations = new ArrayList<>(annotationPosTable.size()); + int saveCursor = cursor; + whitespace(); + while (annotationPosTable.containsKey(cursor)) { + JCTree.JCAnnotation jcAnnotation = annotationPosTable.get(cursor); + annotationPosTable.remove(cursor); + cursor = saveCursor; + J.Annotation ann = convert(jcAnnotation); + annotations.add(ann); + saveCursor = cursor; + whitespace(); + } + cursor = saveCursor; + return annotations.isEmpty() ? emptyList() : annotations; + } + + private TypeTree annotatedTypeTree(Tree node, Map annotationPosTable) { + Space prefix = whitespace(); + if (node instanceof JCFieldAccess) { + JCFieldAccess fieldAccess = (JCFieldAccess) node; + JavaType type = typeMapping.type(node); + Expression select = (Expression) annotatedTypeTree(fieldAccess.selected, annotationPosTable); + Space dotPrefix = sourceBefore("."); + List annotations = leadingAnnotations(annotationPosTable); + return new J.FieldAccess(randomId(), prefix, Markers.EMPTY, + select, + padLeft(dotPrefix, new J.Identifier(randomId(), + sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, + annotations, fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + type + ); + } + return convert(node); } @Override diff --git a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java index 7d2363d89ce..906915e43d1 100644 --- a/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java +++ b/rewrite-java-17/src/main/java/org/openrewrite/java/isolated/ReloadableJava17ParserVisitor.java @@ -1315,8 +1315,49 @@ public J visitTypeCast(TypeCastTree node, Space fmt) { @Override public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) { - return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, convertAll(node.getAnnotations()), - convert(node.getUnderlyingType())); + Map annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations(annotationPosTable), + annotationPosTable.isEmpty() ? convert(node.getUnderlyingType()) : annotatedTypeTree(node.getUnderlyingType(), annotationPosTable)); + } + + private List leadingAnnotations(Map annotationPosTable) { + List annotations = new ArrayList<>(annotationPosTable.size()); + int saveCursor = cursor; + whitespace(); + while (annotationPosTable.containsKey(cursor)) { + JCTree.JCAnnotation jcAnnotation = annotationPosTable.get(cursor); + annotationPosTable.remove(cursor); + cursor = saveCursor; + J.Annotation ann = convert(jcAnnotation); + annotations.add(ann); + saveCursor = cursor; + whitespace(); + } + cursor = saveCursor; + return annotations.isEmpty() ? emptyList() : annotations; + } + + private TypeTree annotatedTypeTree(Tree node, Map annotationPosTable) { + Space prefix = whitespace(); + if (node instanceof JCFieldAccess) { + JCFieldAccess fieldAccess = (JCFieldAccess) node; + JavaType type = typeMapping.type(node); + Expression select = (Expression) annotatedTypeTree(fieldAccess.selected, annotationPosTable); + Space dotPrefix = sourceBefore("."); + List annotations = leadingAnnotations(annotationPosTable); + return new J.FieldAccess(randomId(), prefix, Markers.EMPTY, + select, + padLeft(dotPrefix, new J.Identifier(randomId(), + sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, + annotations, fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + type + ); + } + return convert(node); } @Override diff --git a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java index f4af0ebc1d7..a67815b2e38 100644 --- a/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java +++ b/rewrite-java-21/src/main/java/org/openrewrite/java/isolated/ReloadableJava21ParserVisitor.java @@ -1314,9 +1314,49 @@ public J visitTypeCast(TypeCastTree node, Space fmt) { } @Override - public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) { - return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, convertAll(node.getAnnotations()), - convert(node.getUnderlyingType())); + public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) {Map annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations(annotationPosTable), + annotationPosTable.isEmpty() ? convert(node.getUnderlyingType()) : annotatedTypeTree(node.getUnderlyingType(), annotationPosTable)); + } + + private List leadingAnnotations(Map annotationPosTable) { + List annotations = new ArrayList<>(annotationPosTable.size()); + int saveCursor = cursor; + whitespace(); + while (annotationPosTable.containsKey(cursor)) { + JCTree.JCAnnotation jcAnnotation = annotationPosTable.get(cursor); + annotationPosTable.remove(cursor); + cursor = saveCursor; + J.Annotation ann = convert(jcAnnotation); + annotations.add(ann); + saveCursor = cursor; + whitespace(); + } + cursor = saveCursor; + return annotations.isEmpty() ? emptyList() : annotations; + } + + private TypeTree annotatedTypeTree(Tree node, Map annotationPosTable) { + Space prefix = whitespace(); + if (node instanceof JCFieldAccess) { + JCFieldAccess fieldAccess = (JCFieldAccess) node; + JavaType type = typeMapping.type(node); + Expression select = (Expression) annotatedTypeTree(fieldAccess.selected, annotationPosTable); + Space dotPrefix = sourceBefore("."); + List annotations = leadingAnnotations(annotationPosTable); + return new J.FieldAccess(randomId(), prefix, Markers.EMPTY, + select, + padLeft(dotPrefix, new J.Identifier(randomId(), + sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, + annotations, fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + type + ); + } + return convert(node); } @Override diff --git a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java index b99c6993828..74e576728a1 100644 --- a/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java +++ b/rewrite-java-8/src/main/java/org/openrewrite/java/ReloadableJava8ParserVisitor.java @@ -1231,8 +1231,49 @@ public J visitTypeCast(TypeCastTree node, Space fmt) { @Override public J visitAnnotatedType(AnnotatedTypeTree node, Space fmt) { - return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, convertAll(node.getAnnotations()), - convert(node.getUnderlyingType())); + Map annotationPosTable = new HashMap<>(); + for (AnnotationTree annotationNode : node.getAnnotations()) { + JCAnnotation annotation = (JCAnnotation) annotationNode; + annotationPosTable.put(annotation.pos, annotation); + } + return new J.AnnotatedType(randomId(), fmt, Markers.EMPTY, leadingAnnotations(annotationPosTable), + annotationPosTable.isEmpty() ? convert(node.getUnderlyingType()) : annotatedTypeTree(node.getUnderlyingType(), annotationPosTable)); + } + + private List leadingAnnotations(Map annotationPosTable) { + List annotations = new ArrayList<>(annotationPosTable.size()); + int saveCursor = cursor; + whitespace(); + while (annotationPosTable.containsKey(cursor)) { + JCTree.JCAnnotation jcAnnotation = annotationPosTable.get(cursor); + annotationPosTable.remove(cursor); + cursor = saveCursor; + J.Annotation ann = convert(jcAnnotation); + annotations.add(ann); + saveCursor = cursor; + whitespace(); + } + cursor = saveCursor; + return annotations.isEmpty() ? emptyList() : annotations; + } + + private TypeTree annotatedTypeTree(Tree node, Map annotationPosTable) { + Space prefix = whitespace(); + if (node instanceof JCFieldAccess) { + JCFieldAccess fieldAccess = (JCFieldAccess) node; + JavaType type = typeMapping.type(node); + Expression select = (Expression) annotatedTypeTree(fieldAccess.selected, annotationPosTable); + Space dotPrefix = sourceBefore("."); + List annotations = leadingAnnotations(annotationPosTable); + return new J.FieldAccess(randomId(), prefix, Markers.EMPTY, + select, + padLeft(dotPrefix, new J.Identifier(randomId(), + sourceBefore(fieldAccess.name.toString()), Markers.EMPTY, + annotations, fieldAccess.name.toString(), type, typeMapping.variableType(fieldAccess.sym))), + type + ); + } + return convert(node); } @Override diff --git a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java index 76ce5b5dc80..0de37661b7c 100644 --- a/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java +++ b/rewrite-java-tck/src/main/java/org/openrewrite/java/tree/AnnotationTest.java @@ -274,7 +274,66 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ob }.visit(cu, 0)) ) ); + } + @Issue("https://github.com/openrewrite/rewrite/issues/3683") + @Test + void annotationAfterVariableTypePackageName() { + rewriteRun( + java( + """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Retention; + import java.lang.annotation.RetentionPolicy; + import java.lang.annotation.Target; + import java.util.List; + + import static java.lang.annotation.ElementType.*; + + public class A { + @Leading java. util. @Multi1 @Multi2 List l; + @Leading java. util. @Multi1 @Multi2 List m() { return null; } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(value={FIELD, METHOD}) + public @interface Leading {} + + @Retention(RetentionPolicy.RUNTIME) + @Target(value=TYPE_USE) + public @interface Multi1 {} + + @Retention(RetentionPolicy.RUNTIME) + @Target(value=TYPE_USE) + public @interface Multi2 {} + """, + spec -> spec.afterRecipe(cu -> { + J.VariableDeclarations field = (J.VariableDeclarations) cu.getClasses().get(0).getBody().getStatements().get(0); + assertThat(field.getAllAnnotations()).satisfiesExactly( + leading -> assertThat(leading.getSimpleName()).isEqualTo("Leading") + ); + J.ParameterizedType fieldType = (J.ParameterizedType) field.getTypeExpression(); + assertThat(fieldType).isNotNull(); + J.AnnotatedType annotatedType = (J.AnnotatedType) fieldType.getClazz(); + assertThat(annotatedType.getAllAnnotations()).satisfiesExactly( + multi1 -> assertThat(multi1.getSimpleName()).isEqualTo("Multi1"), + multi2 -> assertThat(multi2.getSimpleName()).isEqualTo("Multi2") + ); + + J.MethodDeclaration method = (J.MethodDeclaration) cu.getClasses().get(0).getBody().getStatements().get(1); + assertThat(method.getAllAnnotations()).satisfiesExactly( + leading -> assertThat(leading.getSimpleName()).isEqualTo("Leading") + ); + J.ParameterizedType returnType = (J.ParameterizedType) method.getReturnTypeExpression(); + assertThat(returnType).isNotNull(); + annotatedType = (J.AnnotatedType) returnType.getClazz(); + assertThat(annotatedType.getAllAnnotations()).satisfiesExactly( + multi1 -> assertThat(multi1.getSimpleName()).isEqualTo("Multi1"), + multi2 -> assertThat(multi2.getSimpleName()).isEqualTo("Multi2") + ); + }) + ) + ); } } diff --git a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java index 2684a452497..724e3ab7a3d 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/tree/J.java @@ -134,6 +134,21 @@ public AnnotatedType withType(@Nullable JavaType type) { return withTypeExpression(typeExpression.withType(type)); } + public List getAllAnnotations() { + List allAnnotations = annotations; + List moreAnnotations; + if (typeExpression instanceof FieldAccess && + !(moreAnnotations = ((FieldAccess) typeExpression).getName().getAnnotations()).isEmpty()) { + if (allAnnotations.isEmpty()) { + allAnnotations = moreAnnotations; + } else { + allAnnotations = new ArrayList<>(annotations); + allAnnotations.addAll(moreAnnotations); + } + } + return allAnnotations; + } + @Override public

J acceptJava(JavaVisitor

v, P p) { return v.visitAnnotatedType(this, p); @@ -3626,9 +3641,6 @@ public List getAllAnnotations() { if (typeParameters != null) { allAnnotations.addAll(typeParameters.getAnnotations()); } - if (returnTypeExpression instanceof AnnotatedType) { - allAnnotations.addAll(((AnnotatedType) returnTypeExpression).getAnnotations()); - } allAnnotations.addAll(name.getAnnotations()); return allAnnotations; } @@ -5661,9 +5673,6 @@ public List getAllAnnotations() { for (J.Modifier modifier : modifiers) { allAnnotations.addAll(modifier.getAnnotations()); } - if (typeExpression != null && typeExpression instanceof J.AnnotatedType) { - allAnnotations.addAll(((J.AnnotatedType) typeExpression).getAnnotations()); - } return allAnnotations; }