Skip to content

AnnotationTemplateGenerator does not handle nested annotations correctly #5712

@steve-aom-elliott

Description

@steve-aom-elliott

What version of OpenRewrite are you using?

  • OpenRewrite v8.56.1 (but really latest commit)
  • rewrite-openapi v0.21.0 (but really latest commit)

How are you running OpenRewrite?

I am debugging tests within rewrite-openapi directly, but the logic issue appears to be within rewrite.

What is the smallest, simplest way to reproduce the problem?

@Test
    void replaceNestedAnnotation() {
        rewriteRun(
          spec -> spec.recipe(toRecipe(() -> new JavaIsoVisitor<>() {
              private AnnotationMatcher ANNOTATION_MATCHER = new AnnotationMatcher("@NestedAnnotation");
              public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
                  J.Annotation an = super.visitAnnotation(annotation, ctx);
                  if (!ANNOTATION_MATCHER.matches(an) || an.getArguments().isEmpty()) {
                      return an;
                  }
                  J.Assignment assignment = (J.Assignment) an.getArguments().get(0);
                  if (assignment.getVariable() instanceof J.Identifier && ((J.Identifier) assignment.getVariable()).getSimpleName().equals("a")) {
                      J.Identifier id = (J.Identifier) assignment.getVariable();
                      return JavaTemplate.builder("#{any()}")
                        .build()
                        .apply(
                          getCursor(),
                          an.getCoordinates().replaceArguments(),
                          assignment.withVariable(id.withSimpleName("b"))
                        );
                  return an;
              }
          })).parser(JavaParser.fromJavaVersion()
            .dependsOn(
              """
                import java.lang.annotation.Repeatable;
                @Repeatable(NestedAnnotations.class)
                @interface NestedAnnotation {
                  String a() default "";
                }
                """,
              """
                @interface NestedAnnotations {
                  NestedAnnotation[] value() default {};
                }
                """
            )
          ),
          //language=java
          java(
            """
              class A {
                @NestedAnnotations(value = {
                  @NestedAnnotation(a = "first"),
                  @NestedAnnotation(a = "second")
                })
                void method() {}
              }
              """,
            """
              class A {
                @NestedAnnotations(value = {
                  @NestedAnnotation(b = "first"),
                  @NestedAnnotation(b = "second")
                })
                void method() {}
              }
              """
          )
        );
    }

It's necessary for this issue for it to use JavaTemplate, so the existing recipe for ChangeAnnotationAttributeName will not trigger this, for example.

What is the full stack trace of any errors you encountered?

java.lang.AssertionError: Failed to run recipe at Cursor{Annotation->JRightPadded(element=@NestedAnnotation(a = "first"), after=Space(comments=<0 comments>, whitespace=<empty>))->JContainer(before=Space(comments=<0 comments>, whitespace=<empty>), elementCount=2)->NewArray->JLeftPadded(before=Space(comments=<0 comments>, whitespace='·₁'), element=J.NewArray(padding=org.openrewrite.java.tree.J$NewArray$Padding@71ea1fda, id=48b94f48-dce9-4b65-8bcb-a02db5d6502c, prefix=Space(comments=<0 comments>, whitespace='·₁'), markers=Markers(id=dbedc218-2180-4b2c-8c72-fb033783d2fc, markers=[]), typeExpression=null, dimensions=[], initializer=[@NestedAnnotation(a = "first"), @NestedAnnotation(a = "second")], type=NestedAnnotation[]))->Assignment->JRightPadded(element=value = {
    @NestedAnnotation(a = "first"),
    @NestedAnnotation(a = "second")
  }, after=Space(comments=<0 comments>, whitespace=<empty>))->JContainer(before=Space(comments=<0 comments>, whitespace=<empty>), elementCount=1)->Annotation->MethodDeclaration->JRightPadded(element=MethodDeclaration{A{name=method,return=void,parameters=[]}}, after=Space(comments=<0 comments>, whitespace=<empty>))->Block->ClassDeclaration->CompilationUnit->root}

	at org.openrewrite.test.RewriteTest.lambda$defaultExecutionContext$14(RewriteTest.java:640)
	at org.openrewrite.scheduling.RecipeRunCycle.handleError(RecipeRunCycle.java:291)
	at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$8(RecipeRunCycle.java:230)
	at org.openrewrite.scheduling.RecipeStack.reduce(RecipeStack.java:60)
	at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$9(RecipeRunCycle.java:179)
	at org.openrewrite.internal.InMemoryLargeSourceSet.lambda$edit$0(InMemoryLargeSourceSet.java:83)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:244)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:267)
	at org.openrewrite.internal.InMemoryLargeSourceSet.edit(InMemoryLargeSourceSet.java:82)
	at org.openrewrite.scheduling.RecipeRunCycle.editSources(RecipeRunCycle.java:177)
	at org.openrewrite.RecipeScheduler.runRecipeCycles(RecipeScheduler.java:84)
	at org.openrewrite.RecipeScheduler.scheduleRun(RecipeScheduler.java:41)
	at org.openrewrite.Recipe.run(Recipe.java:441)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:377)
	at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:130)
	at org.openrewrite.java.JavaTemplateAnnotationTest.replaceNestedAnnotation(JavaTemplateAnnotationTest.java:125)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
