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 extends SymbolInformation> 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 extends SymbolAddOnInformation> 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(""+endElement.getName().toString()+">");
+ 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 extends SymbolInformation> 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 extends SymbolAddOnInformation> 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 extends SymbolInformation> 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 extends SymbolInformation> 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 extends SymbolInformation> symbols, String name, String uri, int startLine, int startCHaracter, int endLine, int endCharacter) {
+ for (Iterator extends SymbolInformation> 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 {
+ }
+
+}