Skip to content

Commit 21b2683

Browse files
committed
Adjustments to AOT data query navigation, CodeLens
1 parent 5c50917 commit 21b2683

File tree

11 files changed

+136
-60
lines changed

11 files changed

+136
-60
lines changed

headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/Editor.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,14 @@ public void assertCodeLens(String over, int occurrence, String codeLensText) thr
631631
.map(cl -> "'%s'".formatted(cl.getCommand().getTitle())).collect(Collectors.joining(", ")), codeLensText));
632632
}
633633
}
634+
635+
public List<CodeLens> getCodeLenses(String over, int occurrence) throws Exception {
636+
int offset = getHoverPosition(over, occurrence);
637+
return harness.getCodeLenses(doc).stream()
638+
.filter(cl -> doc.toOffset(cl.getRange().getStart()) <= offset
639+
&& doc.toOffset(cl.getRange().getEnd()) >= offset)
640+
.collect(Collectors.toList());
641+
}
634642

635643
public void assertLiveCodeLensContains(String codeLensOver, int occurrence, String snippet) throws Exception {
636644
int cmPosition = getHoverPosition(codeLensOver, occurrence);

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ JavaDefinitionHandler javaDefinitionHandler(SimpleLanguageServer server, Compila
420420
new NamedDefinitionProvider(springIndex),
421421
new DataQueryParameterDefinitionProvider(server.getTextDocumentService(), qurySemanticTokens),
422422
new SpelDefinitionProvider(springIndex, cuCache),
423-
new GenAotQueryMethodDefinitionProvider(cuCache, server.getTextDocumentService())));
423+
new GenAotQueryMethodDefinitionProvider(server, cuCache, projectFinder)));
424424
}
425425

