diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java index 25dd860dd..6dec3a166 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadata.java @@ -11,4 +11,12 @@ package org.springframework.ide.vscode.boot.java.data; public record DataRepositoryAotMetadata (String name, String type, String module, DataRepositoryAotMetadataMethod[] methods) { + + public boolean isJPA() { + return module != null && module.toLowerCase().equals("jpa"); + } + + public boolean isMongoDb() { + return module != null && module.toLowerCase().equals("mongodb"); + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java index 465bcc6a9..7ac85a351 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataCodeLensProvider.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.text.StringEscapeUtils; @@ -80,18 +81,30 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid return true; } - static Optional getDataQuery(DataRepositoryAotMetadataService repositoryMetadataService, IJavaProject project, IMethodBinding methodBinding) { +// static Optional getDataQuery(DataRepositoryAotMetadataService repositoryMetadataService, IJavaProject project, IMethodBinding methodBinding) { +// final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim(); +// final IMethodBinding method = methodBinding.getMethodDeclaration(); +// +// DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass); +// +// if (metadata != null) { +// return Optional.ofNullable(repositoryMetadataService.getQueryStatement(metadata, method)); +// } +// +// return Optional.empty(); +// +// } + + static Optional getMetadata(DataRepositoryAotMetadataService dataRepositoryAotMetadataService, IJavaProject project, IMethodBinding methodBinding) { final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim(); + + return Optional.ofNullable(dataRepositoryAotMetadataService.getRepositoryMetadata(project, repositoryClass)); + } + + static Optional getMethodMetadata(DataRepositoryAotMetadataService dataRepositoryAotMetadataService, DataRepositoryAotMetadata metadata, IMethodBinding methodBinding) { final IMethodBinding method = methodBinding.getMethodDeclaration(); - DataRepositoryAotMetadata metadata = repositoryMetadataService.getRepositoryMetadata(project, repositoryClass); - - if (metadata != null) { - return Optional.ofNullable(repositoryMetadataService.getQueryStatement(metadata, method)); - } - - return Optional.empty(); - + return Optional.ofNullable(dataRepositoryAotMetadataService.findMethod(metadata, method)); } protected void provideCodeLens(CancelChecker cancelToken, MethodDeclaration node, TextDocument document, List resultAccumulator) { @@ -106,13 +119,14 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid if (isValidMethodBinding(methodBinding)) { cancelToken.checkCanceled(); - getDataQuery(repositoryMetadataService, project, methodBinding) - .map(queryStatement -> createCodeLenses(node, document, queryStatement)) + + getMetadata(repositoryMetadataService, project, methodBinding) + .map(metadata -> createCodeLenses(node, document, metadata)) .ifPresent(cls -> cls.forEach(resultAccumulator::add)); } } - private List createCodeLenses(MethodDeclaration node, TextDocument document, String queryStatement) { + private List createCodeLenses(MethodDeclaration node, TextDocument document, DataRepositoryAotMetadata metadata) { List codeLenses = new ArrayList<>(2); try { @@ -122,13 +136,16 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid Range range = new Range(startPos, endPos); AnnotationHierarchies hierarchyAnnot = AnnotationHierarchies.get(node); - if (mb != null && hierarchyAnnot != null) { + Optional methodMetadata = getMethodMetadata(repositoryMetadataService, metadata, mb); + + if (mb != null && hierarchyAnnot != null && methodMetadata.isPresent()) { boolean isQueryAnnotated = hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_JPA_QUERY) || hierarchyAnnot.isAnnotatedWith(mb, Annotations.DATA_MONGODB_QUERY); + if (!isQueryAnnotated) { - codeLenses.add(new CodeLens(range, refactorings.createFixCommand(COVERT_TO_QUERY_LABEL, createFixDescriptor(mb, document.getUri(), queryStatement)), null)); + codeLenses.add(new CodeLens(range, refactorings.createFixCommand(COVERT_TO_QUERY_LABEL, createFixDescriptor(mb, document.getUri(), metadata, methodMetadata.get())), null)); } Command impl = new Command("Implementation", GenAotQueryMethodImplProvider.CMD_NAVIGATE_TO_IMPL, List.of(new GenAotQueryMethodImplProvider.GoToImplParams( @@ -142,7 +159,7 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid if (!isQueryAnnotated) { Command queryTitle = new Command(); - queryTitle.setTitle(queryStatement); + queryTitle.setTitle(methodMetadata.get().getQueryStatement(metadata)); codeLenses.add(new CodeLens(range, queryTitle, null)); } } @@ -152,15 +169,31 @@ public class DataRepositoryAotMetadataCodeLensProvider implements CodeLensProvid return codeLenses; } - static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, String queryStatement) { + static FixDescriptor createFixDescriptor(IMethodBinding mb, String docUri, DataRepositoryAotMetadata metadata, DataRepositoryAotMetadataMethod methodMetadata) { return new FixDescriptor(AddAnnotationOverMethod.class.getName(), List.of(docUri), "Turn into `@Query`") + .withRecipeScope(RecipeScope.FILE) - .withParameters(Map.of("annotationType", Annotations.DATA_JPA_QUERY, "method", - "%s %s(%s)".formatted(mb.getDeclaringClass().getQualifiedName(), mb.getName(), - Arrays.stream(mb.getParameterTypes()).map(pt -> pt.getQualifiedName()) - .collect(Collectors.joining(", "))), - "attributes", List.of(new AddAnnotationOverMethod.Attribute("value", - "\"%s\"".formatted(StringEscapeUtils.escapeJava(queryStatement)))))); + + .withParameters(Map.of( + "annotationType", metadata.isJPA() ? Annotations.DATA_JPA_QUERY : Annotations.DATA_MONGODB_QUERY, + "method", "%s %s(%s)".formatted(mb.getDeclaringClass().getQualifiedName(), mb.getName(), + Arrays.stream(mb.getParameterTypes()) + .map(pt -> pt.getQualifiedName()) + .collect(Collectors.joining(", "))), + "attributes", createAttributeList(methodMetadata.getAttributesMap(metadata)))); } + + private static List createAttributeList(Map attributes) { + List result = new ArrayList<>(); + + Set keys = attributes.keySet(); + for (String key : keys) { + result.add(new AddAnnotationOverMethod.Attribute(key, "\"%s\"".formatted(StringEscapeUtils.escapeJava(attributes.get(key))))); + } + + return result; + } + + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataMethod.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataMethod.java index 935df8600..1c778b62c 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataMethod.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataMethod.java @@ -10,5 +10,99 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.data; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.springframework.util.StringUtils; + public record DataRepositoryAotMetadataMethod(String name, String signature, DataRepositoryAotMetadataQuery query) { + + public String getQueryStatement(DataRepositoryAotMetadata repository) { + if (repository != null && repository.isJPA()) { + return getJpaQueryStatement(); + } + else if (repository != null && repository.isMongoDb()) { + return getMongoDbQueryStatement(); + } + else { + return null; + } + } + + private String getJpaQueryStatement() { + return query() != null ? query.query(): null; + } + + private String getMongoDbQueryStatement() { + List parts = new ArrayList<>(); + + if (query == null) return null; + + if (query().filter() != null) { + if (!StringUtils.hasText(query().sort()) + && !StringUtils.hasText(query().fields()) + && !StringUtils.hasText(query().projection()) + && !StringUtils.hasText(query().pipeline())) { + + parts.add(query().filter()); + } + else { + parts.add("filter = \"" + query().filter() + "\""); + } + } + + if (query().fields() != null) { + parts.add("fields = \"" + query().fields() + "\""); + } + + if (query().sort() != null) { + parts.add("sort = \"" + query().sort() + "\""); + } + + if (query().projection() != null) { + parts.add("projection = \"" + query().projection() + "\""); + } + + if (query().pipeline() != null) { + parts.add("pipeline = \"" + query().pipeline() + "\""); + } + + return String.join(", ", parts); + } + + public Map getAttributesMap(DataRepositoryAotMetadata metadata) { + if (metadata != null && metadata.isJPA()) { + return Map.of("value", getJpaQueryStatement()); + } + else if (metadata != null && metadata.isMongoDb()) { + if (query != null) { + return createMongoDbQueryAttributes(); + } + } + + return Map.of(); + } + + private Map createMongoDbQueryAttributes() { + Map result = new HashMap<>(); + + if (query.filter() != null) { + result.put("value", query.filter()); + } + + if (query().fields() != null) { + result.put("fields", query().fields()); + } + + if (query().sort() != null) { + result.put("sort", query().sort()); + } + + // TODO; what about projection and pipeline ? + + return result; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java index b5fb02082..699621950 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryAotMetadataService.java @@ -13,8 +13,6 @@ package org.springframework.ide.vscode.boot.java.data; import java.io.File; import java.io.FileReader; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import org.eclipse.jdt.core.dom.IMethodBinding; @@ -25,7 +23,6 @@ import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser; import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod; -import org.springframework.util.StringUtils; import com.google.gson.Gson; @@ -71,58 +68,9 @@ public class DataRepositoryAotMetadataService { public String getQueryStatement(DataRepositoryAotMetadata metadata, IMethodBinding method) { DataRepositoryAotMetadataMethod methodMetadata = findMethod(metadata, method); - - if (methodMetadata != null) { - if (metadata.module() != null && metadata.module().toUpperCase().equals("JPA")) { - return getJpaQueryStatement(methodMetadata); - } - else if (metadata.module() != null && metadata.module().toUpperCase().equals("MONGODB")) { - return getMongoDbQueryStatement(methodMetadata); - } - } - - return null; + return methodMetadata.getQueryStatement(metadata); } - - private String getMongoDbQueryStatement(DataRepositoryAotMetadataMethod methodMetadata) { - List parts = new ArrayList<>(); - - if (methodMetadata.query().filter() != null) { - if (!StringUtils.hasText(methodMetadata.query().sort()) - && !StringUtils.hasText(methodMetadata.query().fields()) - && !StringUtils.hasText(methodMetadata.query().projection()) - && !StringUtils.hasText(methodMetadata.query().pipeline())) { - - parts.add(methodMetadata.query().filter()); - } - else { - parts.add("filter = \"" + methodMetadata.query().filter() + "\""); - } - } - - if (methodMetadata.query().fields() != null) { - parts.add("fields = \"" + methodMetadata.query().fields() + "\""); - } - - if (methodMetadata.query().sort() != null) { - parts.add("sort = \"" + methodMetadata.query().sort() + "\""); - } - - if (methodMetadata.query().projection() != null) { - parts.add("projection = \"" + methodMetadata.query().projection() + "\""); - } - - if (methodMetadata.query().pipeline() != null) { - parts.add("pipeline = \"" + methodMetadata.query().pipeline() + "\""); - } - - return String.join(", ", parts); - } - - private String getJpaQueryStatement(DataRepositoryAotMetadataMethod methodMetadata) { - return methodMetadata.query().query(); - } - + public DataRepositoryAotMetadataMethod findMethod(DataRepositoryAotMetadata metadata, IMethodBinding method) { String name = method.getName(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/QueryMethodCodeActionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/QueryMethodCodeActionProvider.java index 0c5878432..3153ed631 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/QueryMethodCodeActionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/QueryMethodCodeActionProvider.java @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.java.data; import java.net.URI; +import java.util.Optional; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; @@ -56,15 +57,26 @@ public class QueryMethodCodeActionProvider implements JdtAstCodeActionProvider { @Override public boolean visit(MethodDeclaration node) { cancelToken.checkCanceled(); + if (node.getStartPosition() <= region.getStart() && node.getStartPosition() + node.getLength() >= region.getEnd()) { int start = node.getStartPosition(); int end = node.getName().getStartPosition() + node.getName().getLength(); + if (start <= region.getStart() && end >= region.getEnd()) { + IMethodBinding binding = node.resolveBinding(); AnnotationHierarchies hierarchyAnnot = AnnotationHierarchies.get(node); - if (hierarchyAnnot != null && !hierarchyAnnot.isAnnotatedWith(binding, Annotations.DATA_JPA_QUERY)) { - DataRepositoryAotMetadataCodeLensProvider.getDataQuery(repositoryMetadataService, project, binding) - .map(query -> createCodeAction(binding, docURI, query)).ifPresent(collector::accept); + if (hierarchyAnnot != null + && !hierarchyAnnot.isAnnotatedWith(binding, Annotations.DATA_JPA_QUERY) + && !hierarchyAnnot.isAnnotatedWith(binding, Annotations.DATA_MONGODB_QUERY)) { + + Optional metadata = DataRepositoryAotMetadataCodeLensProvider.getMetadata(repositoryMetadataService, project, binding); + if (metadata.isPresent()) { + Optional methodMetadata = DataRepositoryAotMetadataCodeLensProvider.getMethodMetadata(repositoryMetadataService, metadata.get(), binding); + methodMetadata + .map(method -> createCodeAction(binding, docURI, metadata.get(), method)) + .ifPresent(collector::accept); + } } } return super.visit(node); @@ -75,9 +87,9 @@ public class QueryMethodCodeActionProvider implements JdtAstCodeActionProvider { }; } - private CodeAction createCodeAction(IMethodBinding mb, URI docUri, String query) { + private CodeAction createCodeAction(IMethodBinding mb, URI docUri, DataRepositoryAotMetadata metadata, DataRepositoryAotMetadataMethod method) { CodeAction ca = new CodeAction(); - ca.setCommand(refactorings.createFixCommand(TITLE, DataRepositoryAotMetadataCodeLensProvider.createFixDescriptor(mb, docUri.toASCIIString(), query))); + ca.setCommand(refactorings.createFixCommand(TITLE, DataRepositoryAotMetadataCodeLensProvider.createFixDescriptor(mb, docUri.toASCIIString(), metadata, method))); ca.setTitle(TITLE); ca.setKind(CodeActionKind.Refactor); return ca;