Caused by: org.openrewrite.internal.RecipeRunException: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:281)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:154)
	at org.openrewrite.java.JavaTemplate.apply(JavaTemplate.java:120)
	at org.openrewrite.java.JavaTemplateAnnotationTest$4.visitAnnotation(JavaTemplateAnnotationTest.java:135)
	at org.openrewrite.java.JavaTemplateAnnotationTest$4.visitAnnotation(JavaTemplateAnnotationTest.java:126)
	at org.openrewrite.java.tree.J$Annotation.acceptJava(J.java:244)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.visitRightPadded(JavaVisitor.java:1374)
	at org.openrewrite.java.JavaVisitor.lambda$visitContainer$34(JavaVisitor.java:1424)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:244)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:267)
	at org.openrewrite.java.JavaVisitor.visitContainer(JavaVisitor.java:1424)
	at org.openrewrite.java.JavaVisitor.visitNewArray(JavaVisitor.java:975)
	at org.openrewrite.java.JavaIsoVisitor.visitNewArray(JavaIsoVisitor.java:259)
	at org.openrewrite.java.JavaIsoVisitor.visitNewArray(JavaIsoVisitor.java:30)
	at org.openrewrite.java.tree.J$NewArray.acceptJava(J.java:4578)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.visitLeftPadded(JavaVisitor.java:1402)
	at org.openrewrite.java.JavaVisitor.visitAssignment(JavaVisitor.java:343)
	at org.openrewrite.java.JavaIsoVisitor.visitAssignment(JavaIsoVisitor.java:73)
	at org.openrewrite.java.JavaIsoVisitor.visitAssignment(JavaIsoVisitor.java:30)
	at org.openrewrite.java.tree.J$Assignment.acceptJava(J.java:509)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.visitRightPadded(JavaVisitor.java:1374)
	at org.openrewrite.java.JavaVisitor.lambda$visitContainer$34(JavaVisitor.java:1424)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:244)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:267)
	at org.openrewrite.java.JavaVisitor.visitContainer(JavaVisitor.java:1424)
	at org.openrewrite.java.JavaVisitor.visitAnnotation(JavaVisitor.java:256)
	at org.openrewrite.java.JavaIsoVisitor.visitAnnotation(JavaIsoVisitor.java:48)
	at org.openrewrite.java.JavaTemplateAnnotationTest$4.visitAnnotation(JavaTemplateAnnotationTest.java:129)
	at org.openrewrite.java.JavaTemplateAnnotationTest$4.visitAnnotation(JavaTemplateAnnotationTest.java:126)
	at org.openrewrite.java.tree.J$Annotation.acceptJava(J.java:244)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.lambda$visitMethodDeclaration$17(JavaVisitor.java:860)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:244)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:267)
	at org.openrewrite.java.JavaVisitor.visitMethodDeclaration(JavaVisitor.java:860)
	at org.openrewrite.java.JavaIsoVisitor.visitMethodDeclaration(JavaIsoVisitor.java:234)
	at org.openrewrite.java.JavaIsoVisitor.visitMethodDeclaration(JavaIsoVisitor.java:30)
	at org.openrewrite.java.tree.J$MethodDeclaration.acceptJava(J.java:4036)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.visitRightPadded(JavaVisitor.java:1374)
	at org.openrewrite.java.JavaVisitor.lambda$visitBlock$4(JavaVisitor.java:400)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:244)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:267)
	at org.openrewrite.java.JavaVisitor.visitBlock(JavaVisitor.java:399)
	at org.openrewrite.java.JavaIsoVisitor.visitBlock(JavaIsoVisitor.java:88)
	at org.openrewrite.java.JavaIsoVisitor.visitBlock(JavaIsoVisitor.java:30)
	at org.openrewrite.java.tree.J$Block.acceptJava(J.java:848)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.visitClassDeclaration(JavaVisitor.java:486)
	at org.openrewrite.java.JavaIsoVisitor.visitClassDeclaration(JavaIsoVisitor.java:108)
	at org.openrewrite.java.JavaIsoVisitor.visitClassDeclaration(JavaIsoVisitor.java:30)
	at org.openrewrite.java.tree.J$ClassDeclaration.acceptJava(J.java:1385)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visitAndCast(TreeVisitor.java:311)
	at org.openrewrite.java.JavaVisitor.lambda$visitCompilationUnit$9(JavaVisitor.java:499)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:244)
	at org.openrewrite.internal.ListUtils.map(ListUtils.java:267)
	at org.openrewrite.java.JavaVisitor.visitCompilationUnit(JavaVisitor.java:499)
	at org.openrewrite.java.JavaIsoVisitor.visitCompilationUnit(JavaIsoVisitor.java:113)
	at org.openrewrite.java.JavaIsoVisitor.visitCompilationUnit(JavaIsoVisitor.java:30)
	at org.openrewrite.java.tree.J$CompilationUnit.acceptJava(J.java:1667)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:154)
	at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$7(RecipeRunCycle.java:210)
	at io.micrometer.core.instrument.AbstractTimer.recordCallable(AbstractTimer.java:147)
	at org.openrewrite.table.RecipeRunStats.recordEdit(RecipeRunStats.java:74)
	at org.openrewrite.scheduling.RecipeRunCycle.lambda$editSources$8(RecipeRunCycle.java:206)
	... 16 more
