Skip to content

Commit 7df37a3

Browse files
committed
Validation and Quickfix for RegistrarBean
1 parent 0ca0e8c commit 7df37a3

File tree

18 files changed

+1435
-8
lines changed

18 files changed

+1435
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.commons.rewrite.java;
12+
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
16+
import org.openrewrite.Cursor;
17+
import org.openrewrite.ExecutionContext;
18+
import org.openrewrite.NlsRewrite.Description;
19+
import org.openrewrite.NlsRewrite.DisplayName;
20+
import org.openrewrite.Preconditions;
21+
import org.openrewrite.Recipe;
22+
import org.openrewrite.Tree;
23+
import org.openrewrite.TreeVisitor;
24+
import org.openrewrite.internal.ListUtils;
25+
import org.openrewrite.java.AnnotationMatcher;
26+
import org.openrewrite.java.JavaIsoVisitor;
27+
import org.openrewrite.java.search.DeclaresType;
28+
import org.openrewrite.java.tree.Expression;
29+
import org.openrewrite.java.tree.J;
30+
import org.openrewrite.java.tree.J.Annotation;
31+
import org.openrewrite.java.tree.J.ClassDeclaration;
32+
import org.openrewrite.java.tree.J.NewArray;
33+
import org.openrewrite.java.tree.JLeftPadded;
34+
import org.openrewrite.java.tree.JavaType;
35+
import org.openrewrite.java.tree.JavaType.FullyQualified;
36+
import org.openrewrite.java.tree.JavaType.ShallowClass;
37+
import org.openrewrite.java.tree.Space;
38+
import org.openrewrite.java.tree.TypeTree;
39+
import org.openrewrite.java.tree.TypeUtils;
40+
import org.openrewrite.marker.Markers;
41+
42+
import com.fasterxml.jackson.annotation.JsonCreator;
43+
import com.fasterxml.jackson.annotation.JsonProperty;
44+
45+
public class ImportBeanRegistrarInConfigRecipe extends Recipe {
46+
47+
private static final String IMPORT_FQN = "org.springframework.context.annotation.Import";
48+
49+
private String configBeanFqn;
50+
51+
private String beanRegFqn;
52+
53+
@JsonCreator
54+
public ImportBeanRegistrarInConfigRecipe(
55+
@JsonProperty("configBeanFqn") String configBeanFqn,
56+
@JsonProperty("beanRegFqn") String beanRegFqn) {
57+
this.configBeanFqn = configBeanFqn;
58+
this.beanRegFqn = beanRegFqn;
59+
}
60+
61+
@Override
62+
public @DisplayName String getDisplayName() {
63+
return "Add `BeanRegistrar` with `@Import` in Configuration bean";
64+
}
65+
66+
@Override
67+
public @Description String getDescription() {
68+
return "Add `BeanRegistrar` with `@Import` in Configuration bean.";
69+
}
70+
71+
@Override
72+
public TreeVisitor<?, ExecutionContext> getVisitor() {
73+
final AnnotationMatcher importAnnotationMatcher = new AnnotationMatcher("@" + IMPORT_FQN);
74+
final ShallowClass beanRegistrarType = JavaType.ShallowClass.build(beanRegFqn);
75+
return Preconditions.check(new DeclaresType<>(configBeanFqn), new JavaIsoVisitor<>() {
76+
77+
@Override
78+
public ClassDeclaration visitClassDeclaration(ClassDeclaration classDecl, ExecutionContext p) {
79+
ClassDeclaration cd = classDecl;
80+
FullyQualified type = TypeUtils.asFullyQualified(cd.getType());
81+
82+
if (type != null && configBeanFqn.equals(type.getFullyQualifiedName())) {
83+
J.Annotation importAnnotation = cd.getLeadingAnnotations().stream().filter(importAnnotationMatcher::matches).findFirst().orElse(null);
84+
if (importAnnotation == null) {
85+
ArrayList<J.Annotation> annotations = new ArrayList<>(cd.getLeadingAnnotations());
86+
JavaType.ShallowClass annotationType = JavaType.ShallowClass.build(IMPORT_FQN);
87+
J.Identifier typeName = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), annotationType.getClassName(), annotationType, null);
88+
Space indent = Space.build("\n" + cd.getPrefix().getIndent(), List.of());
89+
boolean noAnnotations = annotations.isEmpty();
90+
importAnnotation = new J.Annotation(
91+
Tree.randomId(),
92+
noAnnotations ? Space.EMPTY : indent,
93+
Markers.EMPTY,
94+
typeName,
95+
null);
96+
annotations.add(importAnnotation);
97+
cd = cd.withLeadingAnnotations(annotations);
98+
if (noAnnotations) {
99+
cd = cd.getPadding().withKind(cd.getPadding().getKind().withPrefix(indent));
100+
}
101+
maybeAddImport(IMPORT_FQN);
102+
}
103+
}
104+
return super.visitClassDeclaration(cd, p);
105+
}
106+
107+
@Override
108+
public Annotation visitAnnotation(Annotation annotation, ExecutionContext p) {
109+
Annotation an = super.visitAnnotation(annotation, p);
110+
FullyQualified anType = TypeUtils.asFullyQualified(an.getType());
111+
if (anType != null && IMPORT_FQN.equals(anType.getFullyQualifiedName())) {
112+
Cursor parentCursor = getCursor().getParentTreeCursor();
113+
if (parentCursor != null && parentCursor.getValue() instanceof J.ClassDeclaration cd
114+
&& cd.getType() != null && configBeanFqn.equals(cd.getType().getFullyQualifiedName())) {
115+
List<Expression> currentArgs = an.getArguments();
116+
if (currentArgs == null || currentArgs.isEmpty() || currentArgs.get(0) instanceof J.Empty) {
117+
an = an.withArguments(List.of(createBeanRegistrarClassFieldAccess(beanRegistrarType)));
118+
maybeAddImport(beanRegistrarType);
119+
} else {
120+
List<Expression> newArgs = ListUtils.map(currentArgs, (i, arg) -> {
121+
if (arg instanceof J.FieldAccess fa) {
122+
if (!isBeanRegistrar(fa)) {
123+
// Turn into array
124+
NewArray array = new J.NewArray(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, List.of(), null, null);
125+
array = array.withInitializer(List.of(
126+
arg,
127+
createBeanRegistrarClassFieldAccess(beanRegistrarType)
128+
));
129+
maybeAddImport(beanRegistrarType);
130+
return autoFormat(array, p, getCursor());
131+
}
132+
} else if (arg instanceof J.NewArray na) {
133+
List<Expression> cna = new ArrayList<>(na.getInitializer());
134+
boolean hasBeanRegistarar = cna.stream().filter(J.FieldAccess.class::isInstance)
135+
.map(J.FieldAccess.class::cast)
136+
.anyMatch(ImportBeanRegistrarInConfigRecipe.this::isBeanRegistrar);
137+
// Not found bean registrar - add it to the array
138+
if (!hasBeanRegistarar) {
139+
cna.add(createBeanRegistrarClassFieldAccess(beanRegistrarType));
140+
maybeAddImport(beanRegistrarType);
141+
return autoFormat(na.withInitializer(cna), p, getCursor());
142+
}
143+
} else if (arg instanceof J.Assignment assign && assign.getVariable() instanceof J.Identifier ident && "value".equals(ident.getSimpleName())) {
144+
if (assign.getAssignment() instanceof J.FieldAccess fa) {
145+
if (!isBeanRegistrar(fa)) {
146+
// Turn into array
147+
NewArray array = new J.NewArray(Tree.randomId(), Space.EMPTY, Markers.EMPTY, null, List.of(), null, null);
148+
array = array.withInitializer(List.of(
149+
fa,
150+
createBeanRegistrarClassFieldAccess(beanRegistrarType)
151+
));
152+
maybeAddImport(beanRegistrarType);
153+
return autoFormat(assign.withAssignment(array), p, getCursor());
154+
}
155+
} else if (assign.getAssignment() instanceof J.NewArray na) {
156+
List<Expression> cna = new ArrayList<>(na.getInitializer());
157+
boolean hasBeanRegistarar = cna.stream().filter(J.FieldAccess.class::isInstance)
158+
.map(J.FieldAccess.class::cast)
159+
.anyMatch(ImportBeanRegistrarInConfigRecipe.this::isBeanRegistrar);
160+
// Not found bean registrar - add it to the array
161+
if (!hasBeanRegistarar) {
162+
cna.add(createBeanRegistrarClassFieldAccess(beanRegistrarType));
163+
maybeAddImport(beanRegistrarType);
164+
return autoFormat(assign.withAssignment(na.withInitializer(cna)), p, getCursor());
165+
}
166+
}
167+
}
168+
return arg;
169+
});
170+
if (newArgs != currentArgs) {
171+
an = an.withArguments(newArgs);
172+
}
173+
}
174+
}
175+
}
176+
return an;
177+
}
178+
179+
});
180+
}
181+
182+
private boolean isBeanRegistrar(J.FieldAccess fa) {
183+
if ("class".equals(fa.getSimpleName()) && fa.getTarget() instanceof TypeTree tt) {
184+
FullyQualified t = TypeUtils.asFullyQualified(tt.getType());
185+
if (t != null && beanRegFqn.equals(t.getFullyQualifiedName())) {
186+
return true;
187+
}
188+
}
189+
return false;
190+
}
191+
192+
private J.FieldAccess createBeanRegistrarClassFieldAccess(JavaType.FullyQualified t) {
193+
J.Identifier i = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), t.getClassName(), t, null);
194+
JavaType.Parameterized classType = new JavaType.Parameterized(0, JavaType.ShallowClass.build("java.lang.Class"), List.of(t));
195+
J.Identifier c = new J.Identifier(Tree.randomId(), Space.EMPTY, Markers.EMPTY, List.of(), "class", classType, null);
196+
return new J.FieldAccess(Tree.randomId(), Space.EMPTY, Markers.EMPTY, i, JLeftPadded.build(c), classType);
197+
}
198+
199+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Broadcom, Inc.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v1.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-v10.html
7+
*
8+
* Contributors:
9+
* Broadcom, Inc. - initial API and implementation
10+
*******************************************************************************/
11+
package org.springframework.ide.vscode.commons.rewrite.java;
12+
13+
import org.junit.jupiter.api.Test;
14+
import org.openrewrite.java.JavaParser;
15+
import org.openrewrite.test.RecipeSpec;
16+
import org.openrewrite.test.RewriteTest;
17+
18+
import static org.openrewrite.java.Assertions.java;
19+
20+
public class ImportBeanRegistrarInConfigRecipeTest implements RewriteTest {
21+
22+
@Override
23+
public void defaults(RecipeSpec spec) {
24+
spec.recipe(new ImportBeanRegistrarInConfigRecipe("com.example.test.Config", "java.util.Date"))
25+
.parser(JavaParser.fromJavaVersion().classpath("spring-beans", "spring-context"));
26+
}
27+
28+
@Test
29+
void addImportAnnotation() {
30+
rewriteRun(
31+
java(
32+
"""
33+
package com.example.test;
34+
35+
class Config {
36+
}
37+
""",
38+
"""
39+
package com.example.test;
40+
41+
import org.springframework.context.annotation.Import;
42+
43+
import java.util.Date;
44+
45+
@Import(Date.class)
46+
class Config {
47+
}
48+
"""
49+
)
50+
);
51+
}
52+
53+
@Test
54+
void addImportAnnotationNextToExistingAnnotation() {
55+
rewriteRun(
56+
java(
57+
"""
58+
package com.example.test;
59+
60+
@Deprecated
61+
class Config {
62+
}
63+
""",
64+
"""
65+
package com.example.test;
66+
67+
import org.springframework.context.annotation.Import;
68+
69+
import java.util.Date;
70+
71+
@Deprecated
72+
@Import(Date.class)
73+
class Config {
74+
}
75+
"""
76+
)
77+
);
78+
}
79+
80+
@Test
81+
void turnIntoArray() {
82+
rewriteRun(
83+
java(
84+
"""
85+
package com.example.test;
86+
87+
import org.springframework.context.annotation.Import;
88+
89+
@Import(String.class)
90+
class Config {
91+
}
92+
""",
93+
"""
94+
package com.example.test;
95+
96+
import org.springframework.context.annotation.Import;
97+
98+
import java.util.Date;
99+
100+
@Import({String.class, Date.class})
101+
class Config {
102+
}
103+
"""
104+
)
105+
);
106+
}
107+
108+
@Test
109+
void addEntryToArray() {
110+
rewriteRun(
111+
java(
112+
"""
113+
package com.example.test;
114+
115+
import org.springframework.context.annotation.Import;
116+
117+
@Import({String.class})
118+
class Config {
119+
}
120+
""",
121+
"""
122+
package com.example.test;
123+
124+
import org.springframework.context.annotation.Import;
125+
126+
import java.util.Date;
127+
128+
@Import({String.class, Date.class})
129+
class Config {
130+
}
131+
"""
132+
)
133+
);
134+
}
135+
136+
@Test
137+
void turnIntoArray_Value() {
138+
rewriteRun(
139+
java(
140+
"""
141+
package com.example.test;
142+
143+
import org.springframework.context.annotation.Import;
144+
145+
@Import(value = String.class)
146+
class Config {
147+
}
148+
""",
149+
"""
150+
package com.example.test;
151+
152+
import org.springframework.context.annotation.Import;
153+
154+
import java.util.Date;
155+
156+
@Import(value = {String.class, Date.class})
157+
class Config {
158+
}
159+
"""
160+
)
161+
);
162+
}
163+
164+
@Test
165+
void addEntryIntoArray_Value() {
166+
rewriteRun(
167+
java(
168+
"""
169+
package com.example.test;
170+
171+
import org.springframework.context.annotation.Import;
172+
173+
@Import(value = {String.class})
174+
class Config {
175+
}
176+
""",
177+
"""
178+
package com.example.test;
179+
180+
import org.springframework.context.annotation.Import;
181+
182+
import java.util.Date;
183+
184+
@Import(value = {String.class, Date.class})
185+
class Config {
186+
}
187+
"""
188+
)
189+
);
190+
}
191+
192+
}

0 commit comments

Comments
 (0)