From 33813af003e203f4e04b423c4f0c66e3924572fd Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 24 Jan 2025 09:32:20 +0100 Subject: [PATCH 1/2] Support annotations on nested classes in `AnnotationTemplateGenerator` --- .../java/JavaTemplateAnnotationTest.java | 35 +++++++++- .../template/AnnotationTemplateGenerator.java | 65 +++++++++++-------- 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateAnnotationTest.java b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateAnnotationTest.java index 6629eea4490..9b174dd6307 100644 --- a/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateAnnotationTest.java +++ b/rewrite-java-test/src/test/java/org/openrewrite/java/JavaTemplateAnnotationTest.java @@ -33,7 +33,7 @@ class JavaTemplateAnnotationTest implements RewriteTest { @DocumentExample @Test - void replaceAnnotation() { + void replaceClassAnnotation() { rewriteRun( spec -> spec.expectedCyclesThatMakeChanges(2) .recipe(toRecipe(() -> new JavaVisitor<>() { @@ -49,7 +49,7 @@ public J visitAnnotation(J.Annotation annotation, ExecutionContext executionCont @Deprecated(since = "1.0", forRemoval = true) class A { } - """, + """, """ @Deprecated(since = "2.0", forRemoval = true) class A { @@ -59,6 +59,37 @@ class A { ); } + @Test + void replaceNestedClassAnnotation() { + rewriteRun( + spec -> spec.expectedCyclesThatMakeChanges(2) + .recipe(toRecipe(() -> new JavaVisitor<>() { + @Override + public J visitAnnotation(J.Annotation annotation, ExecutionContext executionContext) { + return JavaTemplate.apply("@Deprecated(since = \"#{}\", forRemoval = true)", + getCursor(), annotation.getCoordinates().replace(), "2.0"); + } + } + )), + java( + """ + class A { + @Deprecated(since = "1.0", forRemoval = true) + class B { + } + } + """, + """ + class A { + @Deprecated(since = "2.0", forRemoval = true) + class B { + } + } + """ + ) + ); + } + @ExpectedToFail @Test void replaceAnnotation2() { diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java index 45f9083bb2a..0ab81f71108 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java @@ -22,6 +22,7 @@ import org.openrewrite.Cursor; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.service.AnnotationService; import org.openrewrite.java.tree.*; import java.util.ArrayList; @@ -70,7 +71,7 @@ public String template(Cursor cursor, String template) { StringBuilder before = new StringBuilder(); StringBuilder after = new StringBuilder(); - template(next(cursor), cursor.getValue(), before, after, newSetFromMap(new IdentityHashMap<>())); + template(next(cursor), cursor.getValue(), before, after, newSetFromMap(new IdentityHashMap<>()), cursor.getValue()); J j = cursor.getValue(); J annotationParent = j instanceof J.Annotation && cursor.getParent() != null ? cursor.getParent().firstEnclosing(J.class) : null; @@ -115,9 +116,9 @@ public J.Annotation visitAnnotation(J.Annotation annotation, Integer integer) { return annotations; } - private void template(Cursor cursor, J prior, StringBuilder before, StringBuilder after, Set templated) { + private void template(Cursor cursor, J prior, StringBuilder before, StringBuilder after, Set templated, J.Annotation annotation) { templated.add(cursor.getValue()); - J j = cursor.getValue(); + final J j = cursor.getValue(); if (j instanceof JavaSourceFile) { JavaSourceFile cu = (JavaSourceFile) j; for (J.Import anImport : cu.getImports()) { @@ -132,16 +133,14 @@ private void template(Cursor cursor, J prior, StringBuilder before, StringBuilde } List classes = cu.getClasses(); if (!classes.get(classes.size() - 1).getName().getSimpleName().equals("$Placeholder")) { - after.append("@interface $Placeholder {}"); + after.append("\n@interface $Placeholder {}"); } return; - } - if (j instanceof J.Block) { + } else if (j instanceof J.ClassDeclaration) { + classDeclaration(before, after, (J.ClassDeclaration) j, templated, cursor, annotation); + } else if (j instanceof J.Block) { J parent = next(cursor).getValue(); - if (parent instanceof J.ClassDeclaration) { - classDeclaration(before, (J.ClassDeclaration) parent, templated, cursor); - after.append('}'); - } else if (parent instanceof J.MethodDeclaration) { + if (parent instanceof J.MethodDeclaration) { J.MethodDeclaration m = (J.MethodDeclaration) parent; // variable declarations up to the point of insertion @@ -197,32 +196,46 @@ private void template(Cursor cursor, J prior, StringBuilder before, StringBuilde after.append("};"); } - template(next(cursor), j, before, after, templated); + template(next(cursor), j, before, after, templated, annotation); } - private void classDeclaration(StringBuilder before, J.ClassDeclaration parent, Set templated, Cursor cursor) { + private void classDeclaration(StringBuilder before, StringBuilder after, J.ClassDeclaration parent, Set templated, Cursor cursor, J.Annotation annotation) { J.ClassDeclaration c = parent; - for (Statement statement : c.getBody().getStatements()) { - if (templated.contains(statement)) { - continue; - } + boolean annotated = isAnnotated(cursor, annotation); + if (!annotated) { + for (Statement statement : c.getBody().getStatements()) { + if (templated.contains(statement)) { + continue; + } - if (statement instanceof J.VariableDeclarations) { - J.VariableDeclarations v = (J.VariableDeclarations) statement; - if (v.hasModifier(J.Modifier.Type.Final) && v.hasModifier(J.Modifier.Type.Static)) { - before.insert(0, variable((J.VariableDeclarations) statement, cursor) + ";\n"); + if (statement instanceof J.VariableDeclarations) { + J.VariableDeclarations v = (J.VariableDeclarations) statement; + if (v.hasModifier(J.Modifier.Type.Final) && v.hasModifier(J.Modifier.Type.Static)) { + before.insert(0, variable((J.VariableDeclarations) statement, cursor) + ";\n"); + } + } else if (statement instanceof J.ClassDeclaration) { + // this is a sibling class. we need declarations for all variables and methods. + // setting prior to null will cause them all to be written. + before.insert(0, '}'); + classDeclaration(before, after, (J.ClassDeclaration) statement, templated, cursor, annotation); } - } else if (statement instanceof J.ClassDeclaration) { - // this is a sibling class. we need declarations for all variables and methods. - // setting prior to null will cause them all to be written. - before.insert(0, '}'); - classDeclaration(before, (J.ClassDeclaration) statement, templated, cursor); } } c = c.withBody(J.Block.createEmptyBlock()).withLeadingAnnotations(null).withPrefix(Space.EMPTY); String printed = c.printTrimmed(cursor); int braceIndex = printed.lastIndexOf('{'); - before.insert(0, braceIndex == -1 ? printed + '{' : printed.substring(0, braceIndex + 1)); + if (annotated) { + after.append(braceIndex == -1 ? printed + '{' : printed.substring(0, braceIndex + 1)); + } else { + before.insert(0, braceIndex == -1 ? printed + '{' : printed.substring(0, braceIndex + 1)); + } + after.append('}'); + } + + private static boolean isAnnotated(Cursor cursor, J.Annotation annotation) { + Cursor sourceFileCursor = cursor.dropParentUntil(is -> is instanceof JavaSourceFile); + AnnotationService annotationService = sourceFileCursor.getValue().service(AnnotationService.class); + return annotationService.getAllAnnotations(cursor).contains(annotation); } private String variable(J.VariableDeclarations variable, Cursor cursor) { From 88d5a10c8668c513cd50f8a1ec2105895cedbfa1 Mon Sep 17 00:00:00 2001 From: Knut Wannheden Date: Fri, 24 Jan 2025 10:44:00 +0100 Subject: [PATCH 2/2] Fix --- .../template/AnnotationTemplateGenerator.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java index 0ab81f71108..7470f0ecaef 100644 --- a/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java +++ b/rewrite-java/src/main/java/org/openrewrite/java/internal/template/AnnotationTemplateGenerator.java @@ -71,7 +71,7 @@ public String template(Cursor cursor, String template) { StringBuilder before = new StringBuilder(); StringBuilder after = new StringBuilder(); - template(next(cursor), cursor.getValue(), before, after, newSetFromMap(new IdentityHashMap<>()), cursor.getValue()); + template(next(cursor), cursor.getValue(), before, after, newSetFromMap(new IdentityHashMap<>())); J j = cursor.getValue(); J annotationParent = j instanceof J.Annotation && cursor.getParent() != null ? cursor.getParent().firstEnclosing(J.class) : null; @@ -116,7 +116,7 @@ public J.Annotation visitAnnotation(J.Annotation annotation, Integer integer) { return annotations; } - private void template(Cursor cursor, J prior, StringBuilder before, StringBuilder after, Set templated, J.Annotation annotation) { + private void template(Cursor cursor, J prior, StringBuilder before, StringBuilder after, Set templated) { templated.add(cursor.getValue()); final J j = cursor.getValue(); if (j instanceof JavaSourceFile) { @@ -137,7 +137,7 @@ private void template(Cursor cursor, J prior, StringBuilder before, StringBuilde } return; } else if (j instanceof J.ClassDeclaration) { - classDeclaration(before, after, (J.ClassDeclaration) j, templated, cursor, annotation); + classDeclaration(before, after, (J.ClassDeclaration) j, templated, cursor, prior); } else if (j instanceof J.Block) { J parent = next(cursor).getValue(); if (parent instanceof J.MethodDeclaration) { @@ -196,12 +196,12 @@ private void template(Cursor cursor, J prior, StringBuilder before, StringBuilde after.append("};"); } - template(next(cursor), j, before, after, templated, annotation); + template(next(cursor), j, before, after, templated); } - private void classDeclaration(StringBuilder before, StringBuilder after, J.ClassDeclaration parent, Set templated, Cursor cursor, J.Annotation annotation) { + private void classDeclaration(StringBuilder before, StringBuilder after, J.ClassDeclaration parent, Set templated, Cursor cursor, J prior) { J.ClassDeclaration c = parent; - boolean annotated = isAnnotated(cursor, annotation); + boolean annotated = isAnnotated(cursor, prior); if (!annotated) { for (Statement statement : c.getBody().getStatements()) { if (templated.contains(statement)) { @@ -217,7 +217,7 @@ private void classDeclaration(StringBuilder before, StringBuilder after, J.Class // this is a sibling class. we need declarations for all variables and methods. // setting prior to null will cause them all to be written. before.insert(0, '}'); - classDeclaration(before, after, (J.ClassDeclaration) statement, templated, cursor, annotation); + classDeclaration(before, after, (J.ClassDeclaration) statement, templated, cursor, prior); } } } @@ -232,10 +232,13 @@ private void classDeclaration(StringBuilder before, StringBuilder after, J.Class after.append('}'); } - private static boolean isAnnotated(Cursor cursor, J.Annotation annotation) { + private static boolean isAnnotated(Cursor cursor, J maybeAnnotation) { + if (!(maybeAnnotation instanceof J.Annotation)) { + return false; + } Cursor sourceFileCursor = cursor.dropParentUntil(is -> is instanceof JavaSourceFile); AnnotationService annotationService = sourceFileCursor.getValue().service(AnnotationService.class); - return annotationService.getAllAnnotations(cursor).contains(annotation); + return annotationService.getAllAnnotations(cursor).contains(maybeAnnotation); } private String variable(J.VariableDeclarations variable, Cursor cursor) {