Caused by: java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
	at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
	at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
	at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
	at java.base/java.util.Objects.checkIndex(Objects.java:385)
	at java.base/java.util.ArrayList.get(ArrayList.java:427)
	at org.openrewrite.java.internal.template.JavaTemplateJavaExtension$1.visitAnnotation(JavaTemplateJavaExtension.java:73)
	at org.openrewrite.java.internal.template.JavaTemplateJavaExtension$1.visitAnnotation(JavaTemplateJavaExtension.java:56)
	at org.openrewrite.java.tree.J$Annotation.acceptJava(J.java:244)
	at org.openrewrite.java.tree.J.accept(J.java:60)
	at org.openrewrite.TreeVisitor.visit(TreeVisitor.java:245)
	... 99 more

In essence, what I believe is happening is that when it tries to build the template back for the annotation (around here), it isn't finding a typical annotation target (class, method, field) as a direct parent, it's skipping adding parts of the template, resulting in a class wrapping an annotation that doesn't annotate anything, at least for this example. When it later tries to parse the annotations from the substituted template (around here), it's getting back 0 annotations, and then causing an IndexOutOfBoundsException.

Are you interested in contributing a fix to OpenRewrite?

Uncertain at this time if this is beyond my current knowledge to fix correctly, and with it being very core to how the templating works, will likely need assistance if I do move forward with it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingjava

    Type

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions