diff --git a/headless-services/spring-boot-language-server/.classpath b/headless-services/spring-boot-language-server/.classpath index 5353ca082..5a94da1a3 100644 --- a/headless-services/spring-boot-language-server/.classpath +++ b/headless-services/spring-boot-language-server/.classpath @@ -37,17 +37,11 @@ - - - - - - diff --git a/headless-services/spring-boot-language-server/pom.xml b/headless-services/spring-boot-language-server/pom.xml index 255090457..0b435ca17 100644 --- a/headless-services/spring-boot-language-server/pom.xml +++ b/headless-services/spring-boot-language-server/pom.xml @@ -78,13 +78,18 @@ commons-io ${commons-io-version} - + + - - org.springframework.boot - spring-boot-starter-test - test - + + org.springframework.boot + spring-boot-starter-test + test + org.springframework.ide.vscode language-server-test-harness 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 d9da12f22..9d427f28a 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2018 Pivotal, Inc. + * Copyright (c) 2016, 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -58,7 +58,7 @@ 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.RestrictedDefaultSymbolProvider; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.boot.java.utils.SpringLiveChangeDetectionWatchdog; import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog; import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor; @@ -97,7 +97,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent private final SimpleLanguageServer server; private final BootLanguageServerParams serverParams; - private final SpringIndexer indexer; + private final SpringSymbolIndex indexer; private final SpringPropertyIndexProvider propertyIndexProvider; private final ProjectBasedPropertyIndexProvider adHocPropertyIndexProvider; private final SpringLiveHoverWatchdog liveHoverWatchdog; @@ -373,7 +373,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent return new BootJavaHoverProvider(this, javaProjectFinder, providers, runningAppProvider); } - protected SpringIndexer createAnnotationIndexer(SimpleLanguageServer server, BootLanguageServerParams params) { + protected SpringSymbolIndex createAnnotationIndexer(SimpleLanguageServer server, BootLanguageServerParams params) { AnnotationHierarchyAwareLookup providers = new AnnotationHierarchyAwareLookup<>(); RequestMappingSymbolProvider requestMappingSymbolProvider = new RequestMappingSymbolProvider(); BeansSymbolProvider beansSymbolProvider = new BeansSymbolProvider(); @@ -416,7 +416,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent providers.put(Annotations.REPOSITORY, dataRepositorySymbolProvider); providers.put("", webfluxRouterSymbolProvider); - return new SpringIndexer(server, params, providers); + return new SpringSymbolIndex(server, params, providers); } protected ReferencesHandler createReferenceHandler(SimpleLanguageServer server, JavaProjectFinder projectFinder) { @@ -449,7 +449,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent return projectFinder; } - public SpringIndexer getSpringIndexer() { + public SpringSymbolIndex getSpringSymbolIndex() { return indexer; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentSymbolHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentSymbolHandler.java index b92ca2b6c..738e4eec6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentSymbolHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaDocumentSymbolHandler.java @@ -14,7 +14,7 @@ import java.util.List; import org.eclipse.lsp4j.DocumentSymbolParams; import org.eclipse.lsp4j.SymbolInformation; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbolHandler; /** @@ -22,9 +22,9 @@ import org.springframework.ide.vscode.commons.languageserver.util.DocumentSymbol */ public class BootJavaDocumentSymbolHandler implements DocumentSymbolHandler { - private SpringIndexer indexer; + private SpringSymbolIndex indexer; - public BootJavaDocumentSymbolHandler(SpringIndexer indexer) { + public BootJavaDocumentSymbolHandler(SpringSymbolIndex indexer) { this.indexer = indexer; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaWorkspaceSymbolHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaWorkspaceSymbolHandler.java index 90236d9e4..1ebe4ec80 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaWorkspaceSymbolHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaWorkspaceSymbolHandler.java @@ -15,7 +15,7 @@ import java.util.List; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.WorkspaceSymbolParams; import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.util.WorkspaceSymbolHandler; /** @@ -23,10 +23,10 @@ import org.springframework.ide.vscode.commons.languageserver.util.WorkspaceSymbo */ public class BootJavaWorkspaceSymbolHandler implements WorkspaceSymbolHandler { - private final SpringIndexer indexer; + private final SpringSymbolIndex indexer; private final LiveAppURLSymbolProvider liveAppSymbolProvider; - public BootJavaWorkspaceSymbolHandler(SpringIndexer indexer, LiveAppURLSymbolProvider liveAppSymbolProvider) { + public BootJavaWorkspaceSymbolHandler(SpringSymbolIndex indexer, LiveAppURLSymbolProvider liveAppSymbolProvider) { this.indexer = indexer; this.liveAppSymbolProvider = liveAppSymbolProvider; } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java index 6fabd7b5b..9b51cd4da 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -21,7 +21,7 @@ import org.eclipse.lsp4j.Command; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider; import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -30,10 +30,10 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument; */ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { - private final SpringIndexer springIndexer; + private final SpringSymbolIndex springIndexer; public WebfluxHandlerCodeLensProvider(BootJavaLanguageServerComponents bootJavaLanguageServerComponents) { - this.springIndexer = bootJavaLanguageServerComponents.getSpringIndexer(); + this.springIndexer = bootJavaLanguageServerComponents.getSpringSymbolIndex(); } @Override @@ -49,13 +49,13 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { protected void provideCodeLens(MethodDeclaration node, TextDocument document, List resultAccumulator) { IMethodBinding methodBinding = node.resolveBinding(); - + if (methodBinding != null && methodBinding.getDeclaringClass() != null && methodBinding.getMethodDeclaration() != null && methodBinding.getDeclaringClass().getBinaryName() != null && methodBinding.getMethodDeclaration().toString() != null) { - + final String handlerClass = methodBinding.getDeclaringClass().getBinaryName().trim(); final String handlerMethod = methodBinding.getMethodDeclaration().toString().trim(); - + List handlerInfos = this.springIndexer.getAllAdditionalInformation((addon) -> { if (addon instanceof WebfluxHandlerInformation) { WebfluxHandlerInformation handlerInfo = (WebfluxHandlerInformation) addon; @@ -64,15 +64,15 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { } return false; }); - + if (handlerInfos != null && handlerInfos.size() > 0) { for (Object object : handlerInfos) { try { WebfluxHandlerInformation handlerInfo = (WebfluxHandlerInformation) object; - + CodeLens codeLens = new CodeLens(); codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength())); - + String httpMethod = WebfluxUtils.getStringRep(handlerInfo.getHttpMethods(), string -> string); String codeLensCommand = httpMethod != null ? httpMethod + " " : ""; @@ -80,12 +80,12 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { String acceptType = WebfluxUtils.getStringRep(handlerInfo.getAcceptTypes(), WebfluxUtils::getMediaType); codeLensCommand += acceptType != null ? " - Accept: " + acceptType : ""; - + String contentType = WebfluxUtils.getStringRep(handlerInfo.getContentTypes(), WebfluxUtils::getMediaType); codeLensCommand += contentType != null ? " - Content-Type: " + contentType : ""; codeLens.setCommand(new Command(codeLensCommand, null)); - + resultAccumulator.add(codeLens); } catch (BadLocationException e) { e.printStackTrace(); @@ -94,5 +94,5 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { } } } - + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java index 55b052b97..aa91a4438 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouteHighlightProdivder.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -19,32 +19,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.util.text.TextDocument; /** * @author Martin Lippert */ public class WebfluxRouteHighlightProdivder implements HighlightProvider { - + private static final Logger log = LoggerFactory.getLogger(WebfluxRouteHighlightProdivder.class); - private final SpringIndexer springIndexer; + private final SpringSymbolIndex springIndexer; public WebfluxRouteHighlightProdivder(BootJavaLanguageServerComponents bootJavaLanguageServerComponents) { - this.springIndexer = bootJavaLanguageServerComponents.getSpringIndexer(); + this.springIndexer = bootJavaLanguageServerComponents.getSpringSymbolIndex(); } @Override public void provideHighlights(TextDocument document, Position position, List resultAccumulator) { log.info("PROVIDE HIGHLIGHTS: {} / {}", position.getLine(), position.getCharacter()); - + this.springIndexer.getAdditonalInformation(document.getUri()) .stream() .filter(addon -> { if (addon instanceof WebfluxElementsInformation) { WebfluxElementsInformation handlerInfo = (WebfluxElementsInformation) addon; - + if (handlerInfo.contains(position)) { return true; } 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 56bc299dd..7ff295074 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2018 Pivotal, Inc. + * Copyright (c) 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -10,718 +10,18 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.utils; -import java.io.File; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -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.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -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 org.apache.commons.io.FileUtils; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ASTVisitor; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FileASTRequestor; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.MarkerAnnotation; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.NormalAnnotation; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.lsp4j.SymbolInformation; -import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; -import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; -import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; -import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; -import org.springframework.ide.vscode.commons.java.IClasspath; -import org.springframework.ide.vscode.commons.java.IClasspathUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; -import org.springframework.ide.vscode.commons.java.SpringProjectUtil; -import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; -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.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 */ -public class SpringIndexer { +public interface SpringIndexer { - private final SimpleLanguageServer server; - private final BootLanguageServerParams params; - private final JavaProjectFinder projectFinder; - private final AnnotationHierarchyAwareLookup symbolProviders; + String[] getFileWatchPatterns(); + boolean isInterestedIn(String docURI); - private final List symbols; - private final List addonInformation; + void initializeProject(IJavaProject project) throws Exception; + void updateFile(IJavaProject project, String docURI, String content) throws Exception; - private final ConcurrentMap> symbolsByDoc; - private final ConcurrentMap> addonInformationByDoc; - - 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()); - initializeProject(project); - } - - @Override - public void changed(IJavaProject project) { - log.debug("project changed event: {}", project.getElementName()); - initializeProject(project); - } - - @Override - public void deleted(IJavaProject project) { - log.debug("project deleted event: {}", project.getElementName()); - deleteProject(project); - } - }; - - 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); - this.server = server; - this.params = params; - this.projectFinder = params.projectFinder; - this.symbolProviders = specificProviders; - - 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 = Executors.newSingleThreadExecutor(); - - getWorkspaceService().onDidChangeWorkspaceFolders(evt -> { - log.debug("workspace roots have changed event arrived - added: " + evt.getEvent().getAdded() + " - removed: " + evt.getEvent().getRemoved()); - }); - - if (getProjectObserver() != null) { - getProjectObserver().addListener(projectListener); - } - } - - public void serverInitialized() { - List globPattern = Arrays.asList("**/*.java"); - getWorkspaceService().getFileObserver().onFileDeleted(globPattern, (file) -> { - deleteDocument(new TextDocumentIdentifier(file).getUri()); - }); - getWorkspaceService().getFileObserver().onFileCreated(globPattern, (file) -> { - createDocument(new TextDocumentIdentifier(file).getUri()); - }); - } - - public void shutdown() { - try { - synchronized(this) { - if (updateQueue != null && !updateQueue.isShutdown()) { - updateQueue.shutdownNow(); - } - - if (getProjectObserver() != null) { - getProjectObserver().removeListener(projectListener); - } - } - } catch (Exception e) { - log.error("{}", e); - } - } - - public CompletableFuture initializeProject(IJavaProject project) { - try { - if (SpringProjectUtil.isBootProject(project) || SpringProjectUtil.isSpringProject(project)) { - if (project.getElementName() == null) { - // Projects indexed by name. No name - no index for it - log.debug("Project with NULL name is being initialized"); - return CompletableFuture.completedFuture(null); - } else { - InitializeProject initializeItem = new InitializeProject(project); - return CompletableFuture.runAsync(initializeItem, this.updateQueue); - } - } else { - return deleteProject(project); - } - } catch (Throwable e) { - log.error("", e); - return Futures.error(e); - } - } - - public CompletableFuture deleteProject(IJavaProject project) { - try { - if (project.getElementName() == null) { - // Projects indexed by name. No name - no index for it - log.debug("Project with NULL name is being removed"); - return CompletableFuture.completedFuture(null); - } else { - 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")) { - try { - Optional maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI)); - if (maybeProject.isPresent()) { - String[] classpathEntries = getClasspathEntries(maybeProject.get()); - - UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, content, classpathEntries); - return CompletableFuture.runAsync(updateItem, this.updateQueue); - } - } - catch (Exception e) { - log.error("{}", e); - } - } - } - - return null; - } - - public CompletableFuture deleteDocument(String deletedDocURI) { - synchronized(this) { - try { - 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")) { - 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(maybeProject.get(), docURI, content, classpathEntries); - return CompletableFuture.runAsync(updateItem, this.updateQueue); - } - } - catch (Exception e) { - log.error("", e); - return Futures.error(e); - } - } - } - return CompletableFuture.completedFuture(null); - } - - public List getAllSymbols(String query) { - if (query != null && query.length() > 0) { - List foundSymbols = searchMatchingSymbols(this.symbols, query); - return foundSymbols.subList(0, Math.min(50, foundSymbols.size())); - } else { - return this.symbols.subList(0, Math.min(50, this.symbols.size())); - } - } - - public List getSymbols(String docURI) { - return this.symbolsByDoc.get(docURI); - } - - public List getAllAdditionalInformation(Predicate filter) { - if (filter != null) { - return addonInformation.stream().filter(filter).collect(Collectors.toList()); - } - else { - return null; - } - } - - public List getAdditonalInformation(String docURI) { - List info = this.addonInformationByDoc.get(docURI); - return info == null ? ImmutableList.of() : info; - } - - /** - * inserts a noop operation into the worker/update quene, which allows invokers to use the - * returned future to wait for the queue items in the queue to be completed which got inserted before - * this noop. - */ - public CompletableFuture waitOperation() { - return CompletableFuture.runAsync(new Runnable() { - @Override - public void run() { - } - }, this.updateQueue); - } - - private List searchMatchingSymbols(List allsymbols, String query) { - return allsymbols.stream() - .filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query)) - .collect(Collectors.toList()); - } - - private void scanProject(IJavaProject project, String[] files) { - try { - ASTParser parser = ASTParser.newParser(AST.JLS11); - String[] classpathEntries = getClasspathEntries(project); - - scanFiles(project, parser, files, classpathEntries); - } - catch (Exception e) { - log.error("error parsing all Java source files from project: " + project.getElementName(), e); - } - } - - private void scanFile(IJavaProject project, String docURI, String content, String[] classpathEntries) throws Exception { - ASTParser parser = ASTParser.newParser(AST.JLS11); - Map options = JavaCore.getOptions(); - JavaCore.setComplianceOptions(JavaCore.VERSION_10, options); - parser.setCompilerOptions(options); - parser.setKind(ASTParser.K_COMPILATION_UNIT); - parser.setStatementsRecovery(true); - parser.setBindingsRecovery(true); - parser.setResolveBindings(true); - parser.setIgnoreMethodBodies(false); - - String[] sourceEntries = new String[] {}; - parser.setEnvironment(classpathEntries, sourceEntries, null, false); - - String unitName = docURI.substring(docURI.lastIndexOf("/")); - parser.setUnitName(unitName); - parser.setSource(content.toCharArray()); - - CompilationUnit cu = (CompilationUnit) parser.createAST(null); - - if (cu != null) { - AtomicReference docRef = new AtomicReference<>(); - scanAST(project, cu, docURI, docRef, content); - } - } - - private void scanFiles(IJavaProject project, ASTParser parser, String[] javaFiles, String[] classpathEntries) throws Exception { - - Map options = JavaCore.getOptions(); - JavaCore.setComplianceOptions(JavaCore.VERSION_10, options); - parser.setCompilerOptions(options); - parser.setKind(ASTParser.K_COMPILATION_UNIT); - parser.setStatementsRecovery(true); - parser.setBindingsRecovery(true); - parser.setResolveBindings(true); - parser.setIgnoreMethodBodies(false); - - String[] sourceEntries = new String[] {}; - parser.setEnvironment(classpathEntries, sourceEntries, null, false); - - FileASTRequestor requestor = new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit cu) { - String docURI = UriUtil.toUri(new File(sourceFilePath)).toString(); - AtomicReference docRef = new AtomicReference<>(); - scanAST(project, cu, docURI, docRef, null); - } - }; - - parser.createASTs(javaFiles, null, new String[0], requestor, null); - } - - 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(project, node, docURI, docRef, content); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); - } - return super.visit(node); - } - - @Override - public boolean visit(MethodDeclaration node) { - try { - extractSymbolInformation(project, node, docURI, docRef, content); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); - } - return super.visit(node); - } - - @Override - public boolean visit(SingleMemberAnnotation node) { - try { - extractSymbolInformation(project, node, docURI, docRef, content); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); - } - - return super.visit(node); - } - - @Override - public boolean visit(NormalAnnotation node) { - try { - extractSymbolInformation(project, node, docURI, docRef, content); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); - } - - return super.visit(node); - } - - @Override - public boolean visit(MarkerAnnotation node) { - try { - extractSymbolInformation(project, node, docURI, docRef, content); - } - catch (Exception e) { - log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); - } - - return super.visit(node); - } - }); - } - - 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); - for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(typeDeclaration, doc); - if (sbls != null) { - sbls.forEach(enhancedSymbol -> { - addSymbol(project, docURI, enhancedSymbol); - }); - } - } - } - } - - 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); - for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(methodDeclaration, doc); - if (sbls != null) { - sbls.forEach(enhancedSymbol -> { - addSymbol(project, docURI, enhancedSymbol); - }); - } - } - } - } - - private void extractSymbolInformation(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content) throws Exception { - ITypeBinding typeBinding = node.resolveTypeBinding(); - - if (typeBinding != null) { - Collection providers = symbolProviders.get(typeBinding); - Collection metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey); - if (!providers.isEmpty()) { - TextDocument doc = getTempTextDocument(docURI, docRef, content); - for (SymbolProvider provider : providers) { - Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); - if (sbls != null) { - sbls.forEach(enhancedSymbol -> { - addSymbol(project, docURI, enhancedSymbol); - }); - } - } - } else { - SymbolInformation symbol = provideDefaultSymbol(project, node, docURI, docRef, content); - if (symbol != null) { - addSymbol(project, docURI, new EnhancedSymbolInformation(symbol, null)); - } - } - } - } - - private TextDocument getTempTextDocument(String docURI, AtomicReference docRef, String content) throws Exception { - TextDocument doc = docRef.get(); - if (doc == null) { - doc = createTempTextDocument(docURI, content); - docRef.set(doc); - } - return doc; - } - - private TextDocument createTempTextDocument(String docURI, String content) throws Exception { - if (content == null) { - Path path = Paths.get(new URI(docURI)); - content = new String(Files.readAllBytes(path)); - } - - TextDocument doc = new TextDocument(docURI, LanguageId.PLAINTEXT, 0, content); - return doc; - } - - private SymbolInformation provideDefaultSymbol(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content) { - try { - ITypeBinding type = node.resolveTypeBinding(); - if (type != null) { - String qualifiedName = type.getQualifiedName(); - if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { - TextDocument doc = getTempTextDocument(docURI, docRef, content); - return DefaultSymbolProvider.provideDefaultSymbol(node, doc); - } - } - } - catch (Exception e) { - log.error("error creating default symbol in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); - } - - return null; - } - - private String[] getClasspathEntries(IJavaProject project) throws Exception { - IClasspath classpath = project.getClasspath(); - Stream classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream(); - return classpathEntries - .filter(file -> file.exists()) - .map(file -> file.getAbsolutePath()) - .toArray(String[]::new); - } - - - private class InitializeProject implements Runnable { - - private final IJavaProject project; - - public InitializeProject(IJavaProject project) { - this.project = project; - log.debug("{} created ", this); - } - - @Override - public void run() { - log.debug("{} starting...", this); - try { - removeSymbolsByProject(project); - - URI projectUri = project.getLocationUri(); - List files = Files.walk(Paths.get(projectUri)) - .filter(path -> path.getFileName().toString().endsWith(".java")) - .filter(Files::isRegularFile) - .map(path -> path.toAbsolutePath().toString()) - .collect(Collectors.toList()); - - SpringIndexer.this.scanProject(project, (String[]) files.toArray(new String[files.size()])); - - log.debug("{} completed", this); - } catch (Throwable e) { - log.error("{} threw exception", this, e); - } - } - - } - - 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; - - public UpdateItem(IJavaProject project, String docURI, String content, String[] classpathEntries) { - this.project = project; - this.docURI = docURI; - this.content = content; - this.classpathEntries = classpathEntries; - } - - @Override - public void run() { - try { - removeSymbolsByDoc(project, docURI); - SpringIndexer.this.scanFile(project, docURI, content, classpathEntries); - } catch (Exception e) { - log.error("{}", e); - } - } - } - - private class DeleteItem implements Runnable { - - private final String docURI; - private IJavaProject project; - - public DeleteItem(IJavaProject project, String docURI) { - this.project = project; - this.docURI = docURI; - } - - @Override - public void run() { - try { - removeSymbolsByDoc(project, docURI); - } catch (Exception e) { - log.error("{}", e); - } - } - } - - 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) { - // If project name is null it cannot be in the cache - if (project.getElementName() == null) { - return; - } - 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/java/utils/SpringIndexerJava.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java new file mode 100644 index 000000000..8a0d76076 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerJava.java @@ -0,0 +1,323 @@ +/******************************************************************************* + * Copyright (c) 2017, 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.io.File; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTParser; +import org.eclipse.jdt.core.dom.ASTVisitor; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.CompilationUnit; +import org.eclipse.jdt.core.dom.FileASTRequestor; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MarkerAnnotation; +import org.eclipse.jdt.core.dom.MethodDeclaration; +import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.SymbolInformation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +import org.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.java.IClasspathUtil; +import org.springframework.ide.vscode.commons.java.IJavaProject; +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; + +/** + * @author Martin Lippert + */ +public class SpringIndexerJava implements SpringIndexer { + + private static final Logger log = LoggerFactory.getLogger(SpringIndexerJava.class); + + private final SymbolHandler symbolHandler; + private final AnnotationHierarchyAwareLookup symbolProviders; + + public SpringIndexerJava(SymbolHandler symbolHandler, AnnotationHierarchyAwareLookup symbolProviders) { + this.symbolHandler = symbolHandler; + this.symbolProviders = symbolProviders; + } + + @Override + public String[] getFileWatchPatterns() { + return new String[] {"**/*.java"}; + } + + @Override + public boolean isInterestedIn(String docURI) { + return docURI.endsWith(".java"); + } + + @Override + public void initializeProject(IJavaProject project) throws Exception { + List files = Files.walk(Paths.get(project.getLocationUri())) + .filter(path -> path.getFileName().toString().endsWith(".java")) + .filter(Files::isRegularFile) + .map(path -> path.toAbsolutePath().toString()) + .collect(Collectors.toList()); + + scanProject(project, (String[]) files.toArray(new String[files.size()])); + } + + @Override + public void updateFile(IJavaProject project, String docURI, String content) throws Exception { + String[] classpathEntries = getClasspathEntries(project); + scanFile(project, docURI, content, classpathEntries); + } + + + private void scanProject(IJavaProject project, String[] files) { + try { + ASTParser parser = ASTParser.newParser(AST.JLS11); + String[] classpathEntries = getClasspathEntries(project); + + scanFiles(project, parser, files, classpathEntries); + } + catch (Exception e) { + log.error("error parsing all Java source files from project: " + project.getElementName(), e); + } + } + + private void scanFile(IJavaProject project, String docURI, String content, String[] classpathEntries) throws Exception { + ASTParser parser = ASTParser.newParser(AST.JLS11); + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_10, options); + parser.setCompilerOptions(options); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setResolveBindings(true); + parser.setIgnoreMethodBodies(false); + + String[] sourceEntries = new String[] {}; + parser.setEnvironment(classpathEntries, sourceEntries, null, false); + + String unitName = docURI.substring(docURI.lastIndexOf("/")); + parser.setUnitName(unitName); + parser.setSource(content.toCharArray()); + + CompilationUnit cu = (CompilationUnit) parser.createAST(null); + + if (cu != null) { + AtomicReference docRef = new AtomicReference<>(); + scanAST(project, cu, docURI, docRef, content); + } + } + + private void scanFiles(IJavaProject project, ASTParser parser, String[] javaFiles, String[] classpathEntries) throws Exception { + + Map options = JavaCore.getOptions(); + JavaCore.setComplianceOptions(JavaCore.VERSION_10, options); + parser.setCompilerOptions(options); + parser.setKind(ASTParser.K_COMPILATION_UNIT); + parser.setStatementsRecovery(true); + parser.setBindingsRecovery(true); + parser.setResolveBindings(true); + parser.setIgnoreMethodBodies(false); + + String[] sourceEntries = new String[] {}; + parser.setEnvironment(classpathEntries, sourceEntries, null, false); + + FileASTRequestor requestor = new FileASTRequestor() { + @Override + public void acceptAST(String sourceFilePath, CompilationUnit cu) { + String docURI = UriUtil.toUri(new File(sourceFilePath)).toString(); + AtomicReference docRef = new AtomicReference<>(); + scanAST(project, cu, docURI, docRef, null); + } + }; + + parser.createASTs(javaFiles, null, new String[0], requestor, null); + } + + 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(project, node, docURI, docRef, content); + } + catch (Exception e) { + log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + } + return super.visit(node); + } + + @Override + public boolean visit(MethodDeclaration node) { + try { + extractSymbolInformation(project, node, docURI, docRef, content); + } + catch (Exception e) { + log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + } + return super.visit(node); + } + + @Override + public boolean visit(SingleMemberAnnotation node) { + try { + extractSymbolInformation(project, node, docURI, docRef, content); + } + catch (Exception e) { + log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + } + + return super.visit(node); + } + + @Override + public boolean visit(NormalAnnotation node) { + try { + extractSymbolInformation(project, node, docURI, docRef, content); + } + catch (Exception e) { + log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + } + + return super.visit(node); + } + + @Override + public boolean visit(MarkerAnnotation node) { + try { + extractSymbolInformation(project, node, docURI, docRef, content); + } + catch (Exception e) { + log.error("error extracting symbol information in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + } + + return super.visit(node); + } + }); + } + + 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); + for (SymbolProvider provider : providers) { + Collection sbls = provider.getSymbols(typeDeclaration, doc); + if (sbls != null) { + sbls.forEach(enhancedSymbol -> { + symbolHandler.addSymbol(project, docURI, enhancedSymbol); + }); + } + } + } + } + + 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); + for (SymbolProvider provider : providers) { + Collection sbls = provider.getSymbols(methodDeclaration, doc); + if (sbls != null) { + sbls.forEach(enhancedSymbol -> { + symbolHandler.addSymbol(project, docURI, enhancedSymbol); + }); + } + } + } + } + + private void extractSymbolInformation(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content) throws Exception { + ITypeBinding typeBinding = node.resolveTypeBinding(); + + if (typeBinding != null) { + Collection providers = symbolProviders.get(typeBinding); + Collection metaAnnotations = AnnotationHierarchies.getMetaAnnotations(typeBinding, symbolProviders::containsKey); + if (!providers.isEmpty()) { + TextDocument doc = getTempTextDocument(docURI, docRef, content); + for (SymbolProvider provider : providers) { + Collection sbls = provider.getSymbols(node, typeBinding, metaAnnotations, doc); + if (sbls != null) { + sbls.forEach(enhancedSymbol -> { + symbolHandler.addSymbol(project, docURI, enhancedSymbol); + }); + } + } + } else { + SymbolInformation symbol = provideDefaultSymbol(project, node, docURI, docRef, content); + if (symbol != null) { + symbolHandler.addSymbol(project, docURI, new EnhancedSymbolInformation(symbol, null)); + } + } + } + } + + private SymbolInformation provideDefaultSymbol(IJavaProject project, Annotation node, String docURI, AtomicReference docRef, String content) { + try { + ITypeBinding type = node.resolveTypeBinding(); + if (type != null) { + String qualifiedName = type.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { + TextDocument doc = getTempTextDocument(docURI, docRef, content); + return DefaultSymbolProvider.provideDefaultSymbol(node, doc); + } + } + } + catch (Exception e) { + log.error("error creating default symbol in project '" + project.getElementName() + "' - for docURI '" + docURI + "' - on node: " + node.toString(), e); + } + + return null; + } + + private String[] getClasspathEntries(IJavaProject project) throws Exception { + IClasspath classpath = project.getClasspath(); + Stream classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream(); + return classpathEntries + .filter(file -> file.exists()) + .map(file -> file.getAbsolutePath()) + .toArray(String[]::new); + } + + private TextDocument getTempTextDocument(String docURI, AtomicReference docRef, String content) throws Exception { + TextDocument doc = docRef.get(); + if (doc == null) { + doc = createTempTextDocument(docURI, content); + docRef.set(doc); + } + return doc; + } + + private TextDocument createTempTextDocument(String docURI, String content) throws Exception { + if (content == null) { + Path path = Paths.get(new URI(docURI)); + content = new String(Files.readAllBytes(path)); + } + + TextDocument doc = new TextDocument(docURI, LanguageId.PLAINTEXT, 0, content); + return doc; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java new file mode 100644 index 000000000..a1b5d798c --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexerXML.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.io.FileInputStream; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +import javax.xml.stream.XMLEventReader; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.events.Characters; +import javax.xml.stream.events.EndElement; +import javax.xml.stream.events.StartElement; +import javax.xml.stream.events.XMLEvent; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.commons.java.IJavaProject; + +/** + * @author Martin Lippert + */ +public class SpringIndexerXML implements SpringIndexer { + + private static final Logger log = LoggerFactory.getLogger(SpringIndexerJava.class); + + private final SymbolHandler handler; + + public SpringIndexerXML(SymbolHandler handler) { + this.handler = handler; + } + + @Override + public String[] getFileWatchPatterns() { + return new String[] {"**/*.xml"}; + } + + @Override + public boolean isInterestedIn(String docURI) { + return docURI.endsWith(".xml"); + } + + @Override + public void initializeProject(IJavaProject project) throws Exception { + List files = Files.walk(Paths.get(project.getLocationUri())) + .filter(path -> path.getFileName().toString().endsWith(".xml")) + .filter(Files::isRegularFile) + .map(path -> path.toAbsolutePath().toString()) + .collect(Collectors.toList()); + + scanProject(project, (String[]) files.toArray(new String[files.size()])); + } + + @Override + public void updateFile(IJavaProject project, String docURI, String content) throws Exception { + } + + private void scanProject(IJavaProject project, String[] files) { + for (String file : files) { + scanFile(file); + } + } + + private void scanFile(String file) { + + System.out.println("XML parsing for: " + file); + + try { + InputStream xmlInputStream = new FileInputStream(file); + + XMLInputFactory inputFactory = XMLInputFactory.newInstance(); + XMLEventReader eventReader = inputFactory.createXMLEventReader(xmlInputStream); + + while(eventReader.hasNext()){ + XMLEvent event = eventReader.nextEvent(); + switch (event.getEventType()) { + case XMLEvent.START_ELEMENT: + StartElement startElement = event.asStartElement(); + + System.out.print("<"+startElement.getName().toString()+">"); + break; + case XMLEvent.CHARACTERS: + Characters characters = event.asCharacters(); + System.out.print(characters.getData()); + break; + case XMLEvent.END_ELEMENT: + EndElement endElement = event.asEndElement(); + System.out.println(""); + break; + default: + //do nothing + break; + } + } + } + catch (Exception e) { + log.error("error parsing XML file: ", e); + } + + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java new file mode 100644 index 000000000..b5a9f0e7a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringSymbolIndex.java @@ -0,0 +1,496 @@ +/******************************************************************************* + * Copyright (c) 2017, 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.apache.commons.io.FileUtils; +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; +import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; +import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.SpringProjectUtil; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +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.Futures; +import org.springframework.ide.vscode.commons.util.StringUtil; + +import com.google.common.collect.ImmutableList; + +/** + * @author Martin Lippert + */ +public class SpringSymbolIndex { + + private final SimpleLanguageServer server; + private final BootLanguageServerParams params; + private final JavaProjectFinder projectFinder; + + private final List symbols; + private final List addonInformation; + + private final ConcurrentMap> symbolsByDoc; + private final ConcurrentMap> addonInformationByDoc; + + private final ConcurrentMap> symbolsByProject; + private final ConcurrentMap> addonInformationByProject; + + private final ExecutorService updateQueue; + private final SpringIndexer[] indexer; + + private static final Logger log = LoggerFactory.getLogger(SpringSymbolIndex.class); + + private final Listener projectListener = new Listener() { + @Override + public void created(IJavaProject project) { + log.debug("project created event: {}", project.getElementName()); + initializeProject(project); + } + + @Override + public void changed(IJavaProject project) { + log.debug("project changed event: {}", project.getElementName()); + initializeProject(project); + } + + @Override + public void deleted(IJavaProject project) { + log.debug("project deleted event: {}", project.getElementName()); + deleteProject(project); + } + }; + + private SimpleWorkspaceService getWorkspaceService() { + return server.getServer().getWorkspaceService(); + } + + private ProjectObserver getProjectObserver() { + return params.projectObserver; + } + + public SpringSymbolIndex(SimpleLanguageServer server, BootLanguageServerParams params, AnnotationHierarchyAwareLookup specificProviders) { + log.debug("Creating {}", this); + this.server = server; + this.params = params; + this.projectFinder = params.projectFinder; + + 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<>(); + + SymbolHandler handler = new SymbolHandler() { + @Override + public void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol) { + SpringSymbolIndex.this.addSymbol(project, docURI, enhancedSymbol); + } + }; + +// this.indexer = new SpringIndexer[] {new SpringIndexerJava(handler, specificProviders), new SpringIndexerXML(handler) }; + this.indexer = new SpringIndexer[] {new SpringIndexerJava(handler, specificProviders)}; + + this.updateQueue = Executors.newSingleThreadExecutor(); + + getWorkspaceService().onDidChangeWorkspaceFolders(evt -> { + log.debug("workspace roots have changed event arrived - added: " + evt.getEvent().getAdded() + " - removed: " + evt.getEvent().getRemoved()); + }); + + if (getProjectObserver() != null) { + getProjectObserver().addListener(projectListener); + } + } + + public void serverInitialized() { + List globPattern = Arrays.stream(this.indexer).map(indexer -> + indexer.getFileWatchPatterns()).flatMap(Arrays::stream).collect(Collectors.toList()); + + getWorkspaceService().getFileObserver().onFileDeleted(globPattern, (file) -> { + deleteDocument(new TextDocumentIdentifier(file).getUri()); + }); + getWorkspaceService().getFileObserver().onFileCreated(globPattern, (file) -> { + createDocument(new TextDocumentIdentifier(file).getUri()); + }); + } + + public void shutdown() { + try { + synchronized(this) { + if (updateQueue != null && !updateQueue.isShutdown()) { + updateQueue.shutdownNow(); + } + + if (getProjectObserver() != null) { + getProjectObserver().removeListener(projectListener); + } + } + } catch (Exception e) { + log.error("{}", e); + } + } + + public CompletableFuture initializeProject(IJavaProject project) { + try { + if (SpringProjectUtil.isBootProject(project) || SpringProjectUtil.isSpringProject(project)) { + if (project.getElementName() == null) { + // Projects indexed by name. No name - no index for it + log.debug("Project with NULL name is being initialized"); + return CompletableFuture.completedFuture(null); + } else { + + removeSymbolsByProject(project); + + CompletableFuture[] futures = new CompletableFuture[this.indexer.length]; + for (int i = 0; i < this.indexer.length; i++) { + InitializeProject initializeItem = new InitializeProject(project, this.indexer[i]); + futures[i] = CompletableFuture.runAsync(initializeItem, this.updateQueue); + } + + return CompletableFuture.allOf(futures); + } + } else { + return deleteProject(project); + } + } catch (Throwable e) { + log.error("", e); + return Futures.error(e); + } + } + + public CompletableFuture deleteProject(IJavaProject project) { + try { + if (project.getElementName() == null) { + // Projects indexed by name. No name - no index for it + log.debug("Project with NULL name is being removed"); + return CompletableFuture.completedFuture(null); + } else { + DeleteProject initializeItem = new DeleteProject(project); + return CompletableFuture.runAsync(initializeItem, this.updateQueue); + } + } catch (Throwable e) { + log.error("", e); + return Futures.error(e); + } + } + + public CompletableFuture createDocument(String docURI) { + synchronized(this) { + List> futures = new ArrayList<>(); + + for (SpringIndexer indexer : this.indexer) { + if (indexer.isInterestedIn(docURI)) { + Optional maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI)); + + if (maybeProject.isPresent()) { + try { + String content = FileUtils.readFileToString(new File(new URI(docURI))); + UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, content, indexer); + futures.add(CompletableFuture.runAsync(updateItem, this.updateQueue)); + } + catch (Exception e) { + log.error("", e); + futures.add(Futures.error(e)); + } + } + } + } + + return CompletableFuture.allOf((CompletableFuture[]) futures.toArray(new CompletableFuture[futures.size()])); + } + } + + public CompletableFuture updateDocument(String docURI, String content) { + synchronized(this) { + List> futures = new ArrayList<>(); + + for (SpringIndexer indexer : this.indexer) { + if (indexer.isInterestedIn(docURI)) { + Optional maybeProject = projectFinder.find(new TextDocumentIdentifier(docURI)); + if (maybeProject.isPresent()) { + try { + UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, content, indexer); + futures.add(CompletableFuture.runAsync(updateItem, this.updateQueue)); + } + catch (Exception e) { + log.error("{}", e); + } + } + } + } + return CompletableFuture.allOf((CompletableFuture[]) futures.toArray(new CompletableFuture[futures.size()])); + } + } + + public CompletableFuture deleteDocument(String deletedDocURI) { + synchronized(this) { + try { + 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 List getAllSymbols(String query) { + if (query != null && query.length() > 0) { + List foundSymbols = searchMatchingSymbols(this.symbols, query); + return foundSymbols.subList(0, Math.min(50, foundSymbols.size())); + } else { + return this.symbols.subList(0, Math.min(50, this.symbols.size())); + } + } + + public List getSymbols(String docURI) { + return this.symbolsByDoc.get(docURI); + } + + public List getAllAdditionalInformation(Predicate filter) { + if (filter != null) { + return addonInformation.stream().filter(filter).collect(Collectors.toList()); + } + else { + return null; + } + } + + public List getAdditonalInformation(String docURI) { + List info = this.addonInformationByDoc.get(docURI); + return info == null ? ImmutableList.of() : info; + } + + /** + * inserts a noop operation into the worker/update quene, which allows invokers to use the + * returned future to wait for the queue items in the queue to be completed which got inserted before + * this noop. + */ + public CompletableFuture waitOperation() { + return CompletableFuture.runAsync(new Runnable() { + @Override + public void run() { + } + }, this.updateQueue); + } + + private List searchMatchingSymbols(List allsymbols, String query) { + return allsymbols.stream() + .filter(symbol -> StringUtil.containsCharactersCaseInsensitive(symbol.getName(), query)) + .collect(Collectors.toList()); + } + + + + // + // + // worker queue items to initialize, update, or delete symbols for files and projects + // + // + + private class InitializeProject implements Runnable { + + private final IJavaProject project; + private final SpringIndexer indexer; + + public InitializeProject(IJavaProject project, SpringIndexer indexer) { + this.project = project; + this.indexer = indexer; + log.debug("{} created ", this); + } + + @Override + public void run() { + log.debug("{} starting...", this); + try { + indexer.initializeProject(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 IJavaProject project; + private final SpringIndexer indexer; + + public UpdateItem(IJavaProject project, String docURI, String content, SpringIndexer indexer) { + this.project = project; + this.docURI = docURI; + this.content = content; + this.indexer = indexer; + } + + @Override + public void run() { + try { + removeSymbolsByDoc(project, docURI); + indexer.updateFile(project, docURI, content); + } catch (Exception e) { + log.error("{}", e); + } + } + } + + private class DeleteItem implements Runnable { + + private final String docURI; + private IJavaProject project; + + public DeleteItem(IJavaProject project, String docURI) { + this.project = project; + this.docURI = docURI; + } + + @Override + public void run() { + try { + removeSymbolsByDoc(project, docURI); + } catch (Exception e) { + log.error("{}", e); + } + } + } + + 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 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) { + // If project name is null it cannot be in the cache + if (project.getElementName() == null) { + return; + } + 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/java/utils/SymbolHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolHandler.java new file mode 100644 index 000000000..4ef34d899 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SymbolHandler.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils; + +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; +import org.springframework.ide.vscode.commons.java.IJavaProject; + +/** + * @author Martin Lippert + */ +public interface SymbolHandler { + + void addSymbol(IJavaProject project, String docURI, EnhancedSymbolInformation enhancedSymbol); + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SymbolProviderTestConf.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SymbolProviderTestConf.java index a36549ffc..a237e8186 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SymbolProviderTestConf.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/SymbolProviderTestConf.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Pivotal, Inc. + * Copyright (c) 2018, 2019 Pivotal, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -15,14 +15,12 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.app.BootLanguageServerInitializer; import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; -import org.springframework.ide.vscode.boot.editor.harness.AdHocPropertyHarness; import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory; import org.springframework.ide.vscode.boot.java.links.SourceLinks; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; -import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -49,8 +47,8 @@ public class SymbolProviderTestConf { return BootLanguageServerParams.createTestDefault(server, valueProviders); } - @Bean SpringIndexer springIndexer(BootLanguageServerInitializer serverInit) { - return serverInit.getComponents().get(BootJavaLanguageServerComponents.class).getSpringIndexer(); + @Bean SpringSymbolIndex springSymbolIndex(BootLanguageServerInitializer serverInit) { + return serverInit.getComponents().get(BootJavaLanguageServerComponents.class).getSpringSymbolIndex(); } @Bean DefaultSpringPropertyIndexProvider indexProvider(BootLanguageServerParams serverParams) { 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 0ba7c7c18..e73383067 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 @@ -24,7 +24,7 @@ import org.springframework.ide.vscode.boot.app.BootLanguageServerInitializer; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -43,7 +43,7 @@ public class SpringIndexerBeansTest { @Autowired private JavaProjectFinder projectFinder; private File directory; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; @Before public void setup() throws Exception { 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 6d0e3f1da..a7a4ad128 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 @@ -22,7 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -37,7 +37,7 @@ import org.springframework.test.context.junit4.SpringRunner; public class SpringIndexerFunctionBeansTest { @Autowired private BootLanguageServerHarness harness; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; @Autowired private JavaProjectFinder projectFinder; private File directory; 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 ec3d850cc..760263dd0 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 @@ -22,7 +22,7 @@ import java.util.List; import org.apache.commons.io.IOUtils; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.SymbolInformation; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; 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; @@ -101,7 +101,7 @@ public class SpringIndexerHarness { return new TestSymbolInfo(coveredText, label); } - public static void assertDocumentSymbols(SpringIndexer indexer, String documentUri, TestSymbolInfo... expectedSymbols) throws Exception { + public static void assertDocumentSymbols(SpringSymbolIndex indexer, String documentUri, TestSymbolInfo... expectedSymbols) throws Exception { List actualSymbols = getSymbolsInFile(indexer, documentUri); assertEquals(symbolsString(Arrays.asList(expectedSymbols)), symbolsString(actualSymbols)); } @@ -114,7 +114,7 @@ public class SpringIndexerHarness { return buf.toString(); } - public static List getSymbolsInFile(SpringIndexer indexer, String docURI) throws Exception { + public static List getSymbolsInFile(SpringSymbolIndex indexer, String docURI) throws Exception { List symbols = indexer.getSymbols(docURI); if (symbols!=null) { symbols = new ArrayList<>(symbols); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java index cc7f50fd0..4dd6ec002 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java @@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -44,7 +44,7 @@ public class DataRepositorySymbolProviderTest { @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; private File directory; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java index 1092d3405..4f64f3fdf 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java @@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; @@ -44,7 +44,7 @@ public class RequestMappingSymbolProviderTest { @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; private File directory; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java index 59c37bee1..602a65fe3 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java @@ -30,7 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.languageserver.testharness.TextDocumentInfo; @@ -49,7 +49,7 @@ public class WebFluxCodeLensProviderTest { @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; private File directory; @Before diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java index 68bdec668..962449c54 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java @@ -32,7 +32,7 @@ import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation; import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerInformation; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; @@ -51,7 +51,7 @@ public class WebFluxMappingSymbolProviderTest { private BootLanguageServerHarness harness; @Autowired - private SpringIndexer indexer; + private SpringSymbolIndex indexer; @Autowired JavaProjectFinder projectFinder; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java index 0861c70b9..d25a053e9 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerNonBootProjectTest.java @@ -28,7 +28,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; @@ -44,7 +44,7 @@ import org.springframework.test.context.junit4.SpringRunner; public class SpringIndexerNonBootProjectTest { @Autowired private BootLanguageServerHarness harness; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; @Autowired private JavaProjectFinder projectFinder; private File directory; 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 8fef88444..657641880 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 @@ -32,7 +32,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; -import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.util.Assert; @@ -49,7 +49,7 @@ import org.springframework.test.context.junit4.SpringRunner; public class SpringIndexerTest { @Autowired private BootLanguageServerHarness harness; - @Autowired private SpringIndexer indexer; + @Autowired private SpringSymbolIndex indexer; @Autowired private JavaProjectFinder projectFinder; private File directory; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java new file mode 100644 index 000000000..1aeb38e05 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringIndexerXMLProjectTest.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2019 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.utils.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.lsp4j.SymbolInformation; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.boot.java.utils.SpringSymbolIndex; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Martin Lippert + */ +@RunWith(SpringRunner.class) +@BootLanguageServerTest +@Import(SymbolProviderTestConf.class) +public class SpringIndexerXMLProjectTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private SpringSymbolIndex indexer; + @Autowired private JavaProjectFinder projectFinder; + + private File directory; + private String projectDir; + private IJavaProject project; + + @Before + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-xml-project/").toURI()); + projectDir = directory.toURI().toString(); + + // trigger project creation + project = projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + + CompletableFuture initProject = indexer.waitOperation(); + initProject.get(5, TimeUnit.SECONDS); + } + + @Test + public void testScanningSimpleSpringXMLConfig() throws Exception { + List allSymbols = indexer.getAllSymbols(""); + +// assertEquals(3, allSymbols.size()); +// +// String docUri = directory.toPath().resolve("config/simple-spring-config.xml").toUri().toString(); +// assertTrue(containsSymbol(allSymbols, "@+ 'transactionManager' DataSourceTransactionManager", docUri, 6, 8, 7, 46)); +// assertTrue(containsSymbol(allSymbols, "@+ 'jdbcTemplate' JdbcTemplate", docUri, 11, 1, 11, 28)); +// assertTrue(containsSymbol(allSymbols, "@+ 'namedParameterJdbcTemplate' NamedParameterJdbcTemplate", docUri, 11, 1, 11, 28)); + } + + private boolean containsSymbol(List symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) { + for (Iterator iterator = symbols.iterator(); iterator.hasNext();) { + SymbolInformation symbol = iterator.next(); + + if (symbol.getName().equals(name) + && symbol.getLocation().getUri().equals(uri) + && symbol.getLocation().getRange().getStart().getLine() == startLine + && symbol.getLocation().getRange().getStart().getCharacter() == startCHaracter + && symbol.getLocation().getRange().getEnd().getLine() == endLine + && symbol.getLocation().getRange().getEnd().getCharacter() == endCharacter) { + return true; + } + } + + return false; + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.gitignore b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.gitignore new file mode 100644 index 000000000..2af7cefb0 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.gitignore @@ -0,0 +1,24 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +nbproject/private/ +build/ +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.mvn/wrapper/maven-wrapper.jar b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..5fd4d5023 Binary files /dev/null and b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.mvn/wrapper/maven-wrapper.jar differ diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.mvn/wrapper/maven-wrapper.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c954cec91 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/config/simple-spring-config.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/config/simple-spring-config.xml new file mode 100644 index 000000000..4adff198c --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/config/simple-spring-config.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/config/spring-config.notprocessed b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/config/spring-config.notprocessed new file mode 100644 index 000000000..a029c45ab --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/config/spring-config.notprocessed @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/mvnw new file mode 100755 index 000000000..a1ba1bf55 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/mvnw.cmd new file mode 100644 index 000000000..2b934e89d --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/pom.xml new file mode 100644 index 000000000..6d23031d1 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/pom.xml @@ -0,0 +1,22 @@ + + + 4.0.0 + + test-annotation-indexing-non-boot-project + test-projects + 5.1.3 + jar + + test-annotation-indexing-non-boot-project + Test projects for regular non-boot spring project - annotation indexing + + + + org.springframework + spring-webmvc + 5.1.3.RELEASE + + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/src/main/java/org/test/MainClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/src/main/java/org/test/MainClass.java new file mode 100644 index 000000000..79b553914 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-annotation-indexing-xml-project/src/main/java/org/test/MainClass.java @@ -0,0 +1,8 @@ +package org.test; + +public class MainClass { + + public static void main(String[] args) throws Exception { + } + +}