426426
@Bean

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
@Component
6565
public class BootLanguageServerInitializer implements InitializingBean {
6666

67+
public static final String CMD_SHOW_DOC = "sts/show/document";
68+
6769
@Autowired SimpleLanguageServer server;
6870
@Autowired BootLanguageServerParams params;
6971
@Autowired SourceLinks sourceLinks;
@@ -165,7 +167,7 @@ public void afterPropertiesSet() throws Exception {
165167

166168
startListening();
167169

168-
server.onCommand("sts/show/document", p -> {
170+
server.onCommand(CMD_SHOW_DOC, p -> {
169171
ShowDocumentParams showDocParams = new Gson().fromJson((JsonElement)p.getArguments().get(0), ShowDocumentParams.class);
170172
return server.getClient().showDocument(showDocParams).thenApply(r -> {
171173
if (!r.isSuccess()) {

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

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,17 @@
2020
import org.apache.commons.text.StringEscapeUtils;
2121
import org.eclipse.jdt.core.dom.ASTVisitor;
2222
import org.eclipse.jdt.core.dom.CompilationUnit;
23-
import org.eclipse.jdt.core.dom.IAnnotationBinding;
2423
import org.eclipse.jdt.core.dom.IMethodBinding;
25-
import org.eclipse.jdt.core.dom.ITypeBinding;
2624
import org.eclipse.jdt.core.dom.MethodDeclaration;
2725
import org.eclipse.lsp4j.CodeLens;
2826
import org.eclipse.lsp4j.Command;
27+
import org.eclipse.lsp4j.Position;
2928
import org.eclipse.lsp4j.Range;
3029
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
3130
import org.slf4j.Logger;
3231
import org.slf4j.LoggerFactory;
3332
import org.springframework.ide.vscode.boot.java.Annotations;
33+
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
3434
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
3535
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
3636
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -46,7 +46,7 @@
4646
*/
4747
public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvider {
4848

49-
private static final String COVERT_TO_QUERY_LABEL = "Add @Query";
49+
private static final String COVERT_TO_QUERY_LABEL = "Turn into @Query";
5050

5151
private static final Logger log = LoggerFactory.getLogger(DataRepositoryAotMetadataCodeLensProvider.class);
5252

@@ -71,23 +71,12 @@ public boolean visit(MethodDeclaration node) {
7171
});
7272
}
7373

74-
static boolean isDataQuaryNonAnnotatedMethodCandidate(IMethodBinding methodBinding) {
74+
static boolean isValidMethodBinding(IMethodBinding methodBinding) {
7575
if (methodBinding == null || methodBinding.getDeclaringClass() == null
7676
|| methodBinding.getMethodDeclaration() == null
77-
|| methodBinding.getDeclaringClass().getBinaryName() == null
78-
|| methodBinding.getMethodDeclaration().toString() == null) {
77+
|| methodBinding.getDeclaringClass().getBinaryName() == null) {
7978
return false;
8079
}
81-
82-
// Don't show CodeLens if annotated with `@Query` or `@NativeQuery`
83-
for (IAnnotationBinding a : methodBinding.getAnnotations()) {
84-
ITypeBinding t = a.getAnnotationType();
85-
if (t != null
86-
&& (Annotations.DATA_JPA_QUERY.equals(t.getQualifiedName()) || Annotations.DATA_JPA_NATIVE_QUERY.equals(t.getQualifiedName()))) {
87-
return false;
88-
}
89-
}
90-
9180
return true;
9281
}
9382

@@ -115,7 +104,7 @@ protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node
115104

116105
IMethodBinding methodBinding = node.resolveBinding();
117106

118-
if (isDataQuaryNonAnnotatedMethodCandidate(methodBinding)) {
107+
if (isValidMethodBinding(methodBinding)) {
119108
cancelToken.checkCanceled();
120109
getDataQuery(repositoryMetadataService, project, methodBinding)
121110
.map(queryStatement -> createCodeLenses(node, document, queryStatement))
@@ -127,12 +116,30 @@ private List<CodeLens> createCodeLenses(MethodDeclaration node, TextDocument doc
127116
List<CodeLens> codeLenses = new ArrayList<>(2);
128117
try {
129118
IMethodBinding mb = node.resolveBinding();
130-
Range range = document.toRange(node.getName().getStartPosition(), node.getName().getLength());
131-
Command queryTitle = new Command();
132-
queryTitle.setTitle(queryStatement);
133-
codeLenses.add(new CodeLens(range, queryTitle, null));
134-
if (mb != null) {
135-
codeLenses.add(new CodeLens(range, refactorings.createFixCommand(COVERT_TO_QUERY_LABEL, createFixDescriptor(mb, document.getUri(), queryStatement)), null));
119+
Position startPos = document.toPosition(node.getStartPosition());
120+
Position endPos = document.toPosition(node.getName().getStartPosition() + node.getName().getLength());
121+
Range range = new Range(startPos, endPos);
122+
AnnotationHierarchies hierarchyAnnot = AnnotationHierarchies.get(node);
123+
if (mb != null && hierarchyAnnot != null) {
124+
boolean isQueryAnnotated = hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_JPA_QUERY);
125+
if (!isQueryAnnotated) {
126+
codeLenses.add(new CodeLens(range, refactorings.createFixCommand(COVERT_TO_QUERY_LABEL, createFixDescriptor(mb, document.getUri(), queryStatement)), null));
127+
}
128+
129+
Command impl = new Command("Implementation", GenAotQueryMethodDefinitionProvider.CMD_NAVIGATE_TO_IMPL, List.of(new GenAotQueryMethodDefinitionProvider.GoToImplParams(
130+
document.getId(),
131+
mb.getDeclaringClass().getQualifiedName(),
132+
mb.getName(),
133+
Arrays.stream(mb.getParameterTypes()).map(p -> p.getQualifiedName()).toArray(String[]::new),
134+
null
135+
)));
136+
codeLenses.add(new CodeLens(range, impl, null));
137+
138+
if (!isQueryAnnotated) {
139+
Command queryTitle = new Command();
140+
queryTitle.setTitle(queryStatement);
141+
codeLenses.add(new CodeLens(range, queryTitle, null));
142+
}
136143
}
137144
} catch (BadLocationException e) {
138145
log.error("bad location while calculating code lens for data repository query method", e);

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

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222
import java.util.ArrayList;
2323
import java.util.Arrays;
2424
import java.util.List;
25+
import java.util.Optional;
2526
import java.util.Set;
27+
import java.util.concurrent.CompletableFuture;
2628
import java.util.concurrent.atomic.AtomicReference;
2729
import java.util.stream.Collectors;
2830

@@ -35,6 +37,7 @@
3537
import org.eclipse.lsp4j.LocationLink;
3638
import org.eclipse.lsp4j.Position;
3739
import org.eclipse.lsp4j.Range;
40+
import org.eclipse.lsp4j.ShowDocumentParams;
3841
import org.eclipse.lsp4j.TextDocumentIdentifier;
3942
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
4043
import org.slf4j.Logger;
@@ -46,19 +49,29 @@
4649
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
4750
import org.springframework.ide.vscode.commons.java.IJavaProject;
4851
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
52+
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
53+
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
4954
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
5055
import org.springframework.ide.vscode.commons.util.BadLocationException;
5156

57+
import com.google.gson.Gson;
58+
import com.google.gson.JsonElement;
59+
5260
public class GenAotQueryMethodDefinitionProvider implements IJavaDefinitionProvider {
5361

5462
private static Logger log = LoggerFactory.getLogger(GenAotQueryMethodDefinitionProvider.class);
5563

64+
public static final String CMD_NAVIGATE_TO_IMPL = "sts/boot/open-data-query-method-aot-definition";
65+
5666
private final CompilationUnitCache cuCache;
5767
private final SimpleTextDocumentService docService;
68+
private final JavaProjectFinder projectFinder;
5869

59-
public GenAotQueryMethodDefinitionProvider(CompilationUnitCache cuCache, SimpleTextDocumentService docService) {
70+
public GenAotQueryMethodDefinitionProvider(SimpleLanguageServer server, CompilationUnitCache cuCache, JavaProjectFinder projectFinder) {
6071
this.cuCache = cuCache;
61-
this.docService = docService;
72+
this.docService = server.getTextDocumentService();
73+
this.projectFinder = projectFinder;
74+
registerCommands(server);
6275
}
6376

6477
@Override
@@ -73,17 +86,29 @@ public List<LocationLink> getDefinitions(CancelChecker cancelToken, IJavaProject
7386
&& methodBinding.getDeclaringClass() != null
7487
&& ASTUtils.isAnyTypeInHierarchy(methodBinding.getDeclaringClass(),
7588
List.of(Constants.REPOSITORY_TYPE))) {
76-
String genRepoFqn = methodBinding.getDeclaringClass().getQualifiedName() + "Impl__Aot";
77-
Path relativeGenSourcePath = Paths.get("%s.java".formatted(genRepoFqn.replace('.', '/')));
78-
List<LocationLink> defs = findInSourceFolder(project, relativeGenSourcePath, docId, md, methodBinding, genRepoFqn);
79-
return defs.isEmpty() ? findInBuildFolder(project, relativeGenSourcePath, docId, md, methodBinding, genRepoFqn) : defs;
89+
90+
try {
91+
Range originRange = docService.getLatestSnapshot(docId.getUri()).toRange(md.getName().getStartPosition(), md.getName().getLength());
92+
GoToImplParams params = new GoToImplParams(docId, methodBinding.getDeclaringClass().getQualifiedName(), methodBinding.getName(), Arrays.stream(methodBinding.getParameterTypes()).map(b -> b.getQualifiedName()).toArray(String[]::new), originRange);
93+
return findDefinitions(project, params);
94+
} catch (BadLocationException e) {
95+
log.error("", e);
96+
}
97+
8098
}
8199
}
82100
}
83101
return List.of();
84102
}
85103

86-
private List<LocationLink> getLocationInGenFile(IJavaProject project, TextDocumentIdentifier docId, MethodDeclaration md, IMethodBinding methodBinding, Path genRepoSourcePath, String genRepoFqn) {
104+
private List<LocationLink> findDefinitions(IJavaProject project, GoToImplParams implParams) {
105+
String genRepoFqn = implParams.repoFqName() + "Impl__Aot";
106+
Path relativeGenSourcePath = Paths.get("%s.java".formatted(genRepoFqn.replace('.', '/')));
107+
List<LocationLink> defs = findInSourceFolder(project, relativeGenSourcePath, genRepoFqn, implParams);
108+
return defs.isEmpty() ? findInBuildFolder(project, relativeGenSourcePath, genRepoFqn, implParams) : defs;
109+
}
110+
111+
private List<LocationLink> getLocationInGenFile(IJavaProject project, Path genRepoSourcePath, String genRepoFqn, GoToImplParams params) {
87112
if (Files.exists(genRepoSourcePath)) {
88113
URI genUri = genRepoSourcePath.toUri();
89114
return cuCache.withCompilationUnit(project, genUri, genCu -> {
@@ -94,21 +119,18 @@ private List<LocationLink> getLocationInGenFile(IJavaProject project, TextDocume
94119
public boolean visit(MethodDeclaration node) {
95120
IMethodBinding genBinding = node.resolveBinding();
96121
if (genBinding != null
97-
&& genBinding.getName().equals(methodBinding.getName())
98-
&& Arrays.equals(Arrays.stream(genBinding.getParameterTypes()).map(b -> b.getQualifiedName()).toArray(), Arrays.stream(methodBinding.getParameterTypes()).map(b -> b.getQualifiedName()).toArray() )
122+
&& genBinding.getName().equals(params.queryMethodName())
123+
&& Arrays.equals(Arrays.stream(genBinding.getParameterTypes()).map(b -> b.getQualifiedName()).toArray(), params.paramTypes)
99124
&& genRepoFqn.equals(genBinding.getDeclaringClass().getQualifiedName())) {
100125
LocationLink ll = new LocationLink();
101126
ll.setTargetUri(genUri.toASCIIString());
102-
try {
103-
ll.setOriginSelectionRange(docService.getLatestSnapshot(docId.getUri()).toRange(md.getName().getStartPosition(), md.getName().getLength()));
104-
} catch (BadLocationException e) {
105-
log.error("", e);
106-
}
127+
ll.setOriginSelectionRange(params.originSelection());
107128
SimpleName genName = node.getName();
108129
int startLine = genCu.getLineNumber(genName.getStartPosition());
109-
Position targetStartPosition = new Position(startLine, genName.getStartPosition() - genCu.getPosition(startLine, 0));
130+
// LSP line are 0-based hence -1 from line number when building LSP Range/Position
131+
Position targetStartPosition = new Position(startLine - 1, genName.getStartPosition() - genCu.getPosition(startLine, 0));
110132
int endLine = genCu.getLineNumber(genName.getStartPosition() + genName.getLength());
111-
Position targetEndPosition = new Position(endLine, genName.getStartPosition() + genName.getLength() - genCu.getPosition(endLine, 0));
133+
Position targetEndPosition = new Position(endLine - 1, genName.getStartPosition() + genName.getLength() - genCu.getPosition(endLine, 0));
112134
Range targetRange = new Range(targetStartPosition, targetEndPosition);
113135
ll.setTargetRange(targetRange);
114136
ll.setTargetSelectionRange(targetRange);
@@ -124,15 +146,15 @@ public boolean visit(MethodDeclaration node) {
124146
return List.of();
125147
}
126148

127-
private List<LocationLink> findInSourceFolder(IJavaProject project, Path relativeGenSourcePath, TextDocumentIdentifier docId, MethodDeclaration md, IMethodBinding methodBinding, String genRepoFqn) {
149+
private List<LocationLink> findInSourceFolder(IJavaProject project, Path relativeGenSourcePath, String genRepoFqn, GoToImplParams params) {
128150
for (File f : IClasspathUtil.getSourceFolders(project.getClasspath()).collect(Collectors.toSet())) {
129151
Path genRepoSourcePath = f.toPath().resolve(relativeGenSourcePath);
130-
return getLocationInGenFile(project, docId, md, methodBinding, genRepoSourcePath, genRepoFqn);
152+
return getLocationInGenFile(project, genRepoSourcePath, genRepoFqn, params);
131153
}
132154
return List.of();
133155
}
134156

135-
private List<LocationLink> findInBuildFolder(IJavaProject project, Path relativeGenSourcePath, TextDocumentIdentifier docId, MethodDeclaration md, IMethodBinding methodBinding, String genRepoFqn) {
157+
private List<LocationLink> findInBuildFolder(IJavaProject project, Path relativeGenSourcePath, String genRepoFqn, GoToImplParams params) {
136158
Path buildDirRelativePath = null;
137159
Path projectPath = Paths.get(project.getLocationUri());
138160
Set<Path> outputFolders = IClasspathUtil.getOutputFolders(project.getClasspath()).map(f -> f.toPath()).collect(Collectors.toSet());
@@ -182,7 +204,30 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
182204
} catch (IOException e) {
183205
log.error("", e);
184206
}
185-
return genSourceFilePathRef.get() == null ? List.of() : getLocationInGenFile(project, docId, md, methodBinding, genSourceFilePathRef.get(), genRepoFqn);
207+
return genSourceFilePathRef.get() == null ? List.of() : getLocationInGenFile(project, genSourceFilePathRef.get(), genRepoFqn, params);
186208
}
187209

210+
private void registerCommands(SimpleLanguageServer server) {
211+
server.onCommand(CMD_NAVIGATE_TO_IMPL, params -> {
212+
return CompletableFuture.supplyAsync(() -> {
213+
GoToImplParams implParams = new Gson().fromJson((JsonElement) params.getArguments().get(0), GoToImplParams.class);
214+
Optional<IJavaProject> project = projectFinder.find(implParams.docId());
215+
if (project.isEmpty()) {
216+
return List.<LocationLink>of();
217+
}
218+
return findDefinitions(project.get(), implParams);
219+
}).thenCompose(links -> {
220+
if (links.isEmpty()) {
221+
return CompletableFuture.completedFuture(null);
222+
} else {
223+
ShowDocumentParams showDocParams = new ShowDocumentParams(links.get(0).getTargetUri());
224+
showDocParams.setSelection(links.get(0).getTargetRange());
225+
return server.getClient().showDocument(showDocParams);
226+
}
227+
});
228+
});
229+
}
230+
231+
public record GoToImplParams(TextDocumentIdentifier docId, String repoFqName, String queryMethodName, String[] paramTypes, Range originSelection) {}
232+
188233
}

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import org.eclipse.lsp4j.CodeAction;
2020
import org.eclipse.lsp4j.CodeActionKind;
2121
import org.eclipse.lsp4j.jsonrpc.CancelChecker;
22+
import org.springframework.ide.vscode.boot.java.Annotations;
23+
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
2224
import org.springframework.ide.vscode.boot.java.codeaction.JdtAstCodeActionProvider;
2325
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
2426
import org.springframework.ide.vscode.commons.Version;
@@ -30,7 +32,7 @@
3032

3133
public class QueryMethodCodeActionProvider implements JdtAstCodeActionProvider {
3234

33-
private static final String TITLE = "Convert into `@Query`";
35+
private static final String TITLE = "Add `@Query`";
3436

3537
private final DataRepositoryAotMetadataService repositoryMetadataService;
3638
private final RewriteRefactorings refactorings;
@@ -55,11 +57,12 @@ public ASTVisitor createVisitor(CancelChecker cancelToken, IJavaProject project,
5557
public boolean visit(MethodDeclaration node) {
5658
cancelToken.checkCanceled();
5759
if (node.getStartPosition() <= region.getStart() && node.getStartPosition() + node.getLength() >= region.getEnd()) {
58-
int offset = node.getName().getStartPosition();
59-
int length = node.getName().getLength();
60-
if (offset <= region.getStart() && offset + length >= region.getEnd()) {
60+
int start = node.getStartPosition();
61+
int end = node.getName().getStartPosition() + node.getName().getLength();
62+
if (start <= region.getStart() && end >= region.getEnd()) {
6163
IMethodBinding binding = node.resolveBinding();
62-
if (DataRepositoryAotMetadataCodeLensProvider.isDataQuaryNonAnnotatedMethodCandidate(binding)) {
64+
AnnotationHierarchies hierarchyAnnot = AnnotationHierarchies.get(node);
65+
if (hierarchyAnnot != null && !hierarchyAnnot.isAnnotatedWith(binding, Annotations.DATA_JPA_QUERY)) {
6366
DataRepositoryAotMetadataCodeLensProvider.getDataQuery(repositoryMetadataService, project, binding)
6467
.map(query -> createCodeAction(binding, docURI, query)).ifPresent(collector::accept);
6568
}

headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/validation/generations/GenerationsValidator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.eclipse.lsp4j.Diagnostic;
2020
import org.eclipse.lsp4j.Range;
2121
import org.eclipse.lsp4j.ShowDocumentParams;
22+
import org.springframework.ide.vscode.boot.app.BootLanguageServerInitializer;
2223
import org.springframework.ide.vscode.boot.validation.generations.json.Generation;
2324
import org.springframework.ide.vscode.boot.validation.generations.json.ResolvedSpringProject;
2425
import org.springframework.ide.vscode.boot.validation.generations.preferences.VersionValidationProblemType;
@@ -148,7 +149,7 @@ private static CodeAction getCommercialSupportCodeAction() {
148149
showDocumentParams.setExternal(true);
149150
showDocumentParams.setTakeFocus(true);
150151
showDocumentParams.setSelection(new Range());
151-
commercialSupportLink.setCommand(new Command("Get commercial Spring Boot support via Tanzu Spring Runtime", "sts/show/document",
152+
commercialSupportLink.setCommand(new Command("Get commercial Spring Boot support via Tanzu Spring Runtime", BootLanguageServerInitializer.CMD_SHOW_DOC,
152153
ImmutableList.of(showDocumentParams)));
153154
return commercialSupportLink;
154155
}

0 commit comments

Comments
 (0)