Skip to content

Commit 62aa2b4

Browse files
authored
AOT query code action recipe relaxed method matching (#1820)
* AOT query code action recipe relaxed method matching Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> * Fix unit tests on Win Signed-off-by: BoykoAlex <alex.boyko@broadcom.com> --------- Signed-off-by: BoykoAlex <alex.boyko@broadcom.com>
1 parent 02ab0db commit 62aa2b4

File tree

8 files changed

+150
-32
lines changed

8 files changed

+150
-32
lines changed

headless-services/commons/commons-rewrite/src/main/java/org/springframework/ide/vscode/commons/rewrite/java/AddAnnotationOverMethod.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,25 @@
1313
import java.util.ArrayList;
1414
import java.util.List;
1515
import java.util.Optional;
16+
import java.util.stream.Collectors;
1617

1718
import org.jspecify.annotations.Nullable;
1819
import org.openrewrite.ExecutionContext;
1920
import org.openrewrite.NlsRewrite.Description;
2021
import org.openrewrite.NlsRewrite.DisplayName;
2122
import org.openrewrite.Option;
22-
import org.openrewrite.Preconditions;
2323
import org.openrewrite.Recipe;
2424
import org.openrewrite.Tree;
2525
import org.openrewrite.TreeVisitor;
2626
import org.openrewrite.java.AddOrUpdateAnnotationAttribute;
2727
import org.openrewrite.java.JavaIsoVisitor;
2828
import org.openrewrite.java.MethodMatcher;
29-
import org.openrewrite.java.search.DeclaresMethod;
3029
import org.openrewrite.java.tree.J;
3130
import org.openrewrite.java.tree.J.Annotation;
3231
import org.openrewrite.java.tree.J.MethodDeclaration;
32+
import org.openrewrite.java.tree.J.VariableDeclarations;
3333
import org.openrewrite.java.tree.JavaType;
34+
import org.openrewrite.java.tree.NameTree;
3435
import org.openrewrite.java.tree.Space;
3536
import org.openrewrite.java.tree.TypeUtils;
3637
import org.openrewrite.marker.Markers;
@@ -75,11 +76,11 @@ public AddAnnotationOverMethod(
7576
@Override
7677
public TreeVisitor<?, ExecutionContext> getVisitor() {
7778
final MethodMatcher matcher = new MethodMatcher(method);
78-
return Preconditions.check(new DeclaresMethod<>(matcher), new JavaIsoVisitor<>() {
79+
return new JavaIsoVisitor<>() {
7980
@Override
8081
public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, ExecutionContext ctx) {
8182
MethodDeclaration m = super.visitMethodDeclaration(method, ctx);
82-
if (matcher.matches(m.getMethodType())) {
83+
if (matcher.matches(m.getMethodType()) || matchesParameterTypeNames(m)) {
8384
boolean changed = false;
8485
Optional<Annotation> optAnnotation = m.getLeadingAnnotations().stream().filter(a -> TypeUtils.isOfClassType(a.getType(), annotationType)).findFirst();
8586
if (optAnnotation.isEmpty()) {
@@ -113,7 +114,33 @@ public MethodDeclaration visitMethodDeclaration(MethodDeclaration method, Execut
113114
}
114115
return m;
115116
}
116-
});
117+
118+
private boolean matchesParameterTypeNames(MethodDeclaration md) {
119+
String currentMethod = "%s %s(%s)".formatted(md.getMethodType().getDeclaringType().getFullyQualifiedName(),
120+
md.getName().getSimpleName(), md.getParameters().stream()
121+
.map(s -> {
122+
if (s instanceof VariableDeclarations vd) {
123+
return getStr(vd.getTypeExpression());
124+
}
125+
return s.print(getCursor());
126+
}).collect(Collectors.joining(",")));
127+
return method.equals(currentMethod);
128+
}
129+
130+
private String getStr(NameTree tt) {
131+
if (tt instanceof J.Identifier id) {
132+
return id.getSimpleName();
133+
} else if (tt instanceof J.FieldAccess fa) {
134+
return getStr(fa.getName());
135+
} else if (tt instanceof J.ArrayType at) {
136+
return getStr(at.getElementType());
137+
} else if (tt instanceof J.ParameterizedType pt) {
138+
return getStr(pt.getClazz());
139+
} else {
140+
return tt.print(getCursor());
141+
}
142+
}
143+
};
117144
}
118145

119146
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,8 @@ static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, DataR
207207
"annotationType", moduleToQueryMapping.get(module),
208208
"method", "%s %s(%s)".formatted(mb.getDeclaringClass().getQualifiedName(), mb.getName(),
209209
Arrays.stream(mb.getParameterTypes())
210-
.map(pt -> pt.getQualifiedName())
211-
.collect(Collectors.joining(", "))),
210+
.map(pt -> pt.getName())
211+
.collect(Collectors.joining(","))),
212212
"attributes", createAttributeList(methodMetadata.getAttributesMap(), module, config)));
213213
}
214214

headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryAotMetadataCodeLensProviderJdbcTest.java

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ void codeLensOverMethod() throws Exception {
8282
List<CodeLens> cls = editor.getCodeLenses("findAllByNameContaining", 1);
8383
assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle());
8484
assertEquals("Go To Implementation", cls.get(1).getCommand().getTitle());
85-
assertEquals("SELECT \"CATEGORY\".\"ID\" AS \"ID\", \"CATEGORY\".\"NAME\" AS \"NAME\", \"CATEGORY\".\"CREATED\" AS \"CREATED\", \"CATEGORY\".\"INSERTED\" AS \"INSERTED\", \"CATEGORY\".\"DESCRIPTION\" AS \"DESCRIPTION\" FROM \"CATEGORY\" WHERE \"CATEGORY\".\"NAME\" LIKE :name", cls.get(2).getCommand().getTitle());
85+
assertEquals("SELECT \"CATEGORY\".\"ID\" AS \"ID\", \"CATEGORY\".\"NAME\" AS \"NAME\", \"CATEGORY\".\"CREATED\" AS \"CREATED\", \"CATEGORY\".\"INSERTED\" AS \"INSERTED\", \"CATEGORY\".\"DESCRIPTION\" AS \"DESCRIPTION\", \"CATEGORY\".\"PRIORITY_LEVEL\" AS \"PRIORITY_LEVEL\" FROM \"CATEGORY\" WHERE \"CATEGORY\".\"NAME\" LIKE :name", cls.get(2).getCommand().getTitle());
8686
}
8787

8888
@Test
@@ -127,8 +127,98 @@ void turnIntoQueryUsesTextBlock() throws Exception {
127127
assertTrue(queryValue.contains("\nFROM"), "Should have newline before FROM");
128128
assertTrue(queryValue.contains("\nWHERE"), "Should have newline before WHERE");
129129
assertTrue(queryValue.endsWith("\n\"\"\""), "Should end with newline before closing triple quotes");
130+
131+
harness.perform(cls.get(0).getCommand());
132+
133+
assertEquals(editor.getRawText().replace("\r", ""), """
134+
package example.springdata.aot;
135+
136+
import java.util.List;
137+
138+
import org.springframework.data.jdbc.repository.query.Query;
139+
import org.springframework.data.repository.CrudRepository;
140+
141+
interface CategoryRepository extends CrudRepository<Category, Long> {
142+
143+
@Query(""\"
144+
SELECT "CATEGORY"."ID" AS "ID",
145+
"CATEGORY"."NAME" AS "NAME",
146+
"CATEGORY"."CREATED" AS "CREATED",
147+
"CATEGORY"."INSERTED" AS "INSERTED",
148+
"CATEGORY"."DESCRIPTION" AS "DESCRIPTION",
149+
"CATEGORY"."PRIORITY_LEVEL" AS "PRIORITY_LEVEL"
150+
FROM "CATEGORY"
151+
WHERE "CATEGORY"."NAME" LIKE :name
152+
""\")
153+
List<Category> findAllByNameContaining(String name);
154+
155+
List<CategoryProjection> findProjectedByNameContaining(String name);
156+
157+
@Query("SELECT * FROM category WHERE name = :name")
158+
List<Category> findWithDeclaredQuery(String name);
159+
160+
List<Category> findByPriorityLevel(PriorityLevel priorityLevel);
161+
}
162+
"""
163+
);
130164
}
131165

