This commit is contained in:
aboyko
2025-05-22 12:09:56 -07:00
5 changed files with 176 additions and 81 deletions

View File

@@ -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");
}
}

View File

@@ -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<String> getDataQuery(DataRepositoryAotMetadataService repositoryMetadataService, IJavaProject project, IMethodBinding methodBinding) {
// static Optional<String> 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<DataRepositoryAotMetadata> getMetadata(DataRepositoryAotMetadataService dataRepositoryAotMetadataService, IJavaProject project, IMethodBinding methodBinding) {
final String repositoryClass = methodBinding.getDeclaringClass().getBinaryName().trim();
return Optional.ofNullable(dataRepositoryAotMetadataService.getRepositoryMetadata(project, repositoryClass));
}
static Optional<DataRepositoryAotMetadataMethod> 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<CodeLens> 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<CodeLens> createCodeLenses(MethodDeclaration node, TextDocument document, String queryStatement) {
private List<CodeLens> createCodeLenses(MethodDeclaration node, TextDocument document, DataRepositoryAotMetadata metadata) {
List<CodeLens> 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<DataRepositoryAotMetadataMethod> 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<AddAnnotationOverMethod.Attribute> createAttributeList(Map<String, String> attributes) {
List<AddAnnotationOverMethod.Attribute> result = new ArrayList<>();
Set<String> keys = attributes.keySet();
for (String key : keys) {
result.add(new AddAnnotationOverMethod.Attribute(key, "\"%s\"".formatted(StringEscapeUtils.escapeJava(attributes.get(key)))));
}
return result;
}
}

View File

@@ -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<String> 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<String, String> 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<String, String> createMongoDbQueryAttributes() {
Map<String, String> 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;
}
}

View File

@@ -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<String> 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();

View File

@@ -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<DataRepositoryAotMetadata> metadata = DataRepositoryAotMetadataCodeLensProvider.getMetadata(repositoryMetadataService, project, binding);
if (metadata.isPresent()) {
Optional<DataRepositoryAotMetadataMethod> 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;