diff --git a/headless-services/spring-boot-language-server/pom.xml b/headless-services/spring-boot-language-server/pom.xml
index 211f5aa57..be39c9c18 100644
--- a/headless-services/spring-boot-language-server/pom.xml
+++ b/headless-services/spring-boot-language-server/pom.xml
@@ -119,6 +119,11 @@
org.eclipse.jdt.core
${jdt.core.version}
+
+ org.eclipse.jdt
+ org.eclipse.jdt.core.manipulation
+ 1.21.300
+
commons-io
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java
index c43f0e3cd..a980de8a2 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java
@@ -172,7 +172,7 @@ public class BootJavaCompletionEngineConfigurer {
providers.put(Annotations.SCHEDULED, new AnnotationAttributeCompletionProcessor(javaProjectFinder, Map.of(
"cron", new CronExpressionCompletionProvider())));
- providers.put(Annotations.BEAN, new BeanCompletionProvider(javaProjectFinder, springIndex, rewriteRefactorings, config));
+ providers.put(Annotations.BEAN, new BeanCompletionProvider(javaProjectFinder, springIndex, rewriteRefactorings, config, cuCache));
return new BootJavaCompletionEngine(cuCache, providers, snippetManager);
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java
index 40ed08c8b..33041d70f 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProposal.java
@@ -10,33 +10,44 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.beans;
-import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import org.apache.commons.text.similarity.JaroWinklerSimilarity;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.jdt.core.ICompilationUnit;
+import org.eclipse.jdt.core.JavaModelException;
+import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Assignment;
-import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FieldAccess;
+import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.ThisExpression;
-import org.eclipse.lsp4j.Command;
+import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
+import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
+import org.eclipse.jdt.internal.corext.dom.ASTNodes;
+import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
+import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
+import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
+import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.CompletionItemLabelDetails;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.text.edits.TextEditGroup;
import org.openrewrite.java.tree.JavaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
+import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
+import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposalWithScore;
-import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
-import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
-import org.springframework.ide.vscode.commons.rewrite.java.InjectBeanCompletionRecipe;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
@@ -65,8 +76,13 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
private String prefix;
private DocumentEdits edits;
- public BeanCompletionProposal(ASTNode node, int offset, IDocument doc, String beanId, String beanType,
- String fieldName, String className, RewriteRefactorings rewriteRefactorings) {
+ private IJavaProject project;
+
+ private CompilationUnitCache cuCache;
+
+ public BeanCompletionProposal(IJavaProject project, ASTNode node, int offset, IDocument doc, String beanId, String beanType,
+ String fieldName, String className, RewriteRefactorings rewriteRefactorings, CompilationUnitCache cuCache) {
+ this.project = project;
this.node = node;
this.offset = offset;
this.doc = doc;
@@ -75,6 +91,7 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
this.fieldName = fieldName;
this.className = className;
this.rewriteRefactorings = rewriteRefactorings;
+ this.cuCache = cuCache;
this.prefix = computePrefix();
this.edits = computeEdit();
this.score = /*FuzzyMatcher.matchScore*/computeJaroWinklerScore(prefix, beanId);
@@ -133,27 +150,97 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
return null;
}
- private DocumentEdits computeEdit() {
+ private DocumentEdits createFieldDeclaration(CompilationUnit cuRefactorNode) throws JavaModelException, IllegalArgumentException {
+ CompilationUnit cu = ASTNodes.getParent(node, org.eclipse.jdt.core.dom.CompilationUnit.class);
+ CompilationUnitRewrite fCuRewrite = new CompilationUnitRewrite(null, (ICompilationUnit) cu.getTypeRoot(), cuRefactorNode, Map.of());
+// Type type= getConstantType();
+
+ AST ast= fCuRewrite.getAST();
+ VariableDeclarationFragment variableDeclarationFragment= ast.newVariableDeclarationFragment();
+ variableDeclarationFragment.setName(ast.newSimpleName(fieldName));
+
+ FieldDeclaration fieldDeclaration= ast.newFieldDeclaration(variableDeclarationFragment);
+// fieldDeclaration.setType(type);
+ fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.PRIVATE_KEYWORD));
+ fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD));
+
+ AbstractTypeDeclaration parent = ASTNodes.getParent(node, AbstractTypeDeclaration.class);
+ Assert.isNotNull(parent);
+ ListRewrite listRewrite= fCuRewrite.getASTRewrite().getListRewrite(parent, parent.getBodyDeclarationsProperty());
+ TextEditGroup msg= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_declare_constant);
+ listRewrite.insertFirst(fieldDeclaration, msg);
+
+ TextEdit edit = fCuRewrite.getASTRewrite().rewriteAST();
DocumentEdits edits = new DocumentEdits(doc, false);
- if (isInsideConstructor(node)) {
- if (node instanceof Block) {
- edits.insert(offset, "this.%s = %s;".formatted(fieldName, fieldName));
- } else {
- if (node.getParent() instanceof Assignment || node.getParent() instanceof FieldAccess) {
- edits.replace(offset - prefix.length(), offset, "%s = %s;".formatted(fieldName, fieldName));
- } else {
- edits.replace(offset - prefix.length(), offset, "this.%s = %s;".formatted(fieldName, fieldName));
- }
- }
- } else {
- if (node instanceof Block) {
- edits.insert(offset, fieldName);
- } else {
- edits.replace(offset - prefix.length(), offset, fieldName);
- }
- }
return edits;
}
+
+ private DocumentEdits computeEdit() {
+// try {
+ CompilationUnit cu = ASTNodes.getParent(node, org.eclipse.jdt.core.dom.CompilationUnit.class);
+ RefactoringASTParser parser= new RefactoringASTParser(IASTSharedValues.SHARED_AST_LEVEL);
+
+ try {
+// CompilationUnit cuRefactorNode = cuCache.parseCuWithReusableEnv(project, URI.create(doc.getUri()));
+ return createFieldDeclaration(cu);
+ } catch (JavaModelException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ } catch (IllegalArgumentException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+// } catch (ExecutionException e) {
+// // TODO Auto-generated catch block
+// e.printStackTrace();
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ return new DocumentEdits(doc, false);
+
+
+// ASTRewrite astRewrite= ASTRewrite.create(cuRefactorNode.getAST());
+// ImportRewrite importRewrite= StubUtility.createImportRewrite(cu, true);
+//
+// TypeDeclaration declaringType = ASTUtils.findDeclaringType(node);
+//
+// ITypeBinding currTypeBinding= declaringType.resolveBinding();
+// ListRewrite memberRewriter= null;
+//
+// ASTNode node= cuRefactorNode.findDeclaringNode(currTypeBinding);
+// if (node instanceof AnonymousClassDeclaration) {
+// memberRewriter= astRewrite.getListRewrite(node, AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY);
+// } else if (node instanceof AbstractTypeDeclaration) {
+// ChildListPropertyDescriptor property= ((AbstractTypeDeclaration) node).getBodyDeclarationsProperty();
+// memberRewriter= astRewrite.getListRewrite(node, property);
+// } else {
+// throw new IllegalArgumentException();
+// // not possible, we checked this in the constructor
+// }
+
+// } catch (JavaModelException e) {
+// throw new IllegalStateException(e);
+// }
+
+// if (isInsideConstructor(node)) {
+// if (node instanceof Block) {
+// edits.insert(offset, "this.%s = %s;".formatted(fieldName, fieldName));
+// } else {
+// if (node.getParent() instanceof Assignment || node.getParent() instanceof FieldAccess) {
+// edits.replace(offset - prefix.length(), offset, "%s = %s;".formatted(fieldName, fieldName));
+// } else {
+// edits.replace(offset - prefix.length(), offset, "this.%s = %s;".formatted(fieldName, fieldName));
+// }
+// }
+// } else {
+// if (node instanceof Block) {
+// edits.insert(offset, fieldName);
+// } else {
+// edits.replace(offset - prefix.length(), offset, fieldName);
+// }
+// }
+// return edits;
+ }
@Override
public DocumentEdits getTextEdit() {
@@ -179,14 +266,14 @@ public class BeanCompletionProposal implements ICompletionProposalWithScore {
.formatted(beanId, beanType));
}
- @Override
- public Optional getCommand() {
- FixDescriptor f = new FixDescriptor(InjectBeanCompletionRecipe.class.getName(), List.of(this.doc.getUri()),
- "Inject bean completions")
- .withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", fieldName, "classFqName", className))
- .withRecipeScope(RecipeScope.NODE);
- return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f));
- }
+// @Override
+// public Optional getCommand() {
+// FixDescriptor f = new FixDescriptor(InjectBeanCompletionRecipe.class.getName(), List.of(this.doc.getUri()),
+// "Inject bean completions")
+// .withParameters(Map.of("fullyQualifiedName", beanType, "fieldName", fieldName, "classFqName", className))
+// .withRecipeScope(RecipeScope.NODE);
+// return Optional.of(rewriteRefactorings.createFixCommand("Inject bean '%s'".formatted(beanId), f));
+// }
@Override
public double getScore() {
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java
index 30e7e31fc..c77df7b45 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/beans/BeanCompletionProvider.java
@@ -36,6 +36,7 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchie
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
+import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
@@ -55,12 +56,15 @@ public class BeanCompletionProvider implements CompletionProvider {
private final BootJavaConfig config;
+ private CompilationUnitCache cuCache;
+
public BeanCompletionProvider(JavaProjectFinder javaProjectFinder, SpringMetamodelIndex springIndex,
- RewriteRefactorings rewriteRefactorings, BootJavaConfig config) {
+ RewriteRefactorings rewriteRefactorings, BootJavaConfig config, CompilationUnitCache cuCache) {
this.javaProjectFinder = javaProjectFinder;
this.springIndex = springIndex;
this.rewriteRefactorings = rewriteRefactorings;
this.config = config;
+ this.cuCache = cuCache;
}
@Override
@@ -134,8 +138,8 @@ public class BeanCompletionProvider implements CompletionProvider {
for (int i = 0; i < Integer.MAX_VALUE && fieldNames.contains(fieldName); i++, fieldName = "%s_%d".formatted(bean.getName(), i)) {
// nothing
}
- BeanCompletionProposal proposal = new BeanCompletionProposal(node, offset, doc, bean.getName(),
- bean.getType(), fieldName, className, rewriteRefactorings);
+ BeanCompletionProposal proposal = new BeanCompletionProposal(project, node, offset, doc, bean.getName(),
+ bean.getType(), fieldName, className, rewriteRefactorings, cuCache);
if (proposal.getScore() > 0) {
beanCompletions.add(proposal);
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java
index 58ce9fd5f..4d5284490 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/CompilationUnitCache.java
@@ -305,6 +305,20 @@ public final class CompilationUnitCache implements DocumentContentProvider {
}
return cuFuture;
}
+
+ public CompilationUnit parseCuWithReusableEnv(IJavaProject project, URI uri) throws ExecutionException, Exception {
+ logger.info("Started parsing CU for " + uri);
+ Tuple2, INameEnvironmentWithProgress> lookupEnvTuple = loadLookupEnvTuple(project);
+ String uriStr = uri.toASCIIString();
+ String unitName = uriStr.substring(uriStr.lastIndexOf("/") + 1); // skip over '/'
+ CompilationUnit cUnit = parse2(fetchContent(uri).toCharArray(), uriStr, unitName, lookupEnvTuple.getT1(), lookupEnvTuple.getT2(),
+ annotationHierarchies.get(project.getLocationUri(), AnnotationHierarchies::new));
+
+ logger.debug("CU Cache: created new AST for {}", uri.toASCIIString());
+
+ logger.info("Parsed successfully CU for " + uri);
+ return cUnit;
+ }
public static CompilationUnit parse2(char[] source, String docURI, String unitName, IJavaProject project) throws Exception {
List classpaths = createClasspath(getClasspathEntries(project));