166+
@Test
167+
void turnIntoQueryWithImportedType() throws Exception {
168+
harness.changeConfiguration(new Settings(new Gson()
169+
.toJsonTree(Map.of("boot-java", Map.of(
170+
"java", Map.of("codelens-over-query-methods", true),
171+
"code-action", Map.of("data-query-multiline", true)
172+
)))));
173+
174+
Path filePath = Paths.get(testProject.getLocationUri())
175+
.resolve("src/main/java/example/springdata/aot/CategoryRepository.java");
176+
177+
Editor editor = harness.newEditor(
178+
LanguageId.JAVA,
179+
new String(Files.readAllBytes(filePath), StandardCharsets.UTF_8),
180+
filePath.toUri().toASCIIString()
181+
);
182+
183+
List<CodeLens> cls = editor.getCodeLenses("findByPriorityLevel", 1);
184+
185+
assertEquals("Turn into @Query", cls.get(0).getCommand().getTitle());
186+
187+
harness.perform(cls.get(0).getCommand());
188+
189+
assertEquals(editor.getRawText().replace("\r", ""), """
190+
package example.springdata.aot;
191+
192+
import java.util.List;
193+
194+
import org.springframework.data.jdbc.repository.query.Query;
195+
import org.springframework.data.repository.CrudRepository;
196+
197+
interface CategoryRepository extends CrudRepository<Category, Long> {
198+
199+
List<Category> findAllByNameContaining(String name);
200+
201+
List<CategoryProjection> findProjectedByNameContaining(String name);
202+
203+
@Query("SELECT * FROM category WHERE name = :name")
204+
List<Category> findWithDeclaredQuery(String name);
205+
206+
@Query(""\"
207+
SELECT "CATEGORY"."ID" AS "ID",
208+
"CATEGORY"."NAME" AS "NAME",
209+
"CATEGORY"."CREATED" AS "CREATED",
210+
"CATEGORY"."INSERTED" AS "INSERTED",
211+
"CATEGORY"."DESCRIPTION" AS "DESCRIPTION",
212+
"CATEGORY"."PRIORITY_LEVEL" AS "PRIORITY_LEVEL"
213+
FROM "CATEGORY"
214+
WHERE "CATEGORY"."PRIORITY_LEVEL" = :priority_level
215+
""\")
216+
List<Category> findByPriorityLevel(PriorityLevel priorityLevel);
217+
}
218+
"""
219+
);
220+
}
221+
132222
@Test
133223
void turnIntoQueryUsesCompactStyleByDefault() throws Exception {
134224
Path filePath = Paths.get(testProject.getLocationUri())

headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jdbc/src/main/java/example/springdata/aot/Category.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,22 +31,33 @@ public class Category {
3131
private String name, description;
3232
private LocalDateTime created;
3333
private Long inserted;
34+
private PriorityLevel priorityLevel;
35+
36+
public PriorityLevel getPriorityLevel() {
37+
return priorityLevel;
38+
}
39+
40+
public void setPriorityLevel(PriorityLevel priorityLevel) {
41+
this.priorityLevel = priorityLevel;
42+
}
3443

3544
public Category(String name, String description) {
3645

3746
this.id = null;
3847
this.name = name;
3948
this.description = description;
4049
this.created = LocalDateTime.now();
50+
this.priorityLevel = PriorityLevel.Medium;
4151
}
4252

4353
@PersistenceCreator
44-
Category(Long id, String name, String description, LocalDateTime created, Long inserted) {
54+
Category(Long id, String name, String description, LocalDateTime created, Long inserted, PriorityLevel priorityLevel) {
4555
this.id = id;
4656
this.name = name;
4757
this.description = description;
4858
this.created = created;
4959
this.inserted = inserted;
60+
this.priorityLevel = priorityLevel;
5061
}
5162

5263
public void timeStamp() {
@@ -93,7 +104,7 @@ public void setInserted(Long inserted) {
93104
}
94105

95106
public Category withId(Long id) {
96-
return this.id == id ? this : new Category(id, this.name, this.description, this.created, this.inserted);
107+
return this.id == id ? this : new Category(id, this.name, this.description, this.created, this.inserted, this.priorityLevel);
97108
}
98109

99110
@Override
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,10 @@
1-
/*
2-
* Copyright 2025 the original author or authors.
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* https://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package example.springdata.aot;
172

183
import java.util.List;
194

205
import org.springframework.data.jdbc.repository.query.Query;
216
import org.springframework.data.repository.CrudRepository;
227

23-
/**
24-
* Repository for Categories.
25-
*
26-
* @author Mark Paluch
27-
*/
288
interface CategoryRepository extends CrudRepository<Category, Long> {
299

3010
List<Category> findAllByNameContaining(String name);
@@ -34,4 +14,5 @@ interface CategoryRepository extends CrudRepository<Category, Long> {
3414
@Query("SELECT * FROM category WHERE name = :name")
3515
List<Category> findWithDeclaredQuery(String name);
3616

17+
List<Category> findByPriorityLevel(PriorityLevel priorityLevel);
3718
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package example.springdata.aot;
2+
3+
public enum PriorityLevel {
4+
Low,
5+
Medium,
6+
High
7+
}

headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jdbc/src/main/resources/data.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616
INSERT INTO category(name, description, created, inserted)
17-
VALUES ('Cars', 'Anything that has approximately 4 wheels.', now(), 1);
17+
VALUES ('Cars', 'Anything that has approximately 4 wheels.', now(), 1. 'high');
1818
INSERT INTO category(name, description, created, inserted)
1919
VALUES ('Buildings', 'Walls, anyone?', now(), 2);
2020
INSERT INTO category(name, description, created, inserted)

headless-services/spring-boot-language-server/src/test/resources/test-projects/aot-data-repositories-jdbc/src/main/resources/schema.sql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
CREATE TYPE priority_level AS ENUM ('low', 'medium', 'high');
1617
CREATE TABLE IF NOT EXISTS category
1718
(
1819
id INTEGER IDENTITY PRIMARY KEY,
1920
name VARCHAR(100),
2021
description VARCHAR(2000),
2122
created DATETIME,
22-
inserted BIGINT
23+
inserted BIGINT,
24+
priority priority_level NOT NULL DEFAULT 'medium'
2325
);

0 commit comments

Comments
 (0)