diff --git a/headless-services/spring-boot-language-server/pom.xml b/headless-services/spring-boot-language-server/pom.xml index b13b7399b..298c81400 100644 --- a/headless-services/spring-boot-language-server/pom.xml +++ b/headless-services/spring-boot-language-server/pom.xml @@ -109,6 +109,11 @@ rewrite-java ${rewrite-version} + + org.openrewrite + rewrite-java-11 + ${rewrite-version} + commons-io 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 b57e497c8..e9d5aa42b 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 @@ -25,7 +25,7 @@ import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor; import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet; import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext; import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager; -import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; @@ -86,7 +86,7 @@ public class BootJavaCompletionEngineConfigurer { BootLanguageServerParams params, @Qualifier("adHocProperties") ProjectBasedPropertyIndexProvider adHocProperties, JavaSnippetManager snippetManager, - CompilationUnitCache cuCache) { + ORCompilationUnitCache cuCache) { SpringPropertyIndexProvider indexProvider = params.indexProvider; JavaProjectFinder javaProjectFinder = params.projectFinder; Map providers = new HashMap<>(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java index c91867dcb..7f82a279b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerBootApp.java @@ -155,10 +155,14 @@ public class BootLanguageServerBootApp { return SourceLinkFactory.createSourceLinks(server, cuCache, params.projectFinder); } - @Bean ORCompilationUnitCache cuCache(SimpleLanguageServer server, BootLanguageServerParams params) { + @Bean ORCompilationUnitCache orcuCache(SimpleLanguageServer server, BootLanguageServerParams params) { return new ORCompilationUnitCache(params.projectFinder, server, params.projectObserver); } + @Bean CompilationUnitCache cuCache(SimpleLanguageServer server, BootLanguageServerParams params) { + return new CompilationUnitCache(params.projectFinder, server, params.projectObserver); + } + @Bean SpringXMLCompletionEngine xmlCompletionEngine(SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, BootJavaConfig config) { return new SpringXMLCompletionEngine(server, projectFinder, symbolIndex, config); } @@ -188,7 +192,7 @@ public class BootLanguageServerBootApp { } } - @Bean JavaElementLocationProvider javaElementLocationProvider(SimpleLanguageServer server, CompilationUnitCache cuCache, JavaDocumentUriProvider javaDocUriProvider) { + @Bean JavaElementLocationProvider javaElementLocationProvider(SimpleLanguageServer server, ORCompilationUnitCache cuCache, JavaDocumentUriProvider javaDocUriProvider) { switch (LspClient.currentClient()) { case ECLIPSE: case VSCODE: diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java index ffe42d682..9a78e82f2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootLanguageServerInitializer.java @@ -11,7 +11,6 @@ package org.springframework.ide.vscode.boot.app; import java.util.List; -import java.util.Optional; import org.eclipse.lsp4j.MessageType; import org.slf4j.Logger; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java index 7f65893e0..e854d9500 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java @@ -11,15 +11,18 @@ package org.springframework.ide.vscode.boot.java.data; import java.util.Collection; +import java.util.List; import java.util.Optional; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.lsp4j.CompletionItemKind; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.ClassDeclaration; +import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.java.tree.TypeUtils; import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.util.BadLocationException; @@ -33,14 +36,14 @@ import org.springframework.util.StringUtils; public class DataRepositoryCompletionProcessor implements CompletionProvider { @Override - public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + public void provideCompletions(J node, Annotation annotation, int offset, IDocument doc, Collection completions) { } @Override - public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { - TypeDeclaration type = ASTUtils.findDeclaringType(node); - DataRepositoryDefinition repo = getDataRepositoryDefinition(type); + public void provideCompletions(J node, int offset, IDocument doc, Collection completions) { + ClassDeclaration declaration = ORAstUtils.findNode(node, ClassDeclaration.class); + DataRepositoryDefinition repo = getDataRepositoryDefinition(declaration, declaration.getType()); if (repo != null) { DomainType domainType = repo.getDomainType(); if (domainType != null) { @@ -100,41 +103,27 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider { return new FindByCompletionProposal(label.toString(), CompletionItemKind.Method, edits, null, null, Optional.of(additionalEdits), filter); } - private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type) { + private DataRepositoryDefinition getDataRepositoryDefinition(ClassDeclaration declaration, FullyQualified type) { if (type != null) { - ITypeBinding resolvedType = type.resolveBinding(); - return getDataRepositoryDefinition(type, resolvedType); - } - - return null; - } - - private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type, ITypeBinding resolvedType) { - if (resolvedType != null) { // interface analysis - ITypeBinding[] interfaces = resolvedType.getInterfaces(); - for (ITypeBinding resolvedInterface : interfaces) { - String simplifiedType = null; - if (resolvedInterface.isParameterizedType()) { - simplifiedType = resolvedInterface.getBinaryName(); - } - else { - simplifiedType = resolvedType.getQualifiedName(); - } + List interfaces = type.getInterfaces(); + for (FullyQualified resolvedInterface : interfaces) { + String simplifiedType = resolvedInterface.getFullyQualifiedName(); if (Constants.REPOSITORY_TYPE.equals(simplifiedType)) { DomainType domainType = null; - if (resolvedInterface.isParameterizedType()) { - ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments(); - if (typeParameters != null && typeParameters.length > 0) { - domainType = new DomainType(typeParameters[0]); - } - } + // TODO Fix for OR AST +// if (resolvedInterface.isParameterizedType()) { +// ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments(); +// if (typeParameters != null && typeParameters.length > 0) { +// domainType = new DomainType(typeParameters[0]); +// } +// } return createDataRepositoryDefinitionFromType(domainType); } else { - DataRepositoryDefinition repo = getDataRepositoryDefinition(type, resolvedInterface); + DataRepositoryDefinition repo = getDataRepositoryDefinition(declaration, resolvedInterface); if (repo != null) { return repo; } @@ -142,9 +131,9 @@ public class DataRepositoryCompletionProcessor implements CompletionProvider { } // super type analysis - ITypeBinding superclass = resolvedType.getSuperclass(); + FullyQualified superclass = type.getSupertype(); if (superclass != null) { - return getDataRepositoryDefinition(type, superclass); + return getDataRepositoryDefinition(declaration, superclass); } } return null; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java index ce68095c5..384896da0 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java @@ -15,12 +15,13 @@ import java.util.Collection; import java.util.Collections; import java.util.Map; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.NodeFinder; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.JavaType.FullyQualified; +import org.openrewrite.java.tree.TypeUtils; import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager; -import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; +import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.util.LanguageSpecific; @@ -37,9 +38,9 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec private Map completionProviders; private JavaSnippetManager snippets; - private CompilationUnitCache cuCache; + private ORCompilationUnitCache cuCache; - public BootJavaCompletionEngine(CompilationUnitCache cuCache, Map specificProviders, JavaSnippetManager snippets) { + public BootJavaCompletionEngine(ORCompilationUnitCache cuCache, Map specificProviders, JavaSnippetManager snippets) { this.cuCache = cuCache; this.completionProviders = specificProviders; this.snippets = snippets; @@ -49,7 +50,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec public Collection getCompletions(TextDocument document, int offset) throws Exception { return cuCache.withCompilationUnit(document, cu -> { if (cu != null) { - ASTNode node = NodeFinder.perform(cu, offset, 0); + J node = ORAstUtils.findAstNodeAt(cu, offset); if (node != null) { Collection completions = new ArrayList(); @@ -64,30 +65,25 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec }); } - private void collectCompletionsForAnnotations(ASTNode node, int offset, IDocument doc, Collection completions) { - Annotation annotation = null; - ASTNode exactNode = node; + private void collectCompletionsForAnnotations(J node, int offset, IDocument doc, Collection completions) { + Annotation annotation = ORAstUtils.findNode(node, Annotation.class); + J exactNode = node; - while (node != null && !(node instanceof Annotation)) { - node = node.getParent(); - } - - if (node != null) { - annotation = (Annotation) node; - ITypeBinding type = annotation.resolveTypeBinding(); + if (annotation != null) { + FullyQualified type = TypeUtils.asFullyQualified(annotation.getType()); if (type != null) { - String qualifiedName = type.getQualifiedName(); + String qualifiedName = type.getFullyQualifiedName(); if (qualifiedName != null) { CompletionProvider provider = this.completionProviders.get(qualifiedName); if (provider != null) { - provider.provideCompletions(exactNode, annotation, type, offset, doc, completions); + provider.provideCompletions(exactNode, annotation, offset, doc, completions); } } } } } - private void collectCompletions(ASTNode node, int offset, TextDocument document, Collection completions) { + private void collectCompletions(J node, int offset, TextDocument document, Collection completions) { if (node != null) { for (CompletionProvider completionProvider : this.completionProviders.values()) { completionProvider.provideCompletions(node, offset, document, completions); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java index 405cb7661..47c4d585a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java @@ -12,9 +12,8 @@ package org.springframework.ide.vscode.boot.java.handlers; import java.util.Collection; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -23,7 +22,7 @@ import org.springframework.ide.vscode.commons.util.text.IDocument; */ public interface CompletionProvider { - void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, IDocument doc, Collection completions); - void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions); + void provideCompletions(J node, Annotation annotation, int offset, IDocument doc, Collection completions); + void provideCompletions(J node, int offset, IDocument doc, Collection completions); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AbstractSourceLinks.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AbstractSourceLinks.java index 3b80f100a..5194727d6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AbstractSourceLinks.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AbstractSourceLinks.java @@ -16,11 +16,12 @@ import java.net.URISyntaxException; import java.nio.file.Path; import java.util.Optional; import java.util.Stack; +import java.util.concurrent.atomic.AtomicReference; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.tree.J.CompilationUnit; import org.openrewrite.java.tree.TypeUtils; -import org.openrewrite.marker.Position; +import org.openrewrite.marker.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; @@ -30,7 +31,9 @@ import org.springframework.ide.vscode.commons.java.IJavaModuleData; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.javadoc.TypeUrlProviderFromContainerUrl; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.util.text.Region; + +import reactor.util.function.Tuple2; +import reactor.util.function.Tuples; /** * Base logic for {@link SourceLinks} independent of any client @@ -132,11 +135,12 @@ public abstract class AbstractSourceLinks implements SourceLinks { }).map(sourcePath -> findCU(project, sourcePath).orElse(null)); } - protected Region findTypeRegion(CompilationUnit cu, String fqName) { + protected Tuple2 findTypeRegion(CompilationUnit cu, String fqName) { if (cu == null) { return null; } int[] values = new int[] {0, -1}; + AtomicReference range = new AtomicReference<>(); int lastDotIndex = fqName.lastIndexOf('.'); String packageName = fqName.substring(0, lastDotIndex); String typeName = fqName.substring(lastDotIndex + 1); @@ -147,11 +151,9 @@ public abstract class AbstractSourceLinks implements SourceLinks { public org.openrewrite.java.tree.J.ClassDeclaration visitClassDeclaration(org.openrewrite.java.tree.J.ClassDeclaration classDecl, Object p) { String fqName = classDecl.getType().getFullyQualifiedName(); visitedType.push(fqName); - if (values[1] < 0) { + if (range.get() == null) { if (String.join("$", visitedType.toArray(new String[visitedType.size()])).equals(typeName)) { - Position pos = classDecl.getName().getMarkers().findFirst(Position.class).orElseThrow(); - values[0] = pos.getStartPosition(); - values[1] = pos.getLength(); + range.set(classDecl.getName().getMarkers().findFirst(Range.class).orElseThrow()); } } if (values[1] < 0) { @@ -165,7 +167,7 @@ public abstract class AbstractSourceLinks implements SourceLinks { }.visitNonNull(cu, visitedType); } - return values[1] < 0 ? null : new Region(values[0], values[1]); + return Optional.of(range.get()).map(r -> r.getStart()).map(start -> Tuples.of(start.getLine(), start.getColumn())).orElse(null); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AtomSourceLinks.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AtomSourceLinks.java index bdb8b2df7..759246904 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AtomSourceLinks.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/AtomSourceLinks.java @@ -27,6 +27,8 @@ import org.springframework.ide.vscode.commons.util.text.Region; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; +import reactor.util.function.Tuple2; + /** * Source links for Atom client * @@ -54,10 +56,10 @@ public class AtomSourceLinks extends AbstractSourceLinks { @Override protected String positionLink(CompilationUnit cu, String fqName) { if (cu != null) { - Region region = findTypeRegion(cu, fqName); + Tuple2 region = findTypeRegion(cu, fqName); if (region != null) { - int column = cu.getColumnNumber(region.getOffset()); - int line = cu.getLineNumber(region.getOffset()); + int column = region.getT2(); + int line = region.getT1(); StringBuilder sb = new StringBuilder(); sb.append("&line="); sb.append(line); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java index 0cb5c7d1f..bb7927fea 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java @@ -26,21 +26,22 @@ import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; import org.springframework.ide.vscode.commons.java.IField; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.java.IMember; import org.springframework.ide.vscode.commons.java.IMethod; import org.springframework.ide.vscode.commons.java.IType; +import org.springframework.ide.vscode.commons.util.text.TextDocument; public class DefaultJavaElementLocationProvider implements JavaElementLocationProvider { private static final Logger log = LoggerFactory.getLogger(DefaultJavaElementLocationProvider.class); - private CompilationUnitCache cuCache; + private ORCompilationUnitCache cuCache; private JavaDocumentUriProvider javaDocUriProvider; - public DefaultJavaElementLocationProvider(CompilationUnitCache cuCache, JavaDocumentUriProvider javaDocUriProvider) { + public DefaultJavaElementLocationProvider(ORCompilationUnitCache cuCache, JavaDocumentUriProvider javaDocUriProvider) { this.cuCache = cuCache; this.javaDocUriProvider = javaDocUriProvider; } @@ -63,67 +64,68 @@ public class DefaultJavaElementLocationProvider implements JavaElementLocationPr if (cu == null) { return new Range(new Position(0, 0), new Position(0, 0)); } - cu.accept(new ASTVisitor() { - - private Range nameRange(SimpleName nameNode) { - int startOffset = nameNode.getStartPosition(); - int endOffset = nameNode.getLength() + startOffset; - - // Line -1 because for CU lines are starting from 1 - return new Range( - new Position(cu.getLineNumber(startOffset) - 1, cu.getColumnNumber(startOffset)), - new Position(cu.getLineNumber(endOffset) - 1, cu.getColumnNumber(endOffset))); - } - - @Override - public boolean visit(MethodDeclaration node) { - if (member instanceof IMethod) { - String bindingKey = node.resolveBinding().getKey(); - if (matchMethodBindingKeys(memberBindingKey, bindingKey)) { - range.set(nameRange(node.getName())); - return false; - } - } - return true; - } - - @Override - public boolean visit(EnumConstantDeclaration node) { - if (member instanceof IField) { - String bindingKey = node.resolveVariable().getKey(); - if (memberBindingKey.equals(bindingKey)) { - range.set(nameRange(node.getName())); - return false; - } - } - return true; - } - - @Override - public boolean visit(EnumDeclaration node) { - if (member instanceof IType) { - String bindingKey = node.resolveBinding().getKey(); - if (memberBindingKey.equals(bindingKey)) { - range.set(nameRange(node.getName())); - return false; - } - } - return true; - } - - @Override - public boolean visit(TypeDeclaration node) { - if (member instanceof IType) { - String bindingKey = node.resolveBinding().getKey(); - if (memberBindingKey.equals(bindingKey)) { - range.set(nameRange(node.getName())); - return false; - } - } - return true; - } - - }); + // TODO: Fix for OR AST +// cu.accept(new ASTVisitor() { +// +// private Range nameRange(SimpleName nameNode) { +// int startOffset = nameNode.getStartPosition(); +// int endOffset = nameNode.getLength() + startOffset; +// +// // Line -1 because for CU lines are starting from 1 +// return new Range( +// new Position(cu.getLineNumber(startOffset) - 1, cu.getColumnNumber(startOffset)), +// new Position(cu.getLineNumber(endOffset) - 1, cu.getColumnNumber(endOffset))); +// } +// +// @Override +// public boolean visit(MethodDeclaration node) { +// if (member instanceof IMethod) { +// String bindingKey = node.resolveBinding().getKey(); +// if (matchMethodBindingKeys(memberBindingKey, bindingKey)) { +// range.set(nameRange(node.getName())); +// return false; +// } +// } +// return true; +// } +// +// @Override +// public boolean visit(EnumConstantDeclaration node) { +// if (member instanceof IField) { +// String bindingKey = node.resolveVariable().getKey(); +// if (memberBindingKey.equals(bindingKey)) { +// range.set(nameRange(node.getName())); +// return false; +// } +// } +// return true; +// } +// +// @Override +// public boolean visit(EnumDeclaration node) { +// if (member instanceof IType) { +// String bindingKey = node.resolveBinding().getKey(); +// if (memberBindingKey.equals(bindingKey)) { +// range.set(nameRange(node.getName())); +// return false; +// } +// } +// return true; +// } +// +// @Override +// public boolean visit(TypeDeclaration node) { +// if (member instanceof IType) { +// String bindingKey = node.resolveBinding().getKey(); +// if (memberBindingKey.equals(bindingKey)) { +// range.set(nameRange(node.getName())); +// return false; +// } +// } +// return true; +// } +// +// }); return range.get(); }); if (r == null) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/VSCodeSourceLinks.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/VSCodeSourceLinks.java index 2ee3e156f..9451b38bb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/VSCodeSourceLinks.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/VSCodeSourceLinks.java @@ -18,7 +18,8 @@ import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; import org.springframework.ide.vscode.commons.java.IJavaModuleData; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.util.text.Region; + +import reactor.util.function.Tuple2; /** * VSCode specific source links implementation @@ -40,10 +41,10 @@ public class VSCodeSourceLinks extends AbstractSourceLinks { @Override protected String positionLink(CompilationUnit cu, String fqName) { if (cu != null) { - Region region = findTypeRegion(cu, fqName); + Tuple2 region = findTypeRegion(cu, fqName); if (region != null) { - int column = cu.getColumnNumber(region.getOffset()); - int line = cu.getLineNumber(region.getOffset()); + int column = region.getT2(); + int line = region.getT1(); StringBuilder sb = new StringBuilder(); sb.append('#'); sb.append(line); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java index 80d5ffcc9..1d87131fb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java @@ -12,13 +12,14 @@ package org.springframework.ide.vscode.boot.java.scope; import java.util.Collection; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.StringLiteral; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.java.tree.J.Assignment; +import org.openrewrite.java.tree.J.Empty; +import org.openrewrite.java.tree.J.Literal; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; +import org.springframework.ide.vscode.boot.java.utils.ORAstUtils; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -28,15 +29,15 @@ import org.springframework.ide.vscode.commons.util.text.IDocument; public class ScopeCompletionProcessor implements CompletionProvider { @Override - public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + public void provideCompletions(J node, Annotation annotation, int offset, IDocument doc, Collection completions) { try { - if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) { - MemberValuePair memberPair = (MemberValuePair) node.getParent(); + if (node instanceof Assignment) { + Assignment assignment = (Assignment) node; // case: @Scope(value=<*>) - if ("value".equals(memberPair.getName().toString()) && memberPair.getValue().toString().equals("$missing$")) { + if ("value".equals(assignment.getVariable().toString()) && assignment.getAssignment() == null) { for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); completions.add(proposal); @@ -44,33 +45,38 @@ public class ScopeCompletionProcessor implements CompletionProvider { } } // case: @Scope(<*>) - else if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { + else if (node instanceof Empty && ORAstUtils.getParent(node) == annotation) { for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); completions.add(proposal); } } - else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { - // case: @Scope("...") - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); + else if (node instanceof Literal && ORAstUtils.getParent(node) == annotation) { + String nodeStr = node.printTrimmed(); + if (nodeStr.startsWith("\"") && nodeStr.endsWith("\"")) { + // case: @Scope("...") + Range range = node.getMarkers().findFirst(Range.class).orElseThrow(); + String prefix = doc.get(range.getStart().getOffset(), offset - range.getStart().getOffset()); for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { if (completion.getValue().startsWith(prefix)) { - ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); + ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, range.getStart().getOffset(), range.getStart().getOffset() + range.length(), prefix); completions.add(proposal); } } + } } - else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair) { - MemberValuePair memberPair = (MemberValuePair) node.getParent(); - - // case: @Scope(value=<*>) - if ("value".equals(memberPair.getName().toString()) && node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); + // case: @Scope(value=<*>) + else if (node instanceof Literal && ORAstUtils.getParent(node) instanceof Assignment) { + Assignment assignment = (Assignment) ORAstUtils.getParent(node); + String nodeStr = node.printTrimmed(); + + if ("value".equals(assignment.getVariable().printTrimmed()) && nodeStr.startsWith("\"") && nodeStr.endsWith("\"")) { + Range range = node.getMarkers().findFirst(Range.class).orElseThrow(); + String prefix = doc.get(range.getStart().getOffset(), offset - range.getStart().getOffset()); for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { if (completion.getValue().startsWith(prefix)) { - ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); + ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, range.getStart().getOffset(), range.getStart().getOffset() + range.length(), prefix); completions.add(proposal); } } @@ -83,7 +89,7 @@ public class ScopeCompletionProcessor implements CompletionProvider { } @Override - public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { + public void provideCompletions(J node, int offset, IDocument doc, Collection completions) { } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java index 4e5326c50..fb90cce3e 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java @@ -13,9 +13,9 @@ package org.springframework.ide.vscode.boot.java.snippets; import java.util.List; import java.util.Optional; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.lsp4j.CompletionItemKind; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.CompilationUnit; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; @@ -45,7 +45,7 @@ public class JavaSnippet { } public Optional generateCompletion(Supplier snippetBuilderFactory, - DocumentRegion query, ASTNode node, CompilationUnit cu) { + DocumentRegion query, J node, CompilationUnit cu) { if (context.appliesTo(node)) { return Optional.of( diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java index 9b018a425..2d4fbccf3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java @@ -10,21 +10,39 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.snippets; +import java.util.List; import java.util.Optional; -import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.lsp4j.CompletionItemKind; -import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.Result; +import org.openrewrite.TreeVisitor; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J.CompilationUnit; +import org.openrewrite.java.tree.JavaSourceFile; +import org.openrewrite.shaded.jgit.diff.Edit; +import org.openrewrite.shaded.jgit.diff.EditList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.utils.JGitUtils; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; +import org.springframework.ide.vscode.commons.util.text.IDocument; +import org.springframework.ide.vscode.commons.util.text.IRegion; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.base.Supplier; public class JavaSnippetCompletion implements ICompletionProposal{ + + private static final Logger log = LoggerFactory.getLogger(JavaSnippetCompletion.class); private DocumentRegion query; private JavaSnippet javaSnippet; @@ -65,17 +83,69 @@ public class JavaSnippetCompletion implements ICompletionProposal{ @Override public Optional getAdditionalEdit() { - ImportRewrite rewrite = ImportRewrite.create(cu, true); - javaSnippet.getImports().ifPresent((imprts -> - { - for (String imprt : imprts) { - rewrite.addImport(imprt); + Recipe r = new Recipe() { + @Override + public String getDisplayName() { + return "Add Imports"; } - })); + + @Override + protected TreeVisitor getVisitor() { + return new JavaIsoVisitor<>() { + public JavaSourceFile visitJavaSourceFile(JavaSourceFile cu, ExecutionContext p) { + javaSnippet.getImports().ifPresent(imports -> imports.forEach(i -> maybeAddImport(i))); + return cu; + }; + }; + } + + }; + + List results = r.run(List.of(cu)); + if (!results.isEmpty()) { + Result result = results.get(0); + TextDocument newDoc = new TextDocument(null, LanguageId.PLAINTEXT, 0, result.getAfter().printAll()); - DocumentEdits edit = rewrite.createEdit(query.getDocument()); - - return edit != null ? Optional.of(edit) : Optional.empty(); + EditList diff = JGitUtils.getDiff(result.getBefore().printAll(), newDoc.get()); + if (!diff.isEmpty()) { + IDocument doc = query.getDocument(); + DocumentEdits edits = new DocumentEdits(doc, false); + for (Edit e : diff) { + try { + switch(e.getType()) { + case DELETE: + edits.delete(doc.getLineOffset(e.getBeginA()), getStartOfLine(doc, e.getEndA())); + break; + case INSERT: + edits.insert(doc.getLineOffset(e.getBeginA()), newDoc.textBetween(newDoc.getLineOfOffset(e.getBeginB()), getStartOfLine(newDoc, e.getEndB()))); + break; + case REPLACE: + edits.replace(doc.getLineOfOffset(e.getBeginA()), getStartOfLine(doc, e.getEndA()), newDoc.textBetween(newDoc.getLineOfOffset(e.getBeginB()), getStartOfLine(newDoc, e.getEndB()))); + break; + case EMPTY: + break; + } + } catch (BadLocationException ex) { + log.error("Diff conversion failed", ex); + } + } + return Optional.of(edits); + } + } + return Optional.empty(); } + + private static int getStartOfLine(IDocument doc, int lineNumber) { + IRegion lineInformation = doc.getLineInformation(lineNumber); + if (lineInformation != null) { + return lineInformation.getOffset(); + } + if (lineNumber > 0) { + IRegion currentLine = doc.getLineInformation(lineNumber - 1); + return currentLine.getOffset() + currentLine.getLength(); + } + return 0; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java index ee3a471da..4e683c7bb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java @@ -10,11 +10,11 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.snippets; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.ClassDeclaration; public interface JavaSnippetContext { - JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof TypeDeclaration; + JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof ClassDeclaration; - boolean appliesTo(ASTNode node); + boolean appliesTo(J node); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java index 0f0a22d75..57e744cdf 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java @@ -14,8 +14,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.CompilationUnit; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.CompilationUnit; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; @@ -47,7 +47,7 @@ public class JavaSnippetManager { } - public void getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu, Collection completions) { + public void getCompletions(IDocument doc, int offset, J node, CompilationUnit cu, Collection completions) { DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset); for (JavaSnippet javaSnippet : snippets) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/JGitUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/JGitUtils.java new file mode 100644 index 000000000..189d918ae --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/JGitUtils.java @@ -0,0 +1,20 @@ +package org.springframework.ide.vscode.boot.java.utils; + +import java.nio.charset.StandardCharsets; + +import org.openrewrite.shaded.jgit.diff.EditList; +import org.openrewrite.shaded.jgit.diff.HistogramDiff; +import org.openrewrite.shaded.jgit.diff.RawText; +import org.openrewrite.shaded.jgit.diff.RawTextComparator; + +public class JGitUtils { + + public static EditList getDiff(String txt1, String txt2) { + RawText rt1 = new RawText(txt1.getBytes(StandardCharsets.UTF_8)); + RawText rt2 = new RawText(txt2.getBytes(StandardCharsets.UTF_8)); + EditList diffList = new EditList(); + diffList.addAll(new HistogramDiff().diff(RawTextComparator.DEFAULT, rt1, rt2)); + return diffList; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java new file mode 100644 index 000000000..c5b777978 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORAstUtils.java @@ -0,0 +1,104 @@ +package org.springframework.ide.vscode.boot.java.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import org.openrewrite.Cursor; +import org.openrewrite.SourceFile; +import org.openrewrite.Tree; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.CompilationUnit; +import org.openrewrite.marker.Marker; +import org.openrewrite.marker.Range; + +public class ORAstUtils { + + private static class AncestersMarker implements Marker { + + private UUID uuid; + private List ancesters = List.of(); + + public AncestersMarker(List ancesters) { + this.uuid = Tree.randomId(); + this.ancesters = ancesters; + } + + @Override + public UUID getId() { + return uuid; + } + + @SuppressWarnings("unchecked") + public T getFirstAnsector(Class clazz) { + if (ancesters != null) { + for (J node : ancesters) { + if (clazz.isInstance(node)) { + return (T) node; + } + } + } + return null; + } + + public J getParent() { + if (ancesters != null && !ancesters.isEmpty()) { + return ancesters.get(0); + } + return null; + } + } + + public static J findAstNodeAt(CompilationUnit cu, int offset) { + AtomicReference f = new AtomicReference<>(); + new JavaIsoVisitor>() { + public J visit(Tree tree, AtomicReference found) { + if (tree == null) { + return null; + } + if (found.get() == null && tree instanceof J) { + J node = (J) tree; + Range range = node.getMarkers().findFirst(Range.class).orElse(null); + if (range != null + && range.getStart().getOffset() <= offset + && offset <= range.getEnd().getOffset()) { + super.visit(tree, found); + if (found.get() == null) { + List ancesters = new ArrayList<>(); + for (Cursor c = getCursor(); c != null && !(c.getValue() instanceof SourceFile); c = c.getParent()) { + Object o = c.getValue(); + if (o instanceof J) { + ancesters.add((J) o); + } + } + J n = node.withMarkers(node.getMarkers().addIfAbsent(new AncestersMarker(ancesters))); + found.set(n); + return n; + } + } else { + return (J) tree; + } + } + return (J) tree; + }; + }.visitNonNull(cu, f); + return f.get(); + } + + @SuppressWarnings("unchecked") + public static T findNode(J node, Class clazz) { + if (clazz.isInstance(node)) { + return (T) node; + } + AncestersMarker ancestry = node.getMarkers().findFirst(AncestersMarker.class).orElseThrow(); + return ancestry.getFirstAnsector(clazz); + } + + public static J getParent(J node) { + AncestersMarker ancestry = node.getMarkers().findFirst(AncestersMarker.class).orElseThrow(); + return ancestry.getParent(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java index 28e7fbfad..a9e28cb9f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/ORCompilationUnitCache.java @@ -3,7 +3,9 @@ package org.springframework.ide.vscode.boot.java.utils; import java.io.ByteArrayInputStream; import java.io.File; import java.net.URI; +import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -11,6 +13,7 @@ import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.io.IOUtils; @@ -118,22 +121,27 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab private JavaParser loadJavaParser(IJavaProject project) { try { - return javaParsers.get(project, () -> JavaParser.fromJavaVersion().classpath(getClasspathEntries(project)).build()); + return javaParsers.get(project, () -> { + List classpath = getClasspathEntries(project).stream().map(s -> new File(s).toPath()).collect(Collectors.toList()); + JavaParser jp = JavaParser.fromJavaVersion().build(); + jp.setClasspath(classpath); + return jp; + }); } catch (ExecutionException e) { logger.error("{}", e); return null; } } - private static String[] getClasspathEntries(IJavaProject project) throws Exception { + private static Set getClasspathEntries(IJavaProject project) throws Exception { if (project == null) { - return new String[0]; + return Collections.emptySet(); } else { IClasspath classpath = project.getClasspath(); Stream classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream(); return classpathEntries .filter(file -> file.exists()) - .map(file -> file.getAbsolutePath()).toArray(String[]::new); + .map(file -> file.getAbsolutePath()).collect(Collectors.toSet()); } } @@ -200,7 +208,8 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab } }); - Result result = new UpdateSourcePositions().run(javaParser.parseInputs(List.of(input), null, new InMemoryExecutionContext())).get(0); + List parseInputs = javaParser.parseInputs(List.of(input), null, new InMemoryExecutionContext()); + Result result = new UpdateSourcePositions().run(parseInputs).get(0); logger.info("CU Cache: created new AST for {}", uri.toString()); @@ -234,5 +243,26 @@ public class ORCompilationUnitCache implements DocumentContentProvider, Disposab return requestor.apply(null); } + + /** + * Never research shows at the AST is thread-safe when used in read-only mode: + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=58314 + * + * This means that the previous implemented synchronization around the requestor + * working on the AST is not necessary as long as the requestor operates in read-only + * mode on the AST nodes. + * + * Warning: Callers should take care to do all AST processing inside of the requestor callback and + * not pass of AST nodes to helper functions that work aynchronously or store AST nodes or ITypeBindings + * for later use. The JDT ASTs are not thread safe! + */ + @Deprecated + public T withCompilationUnit(TextDocument document, Function requestor) { + IJavaProject project = this.projectFinder != null ? projectFinder.find(document.getId()).orElse(null) : null; + + URI uri = URI.create(document.getUri()); + return withCompilationUnit(project, uri, requestor); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java index e882cc476..7837f35de 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java @@ -19,12 +19,13 @@ import java.util.Optional; import java.util.Set; import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.J.Annotation; +import org.openrewrite.marker.Range; import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.PropertyInfo; @@ -54,7 +55,7 @@ public class ValueCompletionProcessor implements CompletionProvider { } @Override - public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + public void provideCompletions(J node, Annotation annotation, int offset, IDocument doc, Collection completions) { try { @@ -75,28 +76,29 @@ public class ValueCompletionProcessor implements CompletionProvider { completions.add(proposal); } } + // TODO: Get these cases fixed for OR AST // case: @Value(prefix<*>) - else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { - computeProposalsForSimpleName(node, completions, offset, doc); - } - // case: @Value(value=<*>) - else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair - && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { - computeProposalsForSimpleName(node, completions, offset, doc); - } - // case: @Value("prefix<*>") - else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForStringLiteral(node, completions, offset, doc); - } - } - // case: @Value(value="prefix<*>") - else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair - && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { - if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForStringLiteral(node, completions, offset, doc); - } - } +// else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { +// computeProposalsForSimpleName(node, completions, offset, doc); +// } +// // case: @Value(value=<*>) +// else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair +// && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { +// computeProposalsForSimpleName(node, completions, offset, doc); +// } +// // case: @Value("prefix<*>") +// else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { +// if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// computeProposalsForStringLiteral(node, completions, offset, doc); +// } +// } +// // case: @Value(value="prefix<*>") +// else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair +// && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { +// if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { +// computeProposalsForStringLiteral(node, completions, offset, doc); +// } +// } } catch (Exception e) { e.printStackTrace(); @@ -104,7 +106,7 @@ public class ValueCompletionProcessor implements CompletionProvider { } @Override - public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { + public void provideCompletions(J node, int offset, IDocument doc, Collection completions) { } private void computeProposalsForSimpleName(ASTNode node, Collection completions, int offset, @@ -130,14 +132,17 @@ public class ValueCompletionProcessor implements CompletionProvider { } } - private void computeProposalsForStringLiteral(ASTNode node, Collection completions, int offset, + private void computeProposalsForStringLiteral(J node, Collection completions, int offset, IDocument doc) throws BadLocationException { - String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1)); + Range r = node.getMarkers().findFirst(Range.class).orElseThrow(); + int nodeStartOffset = r.getStart().getOffset(); + int nodeLength = r.length(); + String prefix = identifyPropertyPrefix(doc.get(nodeStartOffset + 1, offset - (nodeStartOffset + 1)), offset - (nodeStartOffset + 1)); int startOffset = offset - prefix.length(); int endOffset = offset; - String prePrefix = doc.get(node.getStartPosition() + 1, offset - prefix.length() - node.getStartPosition() - 1); + String prePrefix = doc.get(nodeStartOffset + 1, offset - prefix.length() - nodeStartOffset - 1); String preCompletion; if (prePrefix.endsWith("${")) { @@ -150,7 +155,7 @@ public class ValueCompletionProcessor implements CompletionProvider { preCompletion = "${"; } - String fullNodeContent = doc.get(node.getStartPosition(), node.getLength()); + String fullNodeContent = doc.get(nodeStartOffset, nodeLength); String postCompletion = isClosingBracketMissing(fullNodeContent + preCompletion) ? "}" : ""; List> matches = findMatches(prefix, doc); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SourceLinksTestConf.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SourceLinksTestConf.java index 2ce2ec818..7c41ac168 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SourceLinksTestConf.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SourceLinksTestConf.java @@ -17,7 +17,7 @@ import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness; import org.springframework.ide.vscode.boot.java.links.SourceLinks; import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks; -import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; import org.springframework.ide.vscode.boot.java.utils.SymbolCache; import org.springframework.ide.vscode.boot.java.utils.SymbolCacheVoid; import org.springframework.ide.vscode.boot.java.utils.test.MockProjectObserver; @@ -65,7 +65,7 @@ public class SourceLinksTestConf { return (DefaultSpringPropertyIndexProvider) serverParams.indexProvider; } - @Bean SourceLinks sourceLinks(CompilationUnitCache cuCache, JavaProjectFinder projectFinder) { + @Bean SourceLinks sourceLinks(ORCompilationUnitCache cuCache, JavaProjectFinder projectFinder) { return new VSCodeSourceLinks(cuCache, projectFinder); } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java index a2fa69576..eaaf24f36 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/scope/test/ScopeCompletionTest.java @@ -67,6 +67,19 @@ public class ScopeCompletionTest { "@Scope(\"websocket\"<*>)"); } + @Test + public void testBracketsWithSpacesInsideCompletion() throws Exception { + prepareCase("@Scope(\"onClass\")", "@Scope( <*>)"); + assertAnnotationCompletions( + "@Scope( \"application\"<*>)", + "@Scope( \"globalSession\"<*>)", + "@Scope( \"prototype\"<*>)", + "@Scope( \"request\"<*>)", + "@Scope( \"session\"<*>)", + "@Scope( \"singleton\"<*>)", + "@Scope( \"websocket\"<*>)"); + } + @Test public void testEmptyStringLiteralCompletion() throws Exception { prepareCase("@Scope(\"onClass\")", "@Scope(\"<*>\")"); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java index a5bc15346..bf22d4f17 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/VSCodeSourceLinksTest.java @@ -24,7 +24,7 @@ import org.eclipse.lsp4j.TextDocumentIdentifier; import org.gradle.internal.impldep.com.google.common.collect.ImmutableList; import org.junit.Test; import org.springframework.ide.vscode.boot.java.links.VSCodeSourceLinks; -import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.ORCompilationUnitCache; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.javadoc.JavaDocProviders; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -61,7 +61,7 @@ public class VSCodeSourceLinksTest { @Test public void testJavaSourceUrl() throws Exception { MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); - Optional url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication"); + Optional url = new VSCodeSourceLinks(new ORCompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "com.example.EmptyBoot15WebAppApplication"); assertTrue(url.isPresent()); Path projectPath = Paths.get(project.pom().getParent()); URI uri = URI.create(url.get()); @@ -78,7 +78,7 @@ public class VSCodeSourceLinksTest { @Test public void testClasspathResourceOnTomcatUrl() throws Exception { MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); - Optional url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), new JavaProjectFinder() { + Optional url = new VSCodeSourceLinks(new ORCompilationUnitCache(null, null, null), new JavaProjectFinder() { @Override public Optional find(TextDocumentIdentifier doc) { @@ -107,7 +107,7 @@ public class VSCodeSourceLinksTest { @Test public void testJarUrl() throws Exception { MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); - Optional url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication"); + Optional url = new VSCodeSourceLinks(new ORCompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.boot.autoconfigure.SpringBootApplication"); assertTrue(url.isPresent()); String headerPart = url.get().substring(0, url.get().indexOf('?')); assertEquals("jdt://contents/spring-boot-autoconfigure-1.5.8.RELEASE.jar/org.springframework.boot.autoconfigure/SpringBootApplication.class", headerPart); @@ -118,7 +118,7 @@ public class VSCodeSourceLinksTest { @Test public void testJarUrlInnerType() throws Exception { MavenJavaProject project = mavenProjectsCache.get("empty-boot-15-web-app"); - Optional url = new VSCodeSourceLinks(new CompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback"); + Optional url = new VSCodeSourceLinks(new ORCompilationUnitCache(null, null, null), null).sourceLinkUrlForFQName(project, "org.springframework.web.client.RestTemplate$AcceptHeaderRequestCallback"); assertTrue(url.isPresent()); String headerPart = url.get().substring(0, url.get().indexOf('?')); assertEquals("jdt://contents/spring-web-4.3.12.RELEASE.jar/org.springframework.web.client/RestTemplate$AcceptHeaderRequestCallback.class", headerPart);