diff --git a/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java b/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java index ac7f98169..efd90ba61 100644 --- a/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java +++ b/headless-services/commons/commons-gradle/src/main/java/org/springframework/ide/vscode/commons/gradle/GradleJavaProject.java @@ -73,6 +73,11 @@ public class GradleJavaProject extends AbstractJavaProject { boolean update() throws Exception { return classpath.update(); } + + @Override + public String getLocationUri() { + return projectDir.toURI().toString(); + } } diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java index 06c2e3b31..05858afe4 100644 --- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IJavaProject.java @@ -17,6 +17,7 @@ public interface IJavaProject extends IJavaElement { final static String PROJECT_CACHE_FOLDER = ".sts4-cache"; IClasspath getClasspath(); + String getLocationUri(); @Override default String getElementName() { @@ -32,5 +33,6 @@ public interface IJavaProject extends IJavaElement { default boolean exists() { return getClasspath().exists(); } + } diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java index ccad4991b..c937c8855 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java @@ -80,4 +80,9 @@ public class MavenJavaProject extends AbstractJavaProject { return "MavenJavaProject("+classpath.getName()+")"; } + @Override + public String getLocationUri() { + return pom.getParentFile().toURI().toString(); + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 1d199e5c2..9d26be9bf 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -193,7 +193,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent } private void initialize(InitializeParams params) { - this.indexer.initialize(server.getWorkspaceRoots()); +// this.indexer.initialize(server.getWorkspaceRoots()); } private void initialized() { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index bb2298081..2516ca5ec 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -19,22 +19,21 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.BlockingQueue; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import com.google.common.collect.ImmutableList; - import org.apache.commons.io.FileUtils; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; @@ -53,7 +52,6 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.SymbolKind; import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.eclipse.lsp4j.WorkspaceFolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.BootLanguageServerParams; @@ -69,13 +67,14 @@ import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserve import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; -import org.springframework.ide.vscode.commons.util.ExceptionUtil; import org.springframework.ide.vscode.commons.util.Futures; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; +import com.google.common.collect.ImmutableList; + /** * @author Martin Lippert */ @@ -92,34 +91,40 @@ public class SpringIndexer { private final ConcurrentMap> symbolsByDoc; private final ConcurrentMap> addonInformationByDoc; - private final Thread updateWorker; - private final BlockingQueue updateQueue; + private final ConcurrentMap> symbolsByProject; + private final ConcurrentMap> addonInformationByProject; + + private final ExecutorService updateQueue; private static final Logger log = LoggerFactory.getLogger(SpringIndexer.class); private final Listener projectListener = new Listener() { - @Override public void created(IJavaProject project) { log.debug("project created event: {}", project.getElementName()); - refresh(); + initializeProject(project); } @Override public void changed(IJavaProject project) { log.debug("project changed event: {}", project.getElementName()); - refresh(); + initializeProject(project); } @Override public void deleted(IJavaProject project) { log.debug("project deleted event: {}", project.getElementName()); - refresh(); + deleteProject(project); } - }; - private volatile InitializeItem lastInitializeItem; + private SimpleWorkspaceService getWorkspaceService() { + return server.getServer().getWorkspaceService(); + } + + private ProjectObserver getProjectObserver() { + return params.projectObserver; + } public SpringIndexer(SimpleLanguageServer server, BootLanguageServerParams params, AnnotationHierarchyAwareLookup specificProviders) { log.debug("Creating {}", this); @@ -130,32 +135,15 @@ public class SpringIndexer { this.symbols = Collections.synchronizedList(new ArrayList<>()); this.symbolsByDoc = new ConcurrentHashMap<>(); + this.symbolsByProject = new ConcurrentHashMap<>(); this.addonInformation = Collections.synchronizedList(new ArrayList<>()); this.addonInformationByDoc = new ConcurrentHashMap<>(); + this.addonInformationByProject = new ConcurrentHashMap<>(); - this.updateQueue = new LinkedBlockingQueue<>(); - this.updateWorker = new Thread(new Runnable() { - @Override - public void run() { - try { - while (true) { - WorkerItem workerItem = updateQueue.take(); - log.debug("dequeued {}", workerItem); - workerItem.run(); - } - } - catch (InterruptedException e) { - // ignore - } - catch (Exception e) { - e.printStackTrace(); - } - } - }, "Spring Annotation Index Update Worker"); - server.onInitialized(updateWorker::start); + this.updateQueue = Executors.newSingleThreadExecutor(); + getWorkspaceService().onDidChangeWorkspaceFolders(evt -> { log.debug("workspace roots have changed event arrived - added: " + evt.getEvent().getAdded() + " - removed: " + evt.getEvent().getRemoved()); - refresh(); }); if (getProjectObserver() != null) { @@ -163,10 +151,6 @@ public class SpringIndexer { } } - private ProjectObserver getProjectObserver() { - return params.projectObserver; - } - public void serverInitialized() { List globPattern = Arrays.asList("**/*.java"); getWorkspaceService().getFileObserver().onFileDeleted(globPattern, (file) -> { @@ -177,72 +161,11 @@ public class SpringIndexer { }); } - private SimpleWorkspaceService getWorkspaceService() { - return server.getServer().getWorkspaceService(); - } - - public CompletableFuture initialize(Collection workspaceRoots) { - InitializeItem toCancel = null; - try { - synchronized(this) { - toCancel = lastInitializeItem; - //Careful do not cancel until created and setup new item. Otherwise it creates a - //race condition in the test harness which needs to be able to ensure initialization - //is completed. - lastInitializeItem = new InitializeItem(workspaceRoots.toArray(new WorkspaceFolder[workspaceRoots.size()])); - updateQueue.put(lastInitializeItem); - return lastInitializeItem.getFuture(); - } - } catch (Throwable e) { - log.error("", e); - return Futures.error(e); - } finally { - try { - if (toCancel!=null && !toCancel.getFuture().isDone()) { - toCancel.getFuture().cancel(false); - } - } catch (Exception e) { - //ignore - } - } - } - - public boolean isInitializing() { - return lastInitializeItem != null && !lastInitializeItem.getFuture().isDone(); - } - - public void waitForInitializeTask() { - InitializeItem lastInitializeItem = this.lastInitializeItem; - while (lastInitializeItem != null) { - if (!lastInitializeItem.getFuture().isDone()) { - try { - log.debug("Wating for {}", lastInitializeItem); - lastInitializeItem.getFuture().get(); - } catch (Exception e) { - log.debug("Waiting for {} aborted", lastInitializeItem); - log.debug(ExceptionUtil.getMessage(e)); - } - lastInitializeItem = this.lastInitializeItem; - } else { - log.debug("No need to wait for {}", lastInitializeItem); - lastInitializeItem = this.lastInitializeItem==lastInitializeItem ? null : this.lastInitializeItem; - } - } - } - - private void refresh() { - synchronized (this) { - Collection roots = server.getWorkspaceRoots(); - log.debug("refresh spring indexer for roots: {}", roots.toString()); - initialize(roots); - } - } - public void shutdown() { try { synchronized(this) { - if (updateWorker != null && updateWorker.isAlive()) { - updateWorker.interrupt(); + if (updateQueue != null && !updateQueue.isShutdown()) { + updateQueue.shutdownNow(); } if (getProjectObserver() != null) { @@ -254,17 +177,36 @@ public class SpringIndexer { } } + public CompletableFuture initializeProject(IJavaProject project) { + try { + InitializeProject initializeItem = new InitializeProject(project); + return CompletableFuture.runAsync(initializeItem, this.updateQueue); + } catch (Throwable e) { + log.error("", e); + return Futures.error(e); + } + } + + public CompletableFuture deleteProject(IJavaProject project) { + try { + DeleteProject initializeItem = new DeleteProject(project); + return CompletableFuture.runAsync(initializeItem, this.updateQueue); + } catch (Throwable e) { + log.error("", e); + return Futures.error(e); + } + } + public CompletableFuture updateDocument(String docURI, String content) { synchronized(this) { - if (docURI.endsWith(".java") && lastInitializeItem != null) { + if (docURI.endsWith(".java")) { try { Optional maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI)); if (maybeProject.isPresent()) { String[] classpathEntries = getClasspathEntries(maybeProject.get()); - - UpdateItem updateItem = new UpdateItem(docURI, content, classpathEntries); - updateQueue.put(updateItem); - return updateItem.getFuture(); + + UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, content, classpathEntries); + return CompletableFuture.runAsync(updateItem, this.updateQueue); } } catch (Exception e) { @@ -279,29 +221,32 @@ public class SpringIndexer { public CompletableFuture deleteDocument(String deletedDocURI) { synchronized(this) { try { - DeleteItem deleteItem = new DeleteItem(deletedDocURI); - updateQueue.put(deleteItem); - return deleteItem.getFuture(); + Optional maybeProject = projectFinder.find(new TextDocumentIdentifier(deletedDocURI)); + if (maybeProject.isPresent()) { + DeleteItem deleteItem = new DeleteItem(maybeProject.get(), deletedDocURI); + return CompletableFuture.runAsync(deleteItem, this.updateQueue); + } } catch (Exception e) { log.error("", e); return Futures.error(e); } } + + return null; } public CompletableFuture createDocument(String docURI) { synchronized(this) { - if (docURI.endsWith(".java") && lastInitializeItem != null) { + if (docURI.endsWith(".java")) { try { Optional maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI)); if (maybeProject.isPresent()) { String[] classpathEntries = getClasspathEntries(maybeProject.get()); String content = FileUtils.readFileToString(new File(new URI(docURI))); - UpdateItem updateItem = new UpdateItem(docURI, content, classpathEntries); - updateQueue.put(updateItem); - return updateItem.getFuture(); + UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, content, classpathEntries); + return CompletableFuture.runAsync(updateItem, this.updateQueue); } } catch (Exception e) { @@ -314,8 +259,6 @@ public class SpringIndexer { } public List getAllSymbols(String query) { - waitForInitializeTask(); - if (query != null && query.length() > 0) { return searchMatchingSymbols(this.symbols, query); } else { @@ -324,13 +267,10 @@ public class SpringIndexer { } public List getSymbols(String docURI) { - waitForInitializeTask(); return this.symbolsByDoc.get(docURI); } public List getAllAdditionalInformation(Predicate filter) { - waitForInitializeTask(); - if (filter != null) { return addonInformation.stream().filter(filter).collect(Collectors.toList()); } @@ -340,46 +280,29 @@ public class SpringIndexer { } public List getAdditonalInformation(String docURI) { - waitForInitializeTask(); List info = this.addonInformationByDoc.get(docURI); return info == null ? ImmutableList.of() : info; } private List searchMatchingSymbols(List allsymbols, String query) { - waitForInitializeTask(); return allsymbols.stream() .filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query)) .collect(Collectors.toList()); } - private void scanFiles(WorkspaceFolder directory) { - try { - Map, List> projects = Files.walk(Paths.get(new URI(directory.getUri()))) - .filter(path -> path.getFileName().toString().endsWith(".java")) - .filter(Files::isRegularFile) - .map(path -> path.toAbsolutePath().toString()) - .collect(Collectors.groupingBy((javaFile) -> projectFinder.find(new TextDocumentIdentifier(new File(javaFile).toURI().toString())))); - - projects.forEach((maybeProject, files) -> maybeProject.ifPresent(project -> scanProject(project, files.toArray(new String[0])))); - } - catch (Exception e) { - e.printStackTrace(); - } - } - private void scanProject(IJavaProject project, String[] files) { try { ASTParser parser = ASTParser.newParser(AST.JLS10); String[] classpathEntries = getClasspathEntries(project); - scanFiles(parser, files, classpathEntries); + scanFiles(project, parser, files, classpathEntries); } catch (Exception e) { e.printStackTrace(); } } - private void scanFile(String docURI, String content, String[] classpathEntries) throws Exception { + private void scanFile(IJavaProject project, String docURI, String content, String[] classpathEntries) throws Exception { ASTParser parser = ASTParser.newParser(AST.JLS10); Map options = JavaCore.getOptions(); JavaCore.setComplianceOptions(JavaCore.VERSION_10, options); @@ -400,22 +323,12 @@ public class SpringIndexer { CompilationUnit cu = (CompilationUnit) parser.createAST(null); if (cu != null) { - List oldSymbols = symbolsByDoc.remove(docURI); - if (oldSymbols != null) { - symbols.removeAll(oldSymbols); - } - - List oldAddOnInformation = addonInformationByDoc.remove(docURI); - if (oldAddOnInformation != null) { - addonInformation.removeAll(oldAddOnInformation); - } - AtomicReference docRef = new AtomicReference<>(); - scanAST(cu, docURI, docRef, content); + scanAST(project, cu, docURI, docRef, content); } } - private void scanFiles(ASTParser parser, String[] javaFiles, String[] classpathEntries) throws Exception { + private void scanFiles(IJavaProject project, ASTParser parser, String[] javaFiles, String[] classpathEntries) throws Exception { Map options = JavaCore.getOptions(); JavaCore.setComplianceOptions(JavaCore.VERSION_10, options); @@ -434,20 +347,20 @@ public class SpringIndexer { public void acceptAST(String sourceFilePath, CompilationUnit cu) { String docURI = UriUtil.toUri(new File(sourceFilePath)).toString(); AtomicReference docRef = new AtomicReference<>(); - scanAST(cu, docURI, docRef, null); + scanAST(project, cu, docURI, docRef, null); } }; parser.createASTs(javaFiles, null, new String[0], requestor, null); } - private void scanAST(final CompilationUnit cu, final String docURI, AtomicReference docRef, final String content) { + private void scanAST(final IJavaProject project, final CompilationUnit cu, final String docURI, AtomicReference docRef, final String content) { cu.accept(new ASTVisitor() { @Override public boolean visit(TypeDeclaration node) { try { - extractSymbolInformation(node, docURI, docRef, content); + extractSymbolInformation(project, node, docURI, docRef, content); } catch (Exception e) { e.printStackTrace(); @@ -458,7 +371,7 @@ public class SpringIndexer { @Override public boolean visit(MethodDeclaration node) { try { - extractSymbolInformation(node, docURI, docRef, content); + extractSymbolInformation(project, node, docURI, docRef, content); } catch (Exception e) { e.printStackTrace(); @@ -469,7 +382,7 @@ public class SpringIndexer { @Override public boolean visit(SingleMemberAnnotation node) { try { - extractSymbolInformation(node, docURI, docRef, content); + extractSymbolInformation(project, node, docURI, docRef, content); } catch (Exception e) { e.printStackTrace(); @@ -481,7 +394,7 @@ public class SpringIndexer { @Override public boolean visit(NormalAnnotation node) { try { - extractSymbolInformation(node, docURI, docRef, content); + extractSymbolInformation(project, node, docURI, docRef, content); } catch (Exception e) { e.printStackTrace(); @@ -493,7 +406,7 @@ public class SpringIndexer { @Override public boolean visit(MarkerAnnotation node) { try { - extractSymbolInformation(node, docURI, docRef, content); + extractSymbolInformation(project, node, docURI, docRef, content); } catch (Exception e) { e.printStackTrace(); @@ -504,7 +417,7 @@ public class SpringIndexer { }); } - private void extractSymbolInformation(TypeDeclaration typeDeclaration, String docURI, AtomicReference docRef, String content) throws Exception { + private void extractSymbolInformation(IJavaProject project, TypeDeclaration typeDeclaration, String docURI, AtomicReference docRef, String content) throws Exception { Collection providers = symbolProviders.getAll(); if (!providers.isEmpty()) { TextDocument doc = getTempTextDocument(docURI, docRef, content); @@ -512,20 +425,14 @@ public class SpringIndexer { Collection sbls = provider.getSymbols(typeDeclaration, doc); if (sbls != null) { sbls.forEach(enhancedSymbol -> { - symbols.add(enhancedSymbol.getSymbol()); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); - - if (enhancedSymbol.getAdditionalInformation() != null) { - addonInformation.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - } + addSymbol(project, docURI, enhancedSymbol); }); } } } } - private void extractSymbolInformation(MethodDeclaration methodDeclaration, String docURI, AtomicReference docRef, String content) throws Exception { + private void extractSymbolInformation(IJavaProject project, MethodDeclaration methodDeclaration, String docURI, AtomicReference docRef, String content) throws Exception { Collection providers = symbolProviders.getAll(); if (!providers.isEmpty()) { TextDocument doc = getTempTextDocument(docURI, docRef, content); @@ -533,20 +440,14 @@ public class SpringIndexer { Collection sbls = provider.getSymbols(methodDeclaration, doc); if (sbls != null) { sbls.forEach(enhancedSymbol -> { - symbols.add(enhancedSymbol.getSymbol()); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); - - if (enhancedSymbol.getAdditionalInformation() != null) { - addonInformation.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - } + addSymbol(project, docURI, enhancedSymbol); }); } } } } - private void extractSymbolInformation(Annotation node, String docURI, AtomicReference docRef, String content) throws Exception { + private void extractSymbolInformation(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content) throws Exception { ITypeBinding typeBinding = node.resolveTypeBinding(); if (typeBinding != null) { @@ -558,21 +459,14 @@ public class SpringIndexer { Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); if (sbls != null) { sbls.forEach(enhancedSymbol -> { - symbols.add(enhancedSymbol.getSymbol()); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); - - if (enhancedSymbol.getAdditionalInformation() != null) { - addonInformation.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); - } + addSymbol(project, docURI, enhancedSymbol); }); } } } else { SymbolInformation symbol = provideDefaultSymbol(node, docURI, docRef, content); if (symbol != null) { - symbols.add(symbol); - symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(symbol); + addSymbol(project, docURI, new EnhancedSymbolInformation(symbol, null)); } } } @@ -625,136 +519,176 @@ public class SpringIndexer { .map(path -> path.toAbsolutePath().toString()).toArray(String[]::new); } - /** - * inner class to capture items for the update worker - */ - private interface WorkerItem { - public void run(); - public CompletableFuture getFuture(); - - } - - private static AtomicInteger initItemId = new AtomicInteger(0); - - private class InitializeItem implements WorkerItem { + private class InitializeProject implements Runnable { - private int id = initItemId.incrementAndGet(); - - private final WorkspaceFolder[] workspaceRoots; - private final CompletableFuture future; + private final IJavaProject project; - public InitializeItem(WorkspaceFolder[] workspaceRoots) { - this.workspaceRoots = workspaceRoots; - this.future = new CompletableFuture(); + public InitializeProject(IJavaProject project) { + this.project = project; log.debug("{} created ", this); } - @Override - public CompletableFuture getFuture() { - return future; - } - @Override public void run() { log.debug("{} starting...", this); try { - if (!future.isCancelled()) { -// log.debug("initialze spring indexer task started for roots: " + Arrays.toString(workspaceRoots)); - symbols.clear(); - symbolsByDoc.clear(); + removeSymbolsByProject(project); - addonInformation.clear(); - addonInformationByDoc.clear(); + String projectUri = project.getLocationUri(); + List files = Files.walk(Paths.get(new URI(projectUri))) + .filter(path -> path.getFileName().toString().endsWith(".java")) + .filter(Files::isRegularFile) + .map(path -> path.toAbsolutePath().toString()) + .collect(Collectors.toList()); - for (WorkspaceFolder root : workspaceRoots) { - SpringIndexer.this.scanFiles(root); - } + SpringIndexer.this.scanProject(project, (String[]) files.toArray(new String[files.size()])); -// log.debug("initialze spring indexer task completed for roots: " + Arrays.toString(workspaceRoots)); - - future.complete(null); - log.debug("{} completed", this); - } - else { - log.debug("{} skipped because it was canceled", this); - } + log.debug("{} completed", this); } catch (Throwable e) { log.error("{} threw exception", this, e); } } - @Override - public String toString() { - return "InitItem("+id+")"; - } } - private class UpdateItem implements WorkerItem { + private class DeleteProject implements Runnable { + + private final IJavaProject project; + + public DeleteProject(IJavaProject project) { + this.project = project; + log.debug("{} created ", this); + } + + @Override + public void run() { + log.debug("{} starting...", this); + try { + removeSymbolsByProject(project); + log.debug("{} completed", this); + } catch (Throwable e) { + log.error("{} threw exception", this, e); + } + } + + } + + private class UpdateItem implements Runnable { private final String docURI; private final String content; private final String[] classpathEntries; + private final IJavaProject project; - private final CompletableFuture future; - - public UpdateItem(String docURI, String content, String[] classpathEntries) { + public UpdateItem(IJavaProject project, String docURI, String content, String[] classpathEntries) { + this.project = project; this.docURI = docURI; this.content = content; this.classpathEntries = classpathEntries; - this.future = new CompletableFuture(); - } - - @Override - public CompletableFuture getFuture() { - return future; } @Override public void run() { try { - SpringIndexer.this.scanFile(docURI, content, classpathEntries); + removeSymbolsByDoc(project, docURI); + SpringIndexer.this.scanFile(project, docURI, content, classpathEntries); } catch (Exception e) { log.error("{}", e); } - future.complete(null); } } - private class DeleteItem implements WorkerItem { + private class DeleteItem implements Runnable { private final String docURI; - private final CompletableFuture future; + private IJavaProject project; - public DeleteItem(String docURI) { + public DeleteItem(IJavaProject project, String docURI) { + this.project = project; this.docURI = docURI; - this.future = new CompletableFuture(); - } - - @Override - public CompletableFuture getFuture() { - return future; } @Override public void run() { try { - - List oldSymbols = symbolsByDoc.remove(docURI); - if (oldSymbols != null) { - symbols.removeAll(oldSymbols); - } - - List oldAddInInformation = addonInformationByDoc.remove(docURI); - if (oldAddInInformation != null) { - addonInformation.removeAll(oldAddInInformation); - } - + removeSymbolsByDoc(project, docURI); } catch (Exception e) { log.error("{}", e); } - future.complete(null); } } + + private void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol) { + symbols.add(enhancedSymbol.getSymbol()); + symbolsByDoc.computeIfAbsent(docURI, s -> new ArrayList()).add(enhancedSymbol.getSymbol()); + symbolsByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()).add(enhancedSymbol.getSymbol()); + + if (enhancedSymbol.getAdditionalInformation() != null) { + addonInformation.addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + addonInformationByDoc.computeIfAbsent(docURI, s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + addonInformationByProject.computeIfAbsent(project.getElementName(), s -> new ArrayList()).addAll(Arrays.asList(enhancedSymbol.getAdditionalInformation())); + } + } + + private void removeSymbolsByDoc(IJavaProject project, String docURI) { + List oldSymbols = symbolsByDoc.remove(docURI); + if (oldSymbols != null) { + symbols.removeAll(oldSymbols); + + List projectSymbols = symbolsByProject.get(project.getElementName()); + if (projectSymbols != null) { + projectSymbols.removeAll(oldSymbols); + } + } + + List oldAddInInformation = addonInformationByDoc.remove(docURI); + if (oldAddInInformation != null) { + addonInformation.removeAll(oldAddInInformation); + + List projectAddOns = addonInformationByProject.get(project.getElementName()); + if (projectAddOns != null) { + projectAddOns.removeAll(oldAddInInformation); + } + } + + } + + private void removeSymbolsByProject(IJavaProject project) { + List oldSymbols = symbolsByProject.remove(project.getElementName()); + if (oldSymbols != null) { + symbols.removeAll(oldSymbols); + + Set keySet = symbolsByDoc.keySet(); + Iterator docIter = keySet.iterator(); + while (docIter.hasNext()) { + String docURI = docIter.next(); + List docSymbols = symbolsByDoc.get(docURI); + docSymbols.removeAll(oldSymbols); + + if (docSymbols.isEmpty()) { + docIter.remove(); + } + } + } + + List oldAddInInformation = addonInformationByProject.remove(project.getElementName()); + if (oldAddInInformation != null) { + addonInformation.removeAll(oldAddInInformation); + + Set keySet = addonInformationByDoc.keySet(); + Iterator docIter = keySet.iterator(); + while (docIter.hasNext()) { + String docURI = docIter.next(); + List docAddons = addonInformationByDoc.get(docURI); + docAddons.removeAll(oldAddInInformation); + + if (docAddons.isEmpty()) { + docIter.remove(); + } + } + } + + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java index 2218d81ea..9a809b4f2 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java @@ -219,6 +219,7 @@ public class JdtLsProjectCache implements JavaProjectsService { return classpath; } + @Override public String getLocationUri() { return classpath.projectUri; } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java index bbb9240b4..c6ff2e79e 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java @@ -11,15 +11,19 @@ package org.springframework.ide.vscode.boot.java.beans.test; import java.io.File; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; -import org.springframework.ide.vscode.boot.java.beans.test.SpringIndexerHarness.TestSymbolInfo; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.project.harness.BootJavaLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -30,6 +34,8 @@ public class SpringIndexerBeansTest { private AnnotationHierarchyAwareLookup symbolProviders; private BootJavaLanguageServerHarness harness; + private File directory; + private SpringIndexer indexer; @Before public void setup() throws Exception { @@ -38,105 +44,81 @@ public class SpringIndexerBeansTest { symbolProviders.put(Annotations.COMPONENT, new ComponentSymbolProvider()); harness = BootJavaLanguageServerHarness.builder().build(); - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI())); + harness.intialize(null); + + indexer = harness.getServerWrapper().getComponents().getSpringIndexer(); + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); + + String projectDir = directory.toURI().toString(); + IJavaProject project = harness.getServerWrapper().getComponents().getProjectFinder().find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.initializeProject(project); + initProject.get(5, TimeUnit.SECONDS); } @Test public void testScanSimpleConfigurationClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleConfiguration.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@Configuration", "@+ 'simpleConfiguration' (@Configuration <: @Component) SimpleConfiguration"), - symbol("@Bean", "@+ 'simpleBean' (@Bean) BeanClass") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Configuration", "@+ 'simpleConfiguration' (@Configuration <: @Component) SimpleConfiguration"), + SpringIndexerHarness.symbol("@Bean", "@+ 'simpleBean' (@Bean) BeanClass") ); } @Test public void testScanSpecialConfigurationClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/SpecialConfiguration.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@Configuration", "@+ 'specialConfiguration' (@Configuration <: @Component) SpecialConfiguration"), + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Configuration", "@+ 'specialConfiguration' (@Configuration <: @Component) SpecialConfiguration"), // @Bean("implicitNamedBean") - symbol("implicitNamedBean", "@+ 'implicitNamedBean' (@Bean) BeanClass"), + SpringIndexerHarness.symbol("implicitNamedBean", "@+ 'implicitNamedBean' (@Bean) BeanClass"), // @Bean(value="valueBean") - symbol("valueBean", "@+ 'valueBean' (@Bean) BeanClass"), + SpringIndexerHarness.symbol("valueBean", "@+ 'valueBean' (@Bean) BeanClass"), // @Bean(value= {"valueBean1", "valueBean2"}) - symbol("valueBean1", "@+ 'valueBean1' (@Bean) BeanClass"), - symbol("valueBean2", "@+ 'valueBean2' (@Bean) BeanClass"), + SpringIndexerHarness.symbol("valueBean1", "@+ 'valueBean1' (@Bean) BeanClass"), + SpringIndexerHarness.symbol("valueBean2", "@+ 'valueBean2' (@Bean) BeanClass"), // @Bean(name="namedBean") - symbol("namedBean", "@+ 'namedBean' (@Bean) BeanClass"), + SpringIndexerHarness.symbol("namedBean", "@+ 'namedBean' (@Bean) BeanClass"), // @Bean(name= {"namedBean1", "namedBean2"}) - symbol("namedBean1", "@+ 'namedBean1' (@Bean) BeanClass"), - symbol("namedBean2", "@+ 'namedBean2' (@Bean) BeanClass") + SpringIndexerHarness.symbol("namedBean1", "@+ 'namedBean1' (@Bean) BeanClass"), + SpringIndexerHarness.symbol("namedBean2", "@+ 'namedBean2' (@Bean) BeanClass") ); } @Test public void testScanAbstractBeanConfiguration() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/AbstractBeanConfiguration.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@Configuration", "@+ 'abstractBeanConfiguration' (@Configuration <: @Component) AbstractBeanConfiguration") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Configuration", "@+ 'abstractBeanConfiguration' (@Configuration <: @Component) AbstractBeanConfiguration") ); } @Test public void testScanSimpleComponentClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleComponent.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@Component", "@+ 'simpleComponent' (@Component) SimpleComponent") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Component", "@+ 'simpleComponent' (@Component) SimpleComponent") ); } - @Test public void testScanSimpleControllerClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - + @Test + public void testScanSimpleControllerClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleController.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@Controller", "@+ 'simpleController' (@Controller <: @Component) SimpleController") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Controller", "@+ 'simpleController' (@Controller <: @Component) SimpleController") ); } - @Test public void testScanRestControllerClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - + @Test + public void testScanRestControllerClass() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleRestController.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@RestController", "@+ 'simpleRestController' (@RestController <: @Controller, @Component) SimpleRestController") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@RestController", "@+ 'simpleRestController' (@RestController <: @Controller, @Component) SimpleRestController") ); } - - //////////////////////////////// - // harness code - - private SpringIndexerHarness createIndexerHarness() { - return new SpringIndexerHarness(harness.getServer(), harness.getServerParams(), symbolProviders); - } - - private TestSymbolInfo symbol(String coveredText, String label) { - return new TestSymbolInfo(coveredText, label); - } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java index 8b45d4609..a4bcee028 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerFunctionBeansTest.java @@ -11,15 +11,19 @@ package org.springframework.ide.vscode.boot.java.beans.test; import java.io.File; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.junit.Before; import org.junit.Test; import org.springframework.ide.vscode.boot.java.Annotations; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; -import org.springframework.ide.vscode.boot.java.beans.test.SpringIndexerHarness.TestSymbolInfo; import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.project.harness.BootJavaLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -30,6 +34,8 @@ public class SpringIndexerFunctionBeansTest { private AnnotationHierarchyAwareLookup symbolProviders; private BootJavaLanguageServerHarness harness; + private SpringIndexer indexer; + private File directory; @Before public void setup() throws Exception { @@ -38,97 +44,67 @@ public class SpringIndexerFunctionBeansTest { symbolProviders.put(Annotations.COMPONENT, new ComponentSymbolProvider()); harness = BootJavaLanguageServerHarness.builder().build(); - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI())); + harness.intialize(null); + + indexer = harness.getServerWrapper().getComponents().getSpringIndexer(); + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); + + String projectDir = directory.toURI().toString(); + IJavaProject project = harness.getServerWrapper().getComponents().getProjectFinder().find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.initializeProject(project); + initProject.get(5, TimeUnit.SECONDS); } @Test public void testScanSimpleFunctionBean() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/FunctionClass.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("@Configuration", "@+ 'functionClass' (@Configuration <: @Component) FunctionClass"), - symbol("@Bean", "@> 'uppercase' (@Bean) Function") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("@Configuration", "@+ 'functionClass' (@Configuration <: @Component) FunctionClass"), + SpringIndexerHarness.symbol("@Bean", "@> 'uppercase' (@Bean) Function") ); } @Test public void testScanSimpleFunctionClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/ScannedFunctionClass.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("ScannedFunctionClass", "@> 'scannedFunctionClass' Function") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("ScannedFunctionClass", "@> 'scannedFunctionClass' Function") ); } @Test public void testScanSpecializedFunctionClass() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/FunctionFromSpecializedClass.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("FunctionFromSpecializedClass", "@> 'functionFromSpecializedClass' Function") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("FunctionFromSpecializedClass", "@> 'functionFromSpecializedClass' Function") ); } @Test public void testScanSpecializedFunctionInterface() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/FunctionFromSpecializedInterface.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri, - symbol("FunctionFromSpecializedInterface", "@> 'functionFromSpecializedInterface' Function") + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri, + SpringIndexerHarness.symbol("FunctionFromSpecializedInterface", "@> 'functionFromSpecializedInterface' Function") ); } @Test public void testNoSymbolForAbstractClasses() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/SpecializedFunctionClass.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); } @Test public void testNoSymbolForSubInterfaces() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/SpecializedFunctionInterface.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); } @Test public void testScanInconsistentInterfaceHierarchy() throws Exception { - SpringIndexerHarness indexer = createIndexerHarness(); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(indexer.wsFolder(directory)); - String docUri = directory.toPath().resolve("src/main/java/org/test/LoopedFunctionClass.java").toUri().toString(); - indexer.assertDocumentSymbols(docUri); - } - - //////////////////////////////// - // harness code - - private TestSymbolInfo symbol(String coveredText, String label) { - return new TestSymbolInfo(coveredText, label); - } - - private SpringIndexerHarness createIndexerHarness() { - return new SpringIndexerHarness(harness.getServer(), harness.getServerParams(), symbolProviders); + SpringIndexerHarness.assertDocumentSymbols(indexer, docUri); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java index 0050787b4..ec3d850cc 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java @@ -12,11 +12,9 @@ package org.springframework.ide.vscode.boot.java.beans.test; import static org.junit.Assert.assertEquals; -import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -24,12 +22,7 @@ import java.util.List; import org.apache.commons.io.IOUtils; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolInformation; -import org.eclipse.lsp4j.WorkspaceFolder; -import org.springframework.ide.vscode.boot.BootLanguageServerParams; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; -import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -104,18 +97,16 @@ public class SpringIndexerHarness { }; - private SpringIndexer indexer; - - public SpringIndexerHarness(SimpleLanguageServer server, BootLanguageServerParams params, AnnotationHierarchyAwareLookup symbolProviders) { - this.indexer = new SpringIndexer(server, params, symbolProviders); + public static TestSymbolInfo symbol(String coveredText, String label) { + return new TestSymbolInfo(coveredText, label); } - public void assertDocumentSymbols(String documentUri, TestSymbolInfo... expectedSymbols) throws Exception { - List actualSymbols = getSymbolsInFile(documentUri); + public static void assertDocumentSymbols(SpringIndexer indexer, String documentUri, TestSymbolInfo... expectedSymbols) throws Exception { + List actualSymbols = getSymbolsInFile(indexer, documentUri); assertEquals(symbolsString(Arrays.asList(expectedSymbols)), symbolsString(actualSymbols)); } - private String symbolsString(List symbols) { + private static String symbolsString(List symbols) { StringBuilder buf = new StringBuilder(); for (TestSymbolInfo s : symbols) { buf.append(s+"\n"); @@ -123,7 +114,7 @@ public class SpringIndexerHarness { return buf.toString(); } - public List getSymbolsInFile(String docURI) throws Exception { + public static List getSymbolsInFile(SpringIndexer indexer, String docURI) throws Exception { List symbols = indexer.getSymbols(docURI); if (symbols!=null) { symbols = new ArrayList<>(symbols); @@ -140,17 +131,4 @@ public class SpringIndexerHarness { return ImmutableList.of(); } - public Collection wsFolder(File directory) { - if (directory != null) { - WorkspaceFolder folder = new WorkspaceFolder(); - folder.setName(directory.getName()); - folder.setUri(directory.toURI().toString()); - return ImmutableList.of(folder); - } - return ImmutableList.of(); - } - - public void initialize(Collection wsRoots) { - indexer.initialize(wsRoots); - } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/CompilationUnitCacheTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/CompilationUnitCacheTest.java index 786c75a65..f185fdfee 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/CompilationUnitCacheTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/CompilationUnitCacheTest.java @@ -57,6 +57,10 @@ public class CompilationUnitCacheTest { public IClasspath getClasspath() { return new DelegatingCachedClasspath<>(() -> null, null); } + @Override + public String getLocationUri() { + return null; + } }); harness.intialize(null); @@ -99,6 +103,10 @@ public class CompilationUnitCacheTest { public IClasspath getClasspath() { return new DelegatingCachedClasspath<>(() -> null, null); } + @Override + public String getLocationUri() { + return null; + } }); harness.intialize(null); @@ -130,6 +138,10 @@ public class CompilationUnitCacheTest { public IClasspath getClasspath() { return new DelegatingCachedClasspath<>(() -> null, null); } + @Override + public String getLocationUri() { + return null; + } }); harness.intialize(null); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java index 264b1c2d0..0a2a54c23 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerTest.java @@ -17,26 +17,19 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.net.URI; -import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.TextDocumentIdentifier; import org.junit.Before; import org.junit.Test; -import org.springframework.ide.vscode.boot.java.Annotations; -import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; -import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; -import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; -import org.springframework.ide.vscode.commons.languageserver.composable.ComposableLanguageServer; -import org.springframework.ide.vscode.commons.maven.MavenCore; +import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.Assert; -import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; import org.springframework.ide.vscode.project.harness.BootJavaLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -45,28 +38,30 @@ import org.springframework.ide.vscode.project.harness.ProjectsHarness; */ public class SpringIndexerTest { - private Map symbolProviders; - private LanguageServerHarness> harness; - - private SpringIndexer indexer() { - return harness.getServerWrapper().getComponents().getSpringIndexer(); - } - + private BootJavaLanguageServerHarness harness; + private File directory; + private SpringIndexer indexer; + private String projectDir; + private IJavaProject project; @Before public void setup() throws Exception { - symbolProviders = new HashMap<>(); - symbolProviders.put(Annotations.SPRING_REQUEST_MAPPING, new RequestMappingSymbolProvider()); harness = BootJavaLanguageServerHarness.builder().build(); + + harness.intialize(null); + indexer = harness.getServerWrapper().getComponents().getSpringIndexer(); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); + projectDir = directory.toURI().toString(); + project = harness.getServerWrapper().getComponents().getProjectFinder().find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.initializeProject(project); + initProject.get(5, TimeUnit.SECONDS); } @Test public void testScanningAllAnnotationsSimpleProjectUpfront() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - - List allSymbols = indexer().getAllSymbols(""); + List allSymbols = indexer.getAllSymbols(""); assertEquals(6, allSymbols.size()); @@ -85,36 +80,28 @@ public class SpringIndexerTest { @Test public void testRetrievingSymbolsPerDocument() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); - List symbols = indexer().getSymbols(docUri); + List symbols = indexer.getSymbols(docUri); assertEquals(3, symbols.size()); assertTrue(containsSymbol(symbols, "@+ 'mainClass' (@SpringBootApplication <: @SpringBootConfiguration, @Configuration, @Component) MainClass", docUri, 6, 0, 6, 22)); assertTrue(containsSymbol(symbols, "@/embedded-foo-mapping", docUri, 17, 1, 17, 41)); assertTrue(containsSymbol(symbols, "@/foo-root-mapping/embedded-foo-mapping-with-root", docUri, 27, 1, 27, 51)); docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); - symbols = indexer().getSymbols(docUri); + symbols = indexer.getSymbols(docUri); assertEquals(2, symbols.size()); assertTrue(containsSymbol(symbols, "@/mapping1", docUri, 6, 1, 6, 28)); assertTrue(containsSymbol(symbols, "@/mapping2", docUri, 11, 1, 11, 28)); docUri = directory.toPath().resolve("src/main/java/org/test/sub/MappingClassSubpackage.java").toUri().toString(); - symbols = indexer().getSymbols(docUri); + symbols = indexer.getSymbols(docUri); assertEquals(1, symbols.size()); assertTrue(containsSymbol(symbols, "@/classlevel/mapping-subpackage", docUri, 7, 1, 7, 38)); } @Test public void testScanningAllAnnotationsMultiModuleProjectUpfront() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing").toURI()); - - List allSymbols = indexer().getAllSymbols(""); + List allSymbols = indexer.getAllSymbols(""); assertEquals(6, allSymbols.size()); @@ -133,27 +120,23 @@ public class SpringIndexerTest { @Test public void testUpdateChangedDocument() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - // update document and update index String changedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); - assertTrue(containsSymbol(indexer().getSymbols(changedDocURI), "@/mapping1", changedDocURI)); + assertTrue(containsSymbol(indexer.getSymbols(changedDocURI), "@/mapping1", changedDocURI)); String newContent = FileUtils.readFileToString(new File(new URI(changedDocURI))).replace("mapping1", "mapping1-CHANGED"); - CompletableFuture updateFuture = indexer().updateDocument(changedDocURI, newContent); + CompletableFuture updateFuture = indexer.updateDocument(changedDocURI, newContent); updateFuture.get(5, TimeUnit.SECONDS); // check for updated index per document - List symbols = indexer().getSymbols(changedDocURI); + List symbols = indexer.getSymbols(changedDocURI); assertEquals(2, symbols.size()); assertTrue(containsSymbol(symbols, "@/mapping1-CHANGED", changedDocURI, 6, 1, 6, 36)); assertTrue(containsSymbol(symbols, "@/mapping2", changedDocURI, 11, 1, 11, 28)); // check for updated index in all symbols - List allSymbols = indexer().getAllSymbols(""); + List allSymbols = indexer.getAllSymbols(""); assertEquals(6, allSymbols.size()); String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); @@ -169,20 +152,15 @@ public class SpringIndexerTest { assertTrue(containsSymbol(allSymbols, "@/classlevel/mapping-subpackage", docUri, 7, 1, 7, 38)); } - - @Test public void testNewDocumentCreated() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - String createdDocURI = directory.toPath().resolve("src/main/java/org/test/CreatedClass.java").toUri().toString(); // check for document to not be created yet - List symbols = indexer().getSymbols(createdDocURI); + List symbols = indexer.getSymbols(createdDocURI); assertNull(symbols); - List allSymbols = indexer().getAllSymbols(""); + List allSymbols = indexer.getAllSymbols(""); assertEquals(6, allSymbols.size()); try { @@ -206,17 +184,17 @@ public class SpringIndexerTest { "}\n" + ""; FileUtils.write(new File(new URI(createdDocURI)), content); - CompletableFuture createFuture = indexer().createDocument(createdDocURI); + CompletableFuture createFuture = indexer.createDocument(createdDocURI); createFuture.get(5, TimeUnit.SECONDS); // check for updated index per document - symbols = indexer().getSymbols(createdDocURI); + symbols = indexer.getSymbols(createdDocURI); assertEquals(2, symbols.size()); assertTrue(containsSymbol(symbols, "@/created-mapping1", createdDocURI, 6, 1, 6, 36)); assertTrue(containsSymbol(symbols, "@/created-mapping2", createdDocURI, 11, 1, 11, 36)); // check for updated index in all symbols - allSymbols = indexer().getAllSymbols(""); + allSymbols = indexer.getAllSymbols(""); assertEquals(8, allSymbols.size()); String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); @@ -241,21 +219,18 @@ public class SpringIndexerTest { @Test public void testRemoveSymbolsFromDeletedDocument() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - // update document and update index String deletedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); - assertFalse(indexer().getSymbols(deletedDocURI).isEmpty()); //We have symbols before deletion? - CompletableFuture deleteFuture = indexer().deleteDocument(deletedDocURI); + assertFalse(indexer.getSymbols(deletedDocURI).isEmpty()); //We have symbols before deletion? + CompletableFuture deleteFuture = indexer.deleteDocument(deletedDocURI); deleteFuture.get(5, TimeUnit.HOURS); // check for updated index per document - Assert.noElements(indexer().getSymbols(deletedDocURI)); + Assert.noElements(indexer.getSymbols(deletedDocURI)); // check for updated index in all symbols - List allSymbols = indexer().getAllSymbols(""); + List allSymbols = indexer.getAllSymbols(""); assertEquals(4, allSymbols.size()); String docUri = directory.toPath().resolve("src/main/java/org/test/MainClass.java").toUri().toString(); @@ -269,11 +244,7 @@ public class SpringIndexerTest { @Test public void testFilterSymbolsUsingQueryString() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - - List allSymbols = indexer().getAllSymbols("mapp"); + List allSymbols = indexer.getAllSymbols("mapp"); assertEquals(6, allSymbols.size()); @@ -291,11 +262,7 @@ public class SpringIndexerTest { @Test public void testFilterSymbolsUsingQueryStringSplittedResult() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - - List allSymbols = indexer().getAllSymbols("@/foo-root-mapping"); + List allSymbols = indexer.getAllSymbols("@/foo-root-mapping"); assertEquals(1, allSymbols.size()); @@ -306,11 +273,7 @@ public class SpringIndexerTest { @Test public void testFilterSymbolsUsingQueryStringFullSymbolString() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - - List allSymbols = indexer().getAllSymbols("@/foo-root-mapping/embedded-foo-mapping-with-root"); + List allSymbols = indexer.getAllSymbols("@/foo-root-mapping/embedded-foo-mapping-with-root"); assertEquals(1, allSymbols.size()); @@ -350,33 +313,16 @@ public class SpringIndexerTest { return false; } - + @Test - public void testRefreshOnProjectChange() throws Exception { - harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); - - File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI()); - - List allSymbols = indexer().getAllSymbols(""); + public void testDeleteProject() throws Exception { + List allSymbols = indexer.getAllSymbols(""); assertEquals(6, allSymbols.size()); - // Delete some symbols - String deletedDocURI = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); - CompletableFuture deleteFuture = indexer().deleteDocument(deletedDocURI); - deleteFuture.get(5, TimeUnit.SECONDS); - // check for updated index in all symbols - allSymbols = indexer().getAllSymbols(""); - assertEquals(4, allSymbols.size()); + CompletableFuture deleteProject = indexer.deleteProject(project); + deleteProject.get(5, TimeUnit.SECONDS); - - File pomFile = directory.toPath().resolve(MavenCore.POM_XML).toFile(); - assertFalse(indexer().isInitializing()); - harness.changeFile(pomFile.toURI().toString()); - - // Everything is expected to be re-indexed hence "fake" deleted document should be indexed now - allSymbols = indexer().getAllSymbols(""); - assertFalse(indexer().isInitializing()); - assertEquals(6, allSymbols.size()); + assertEquals(0, allSymbols.size()); } } diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 6f1c9a318..0555fca37 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -17,8 +17,8 @@ const JAVA_LANGUAGE_ID = "java"; /** Called when extension is activated */ export function activate(context: VSCode.ExtensionContext) { let options : commons.ActivatorOptions = { - DEBUG: false, - CONNECT_TO_LS: false, + DEBUG: true, + CONNECT_TO_LS: true, extensionId: 'vscode-spring-boot', preferJdk: true, checkjvm: (context: VSCode.ExtensionContext, jvm: commons.JVM) => {