Merge pull request #306 from spring-projects/symbol-dependencies

Track symbol dependencies ...
This commit is contained in:
Kris De Volder
2019-06-17 09:17:41 -07:00
committed by GitHub
21 changed files with 827 additions and 151 deletions

View File

@@ -65,4 +65,20 @@ public class UriUtil {
}
}
public static File toFile(String docURI) {
try {
return new File(new URI(docURI));
} catch (Exception e) {
return null;
}
}
public static String toFileString(String docURI) {
try {
return new File(new URI(docURI)).getAbsolutePath();
} catch (Exception e) {
return null;
}
}
}

View File

@@ -90,7 +90,7 @@ public class SpringSymbolIndex implements InitializingBean {
private final ConcurrentMap<String, List<EnhancedSymbolInformation>> symbolsByProject = new ConcurrentHashMap<>();
private final ExecutorService updateQueue = Executors.newSingleThreadExecutor();
private SpringIndexer[] indexer;
private SpringIndexer[] indexers;
private static final Logger log = LoggerFactory.getLogger(SpringSymbolIndex.class);
@@ -153,7 +153,7 @@ public class SpringSymbolIndex implements InitializingBean {
springIndexerXML = new SpringIndexerXML(handler, namespaceHandler, this.cache, projectFinder());
springIndexerJava = new SpringIndexerJava(handler, specificProviders, this.cache, projectFinder());
this.indexer = new SpringIndexer[] {springIndexerJava};
this.indexers = new SpringIndexer[] {springIndexerJava};
getWorkspaceService().onDidChangeWorkspaceFolders(evt -> {
@@ -172,7 +172,7 @@ public class SpringSymbolIndex implements InitializingBean {
if (BootJavaLanguageServerComponents.LANGUAGES.contains(document.getLanguageId())) {
String docURI = document.getId().getUri();
String content = document.get();
this.updateDocument(docURI, content);
this.updateDocument(docURI, content, "didSave event");
}
});
config.addListener(evt -> {
@@ -196,15 +196,15 @@ public class SpringSymbolIndex implements InitializingBean {
createDocument(new TextDocumentIdentifier(file).getUri());
});
getWorkspaceService().getFileObserver().onFileChanged(globPattern, (file) -> {
updateDocument(new TextDocumentIdentifier(file).getUri(), null);
updateDocument(new TextDocumentIdentifier(file).getUri(), null, "file changed");
});
}
public CompletableFuture<Void> configureIndexer(SymbolIndexConfig config) {
List<CompletableFuture<?>> futuresList = new ArrayList<>();
synchronized (this) {
if (config.isScanXml() && !(Arrays.asList(this.indexer).contains(springIndexerXML))) {
this.indexer = new SpringIndexer[] {springIndexerJava, springIndexerXML};
if (config.isScanXml() && !(Arrays.asList(this.indexers).contains(springIndexerXML))) {
this.indexers = new SpringIndexer[] {springIndexerJava, springIndexerXML};
futuresList.add(CompletableFuture.runAsync(() -> springIndexerXML.setScanFolderGlobs(config.getXmlScanFoldersGlobs())));
List<String> globPattern = Arrays.asList(springIndexerXML.getFileWatchPatterns());
@@ -216,12 +216,12 @@ public class SpringSymbolIndex implements InitializingBean {
createDocument(new TextDocumentIdentifier(file).getUri());
});
watchXMLChangedRegistration = getWorkspaceService().getFileObserver().onFileChanged(globPattern, (file) -> {
updateDocument(new TextDocumentIdentifier(file).getUri(), null);
updateDocument(new TextDocumentIdentifier(file).getUri(), null, "xml changed");
});
}
else if (!config.isScanXml() && Arrays.asList(this.indexer).contains(springIndexerXML)) {
this.indexer = new SpringIndexer[] {springIndexerJava};
else if (!config.isScanXml() && Arrays.asList(this.indexers).contains(springIndexerXML)) {
this.indexers = new SpringIndexer[] {springIndexerJava};
futuresList.add(CompletableFuture.runAsync(() -> springIndexerXML.setScanFolderGlobs(new String[0])));
getWorkspaceService().getFileObserver().unsubscribe(watchXMLChangedRegistration);
@@ -267,9 +267,9 @@ public class SpringSymbolIndex implements InitializingBean {
removeSymbolsByProject(project);
@SuppressWarnings("unchecked")
CompletableFuture<Void>[] futures = new CompletableFuture[this.indexer.length];
for (int i = 0; i < this.indexer.length; i++) {
InitializeProject initializeItem = new InitializeProject(project, this.indexer[i]);
CompletableFuture<Void>[] futures = new CompletableFuture[this.indexers.length];
for (int i = 0; i < this.indexers.length; i++) {
InitializeProject initializeItem = new InitializeProject(project, this.indexers[i]);
futures[i] = CompletableFuture.runAsync(initializeItem, this.updateQueue);
}
@@ -284,6 +284,10 @@ public class SpringSymbolIndex implements InitializingBean {
}
}
public SpringIndexerJava getJavaIndexer() {
return springIndexerJava;
}
public CompletableFuture<Void> deleteProject(IJavaProject project) {
try {
if (project.getElementName() == null) {
@@ -291,7 +295,7 @@ public class SpringSymbolIndex implements InitializingBean {
log.debug("Project with NULL name is being removed");
return CompletableFuture.completedFuture(null);
} else {
DeleteProject initializeItem = new DeleteProject(project, this.indexer);
DeleteProject initializeItem = new DeleteProject(project, this.indexers);
return CompletableFuture.runAsync(initializeItem, this.updateQueue);
}
} catch (Throwable e) {
@@ -304,7 +308,7 @@ public class SpringSymbolIndex implements InitializingBean {
synchronized(this) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (SpringIndexer indexer : this.indexer) {
for (SpringIndexer indexer : this.indexers) {
if (indexer.isInterestedIn(docURI)) {
Optional<IJavaProject> maybeProject = projectFinder().find(new TextDocumentIdentifier(docURI));
@@ -320,9 +324,7 @@ public class SpringSymbolIndex implements InitializingBean {
return "";
}
};
UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, lastModified, content, indexer);
futures.add(CompletableFuture.runAsync(updateItem, this.updateQueue));
futures.add(CompletableFuture.runAsync(() -> updateItem(maybeProject.get(), docURI, lastModified, content, indexer), this.updateQueue));
}
catch (Exception e) {
log.error("", e);
@@ -340,11 +342,12 @@ public class SpringSymbolIndex implements InitializingBean {
return params.projectFinder;
}
public CompletableFuture<Void> updateDocument(String docURI, String content) {
public CompletableFuture<Void> updateDocument(String docURI, String content, String reason) {
log.info("Update document [{}]: {}",reason, docURI);
synchronized(this) {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (SpringIndexer indexer : this.indexer) {
for (SpringIndexer indexer : this.indexers) {
if (indexer.isInterestedIn(docURI)) {
Optional<IJavaProject> maybeProject = projectFinder().find(new TextDocumentIdentifier(docURI));
if (maybeProject.isPresent()) {
@@ -365,8 +368,7 @@ public class SpringSymbolIndex implements InitializingBean {
}
};
UpdateItem updateItem = new UpdateItem(maybeProject.get(), docURI, lastModified, contentSupplier, indexer);
futures.add(CompletableFuture.runAsync(updateItem, this.updateQueue));
futures.add(updateItem(maybeProject.get(), docURI, lastModified, contentSupplier, indexer));
}
catch (Exception e) {
log.error("{}", e);
@@ -383,7 +385,7 @@ public class SpringSymbolIndex implements InitializingBean {
try {
Optional<IJavaProject> maybeProject = projectFinder().find(new TextDocumentIdentifier(deletedDocURI));
if (maybeProject.isPresent()) {
DeleteItem deleteItem = new DeleteItem(maybeProject.get(), deletedDocURI, this.indexer);
DeleteItem deleteItem = new DeleteItem(maybeProject.get(), deletedDocURI, this.indexers);
return CompletableFuture.runAsync(deleteItem, this.updateQueue);
}
}
@@ -544,31 +546,17 @@ public class SpringSymbolIndex implements InitializingBean {
}
}
private class UpdateItem implements Runnable {
private final String docURI;
private final Supplier<String> content;
private final IJavaProject project;
private final SpringIndexer indexer;
private final long lastModified;
public UpdateItem(IJavaProject project, String docURI, long lastModified, Supplier<String> content, SpringIndexer indexer) {
this.project = project;
this.docURI = docURI;
this.lastModified = lastModified;
this.content = content;
this.indexer = indexer;
}
@Override
public void run() {
CompletableFuture<Void> updateItem(IJavaProject project, String docURI, long lastModified, Supplier<String> content, SpringIndexer indexer) {
log.debug("scheduling updateItem {}. {}, {}, {}", project.getElementName(), docURI, lastModified, indexer);
return CompletableFuture.runAsync(() -> {
log.debug("updateItem {}. {}, {}, {}", project.getElementName(), docURI, lastModified, indexer);
try {
removeSymbolsByDoc(project, docURI);
indexer.updateFile(project, docURI, lastModified, content);
} catch (Exception e) {
log.error("{}", e);
}
}
}, this.updateQueue);
}
private class DeleteItem implements Runnable {

View File

@@ -15,7 +15,6 @@ import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.core.dom.ASTNode;
@@ -30,13 +29,13 @@ import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.boot.java.utils.CachedSymbol;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import ch.qos.logback.core.Context;
/**
* @author Martin Lippert
*/
@@ -48,11 +47,11 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
if (node.getParent() instanceof MethodDeclaration) {
try {
Location location = new Location(doc.getUri(), doc.toRange(node.getStartPosition(), node.getLength()));
String[] path = getPath(node);
String[] parentPath = getParentPath(node);
String[] methods = getMethod(node);
String[] contentTypes = getContentTypes(node);
String[] acceptTypes = getAcceptTypes(node);
String[] path = getPath(node, context);
String[] parentPath = getParentPath(node, context);
String[] methods = getMethod(node, context);
String[] contentTypes = getContentTypes(node, context);
String[] acceptTypes = getAcceptTypes(node, context);
Stream<String> stream = parentPath == null ? Stream.of("") : Arrays.stream(parentPath);
stream.filter(Objects::nonNull)
@@ -73,7 +72,7 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
}
}
private String[] getMethod(Annotation node) {
private String[] getMethod(Annotation node, SpringIndexerJavaContext context) {
String[] methods = null;
if (node.isNormalAnnotation()) {
@@ -86,7 +85,7 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
String valueName = pair.getName().getIdentifier();
if (valueName != null && valueName.equals("method")) {
Expression expression = pair.getValue();
methods = ASTUtils.getExpressionValueAsArray(expression);
methods = ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
break;
}
}
@@ -98,14 +97,14 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
if (methods == null && node.getParent() instanceof MethodDeclaration) {
Annotation parentAnnotation = getParentAnnotation(node);
if (parentAnnotation != null) {
methods = getMethod(parentAnnotation);
methods = getMethod(parentAnnotation, context);
}
}
return methods;
}
private String[] getPath(Annotation node) {
private String[] getPath(Annotation node, SpringIndexerJavaContext context) {
if (node.isNormalAnnotation()) {
NormalAnnotation normNode = (NormalAnnotation) node;
List<?> values = normNode.values();
@@ -116,22 +115,22 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
String valueName = pair.getName().getIdentifier();
if (valueName != null && (valueName.equals("value") || valueName.equals("path"))) {
Expression expression = pair.getValue();
return ASTUtils.getExpressionValueAsArray(expression);
return ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
}
}
} else if (node.isSingleMemberAnnotation()) {
SingleMemberAnnotation singleNode = (SingleMemberAnnotation) node;
Expression expression = singleNode.getValue();
return ASTUtils.getExpressionValueAsArray(expression);
return ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
return new String[] { "" };
}
private String[] getParentPath(Annotation node) {
private String[] getParentPath(Annotation node, SpringIndexerJavaContext context) {
Annotation parentAnnotation = getParentAnnotation(node);
return parentAnnotation == null ? null : getPath(parentAnnotation);
return parentAnnotation == null ? null : getPath(parentAnnotation, context);
}
private Annotation getParentAnnotation(Annotation node) {
@@ -178,7 +177,7 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
return null;
}
private String[] getAcceptTypes(Annotation node) {
private String[] getAcceptTypes(Annotation node, SpringIndexerJavaContext context) {
if (node.isNormalAnnotation()) {
NormalAnnotation normNode = (NormalAnnotation) node;
List<?> values = normNode.values();
@@ -189,7 +188,7 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
String valueName = pair.getName().getIdentifier();
if (valueName != null && valueName.equals("consumes")) {
Expression expression = pair.getValue();
return ASTUtils.getExpressionValueAsArray(expression);
return ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
}
}
@@ -197,7 +196,7 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
return new String[0];
}
private String[] getContentTypes(Annotation node) {
private String[] getContentTypes(Annotation node, SpringIndexerJavaContext context) {
if (node.isNormalAnnotation()) {
NormalAnnotation normNode = (NormalAnnotation) node;
List<?> values = normNode.values();
@@ -208,7 +207,7 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider {
String valueName = pair.getName().getIdentifier();
if (valueName != null && valueName.equals("produces")) {
Expression expression = pair.getValue();
return ASTUtils.getExpressionValueAsArray(expression);
return ASTUtils.getExpressionValueAsArray(expression, context::addDependency);
}
}
}

View File

@@ -14,8 +14,10 @@ import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
@@ -179,20 +181,24 @@ public class ASTUtils {
}
}
public static String getExpressionValueAsString(Expression exp) {
public static String getExpressionValueAsString(Expression exp, Consumer<ITypeBinding> dependencies) {
if (exp instanceof StringLiteral) {
return getLiteralValue((StringLiteral) exp);
} else if (exp instanceof Name) {
IBinding binding = ((Name) exp).resolveBinding();
if (binding != null && binding.getKind() == IBinding.VARIABLE) {
IVariableBinding varBinding = (IVariableBinding) binding;
ITypeBinding klass = varBinding.getDeclaringClass();
if (klass!=null) {
dependencies.accept(klass);
}
Object constValue = varBinding.getConstantValue();
if (constValue != null) {
return constValue.toString();
}
}
if (exp instanceof QualifiedName) {
return getExpressionValueAsString(((QualifiedName) exp).getName());
return getExpressionValueAsString(((QualifiedName) exp).getName(), dependencies);
}
else if (exp instanceof SimpleName) {
return ((SimpleName) exp).getIdentifier();
@@ -206,13 +212,13 @@ public class ASTUtils {
}
@SuppressWarnings("unchecked")
public static String[] getExpressionValueAsArray(Expression exp) {
public static String[] getExpressionValueAsArray(Expression exp, Consumer<ITypeBinding> dependencies) {
if (exp instanceof ArrayInitializer) {
ArrayInitializer array = (ArrayInitializer) exp;
return ((List<Expression>) array.expressions()).stream().map(e -> getExpressionValueAsString(e))
return ((List<Expression>) array.expressions()).stream().map(e -> getExpressionValueAsString(e, dependencies))
.filter(Objects::nonNull).toArray(String[]::new);
} else {
String rm = getExpressionValueAsString(exp);
String rm = getExpressionValueAsString(exp, dependencies);
if (rm != null) {
return new String[] { rm };
}

View File

@@ -0,0 +1,15 @@
/*******************************************************************************
* 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.utils;
public interface FileScanListener {
void fileScanned(String path);
}

View File

@@ -18,14 +18,17 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
@@ -54,12 +57,14 @@ import org.springframework.ide.vscode.commons.util.UriUtil;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.base.Supplier;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
/**
* @author Martin Lippert
*/
public class SpringIndexerJava implements SpringIndexer {
public static enum SCAN_PASS {
ONE, TWO
}
@@ -71,7 +76,48 @@ public class SpringIndexerJava implements SpringIndexer {
private final SymbolCache cache;
private final JavaProjectFinder projectFinder;
private boolean scanTestJavaSources = false;
private FileScanListener fileScanListener = null; //used by test code only
private final DependencyTracker dependencyTracker = new DependencyTracker();
public DependencyTracker getDependencyTracker() {
return dependencyTracker;
}
public static class DependencyTracker {
private Multimap<String, String> dependencies = MultimapBuilder.hashKeys().hashSetValues().build();
public void addDependency(String sourceFile, ITypeBinding dependsOn) {
dependencies.put(sourceFile, dependsOn.getKey());
}
public void dump() {
log.info("=== Dependencies ===");
for (String sourceFile : dependencies.keySet()) {
Collection<String> values = dependencies.get(sourceFile);
if (!values.isEmpty())
log.info(sourceFile + "=> ");
for (String v : values) {
log.info(" "+v);
}
}
log.info("======================");
}
public Multimap<String, String> getAllDependencies() {
return dependencies;
}
public void update(String file, Set<String> dependenciesForFile) {
dependencies.replaceValues(file, dependenciesForFile);
}
public void restore(Multimap<String, String> deps) {
this.dependencies = deps;
}
}
public SpringIndexerJava(SymbolHandler symbolHandler, AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders, SymbolCache cache,
JavaProjectFinder projectFimder) {
this.symbolHandler = symbolHandler;
@@ -132,8 +178,87 @@ public class SpringIndexerJava implements SpringIndexer {
}
private void scanFile(IJavaProject project, String docURI, long lastModified, String content) throws Exception {
//TODO: optimise? Check last modified to avoid redundant scan. Reason:
// on saving a file, this may be triggered twice. Once when file is saved and once more because of a 'file changed'
// on file system. Looking at the timestamp in the cache we should be able to avoid a second scan of the exact same
// content.
ASTParser parser = createParser(project, false);
String unitName = docURI.substring(docURI.lastIndexOf("/"));
parser.setUnitName(unitName);
log.debug("Scan file: {}", unitName);
parser.setSource(content.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu != null) {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = UriUtil.toFileString(docURI);
Set<String> changedTypes = new HashSet<>();
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
lastModified, docRef, content, generatedSymbols, SCAN_PASS.ONE, new ArrayList<>(), changedTypes);
scanAST(context);
SymbolCacheKey cacheKey = getCacheKey(project);
this.cache.update(cacheKey, file, lastModified, generatedSymbols, context.getDependencies());
// dependencyTracker.dump();
for (CachedSymbol symbol : generatedSymbols) {
symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol());
}
Set<String> scannedFiles = new HashSet<>();
scannedFiles.add(file);
fileScannedEvent(file);
scanAffectedFiles(project, changedTypes, scannedFiles);
}
}
private void fileScannedEvent(String file) {
if (fileScanListener!=null) {
fileScanListener.fileScanned(file);
}
}
private void scanAffectedFiles(IJavaProject project, Set<String> changedTypes, Set<String> scannedFiles) {
log.info("Start scanning affected files for types {}", changedTypes);
//TODO: optimise? When multiple files are 'affected', we could try to parse and scan them in batch.
// I.e. something similar to the 'scanFiles' method.
// That is probably more efficient than one by one.
Multimap<String, String> dependencies = dependencyTracker.getAllDependencies();
// Collection<String> filesToScan = new HashSet<>();
boolean scannedAnyFiles;
do {
scannedAnyFiles = false;
for (String file : dependencies.keys()) {
try {
if (!scannedFiles.contains(file)) {
Collection<String> dependsOn = dependencies.get(file);
if (dependsOn.stream().anyMatch(changedTypes::contains)) {
scannedFiles.add(file);
scannedAnyFiles = true;
log.debug("Should also scan affected file: {}", file);
File f = new File(file);
scanAffectedFile(project, UriUtil.toUri(f).toString(), f.lastModified(), FileUtils.readFileToString(f), changedTypes);
fileScannedEvent(file);
}
}
} catch (Exception e) {
log.debug("Problems scanning file {}", file, e);
}
}
if (scannedAnyFiles) {
log.debug("Some affected files where scanned, make another pass");
}
} while (scannedAnyFiles);
log.info("Finished scanning affected files {}", scannedFiles);
}
private void scanAffectedFile(IJavaProject project, String docURI, long lastModified, String content, Set<String> changedTypes) throws Exception {
symbolHandler.removeSymbols(project, docURI);
ASTParser parser = createParser(project, false);
String unitName = docURI.substring(docURI.lastIndexOf("/"));
parser.setUnitName(unitName);
parser.setSource(content.toCharArray());
@@ -143,27 +268,32 @@ public class SpringIndexerJava implements SpringIndexer {
if (cu != null) {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
AtomicReference<TextDocument> docRef = new AtomicReference<>();
String file = new File(new URI(docURI)).getAbsolutePath();
File file = UriUtil.toFile(docURI);
if (file!=null) {
SpringIndexerJavaContext context = new SpringIndexerJavaContext(
project, cu,
docURI, file.toString(), lastModified, docRef,
content, generatedSymbols,
SCAN_PASS.ONE, new ArrayList<>(), changedTypes);
scanAST(context);
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, file,
lastModified, docRef, content, generatedSymbols, SCAN_PASS.ONE, new ArrayList<>());
SymbolCacheKey cacheKey = getCacheKey(project);
this.cache.update(cacheKey, file.getAbsolutePath(), lastModified, generatedSymbols, context.getDependencies());
// dependencyTracker.dump();
scanAST(context);
SymbolCacheKey cacheKey = getCacheKey(project);
this.cache.update(cacheKey, file, lastModified, generatedSymbols);
for (CachedSymbol symbol : generatedSymbols) {
symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol());
for (CachedSymbol symbol : generatedSymbols) {
symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol());
}
}
}
}
private void scanFiles(IJavaProject project, String[] javaFiles) throws Exception {
SymbolCacheKey cacheKey = getCacheKey(project);
CachedSymbol[] symbols = this.cache.retrieve(cacheKey, javaFiles);
Pair<CachedSymbol[], Multimap<String, String>> cached = this.cache.retrieve(cacheKey, javaFiles);
if (symbols == null) {
CachedSymbol[] symbols;
if (cached == null) {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
log.info("scan java files, AST parse, pass 1 for files: {}", javaFiles.length);
@@ -176,12 +306,16 @@ public class SpringIndexerJava implements SpringIndexer {
scanFiles(project, pass2Files, generatedSymbols, SCAN_PASS.TWO);
}
this.cache.store(cacheKey, javaFiles, generatedSymbols);
this.cache.store(cacheKey, javaFiles, generatedSymbols, dependencyTracker.getAllDependencies());
// dependencyTracker.dump();
symbols = (CachedSymbol[]) generatedSymbols.toArray(new CachedSymbol[generatedSymbols.size()]);
}
else {
symbols = cached.getLeft();
log.info("scan java files used cached data: {} - no. of cached symbols retrieved: {}", project.getElementName(), symbols.length);
this.dependencyTracker.restore(cached.getRight());
log.info("scan java files restored cached dependency data: {} - no. of cached dependencies: {}", cached.getRight().size());
}
if (symbols != null) {
@@ -206,7 +340,7 @@ public class SpringIndexerJava implements SpringIndexer {
AtomicReference<TextDocument> docRef = new AtomicReference<>();
SpringIndexerJavaContext context = new SpringIndexerJavaContext(project, cu, docURI, sourceFilePath,
lastModified, docRef, null, generatedSymbols, pass, nextPassFiles);
lastModified, docRef, null, generatedSymbols, pass, nextPassFiles, null);
scanAST(context);
}
@@ -218,12 +352,18 @@ public class SpringIndexerJava implements SpringIndexer {
}
private void scanAST(final SpringIndexerJavaContext context) {
context.getCu().accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration node) {
try {
Set<String> changedTypes = context.getChangedTypes();
if (changedTypes!=null) {
ITypeBinding changedType = node.resolveBinding();
if (changedType!=null) {
changedTypes.add(changedType.getKey());
}
}
extractSymbolInformation(node, context);
}
catch (Exception e) {
@@ -279,6 +419,7 @@ public class SpringIndexerJava implements SpringIndexer {
return super.visit(node);
}
});
dependencyTracker.update(context.getFile(), context.getDependencies());;
}
private void extractSymbolInformation(TypeDeclaration typeDeclaration, final SpringIndexerJavaContext context) throws Exception {
@@ -471,4 +612,8 @@ public class SpringIndexerJava implements SpringIndexer {
}
}
public void setFileScanListener(FileScanListener fileScanListener) {
this.fileScanListener = fileScanListener;
}
}

View File

@@ -10,10 +10,13 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.utils;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.SCAN_PASS;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -33,10 +36,22 @@ public class SpringIndexerJavaContext {
private final List<CachedSymbol> generatedSymbols;
private final SCAN_PASS pass;
private final List<String> nextPassFiles;
private final Set<String> dependencies = new HashSet<>();
private final Set<String> dependentTypes;
public SpringIndexerJavaContext(IJavaProject project, CompilationUnit cu, String docURI, String file, long lastModified,
AtomicReference<TextDocument> docRef, String content, List<CachedSymbol> generatedSymbols, SCAN_PASS pass,
List<String> nextPassFiles) {
public SpringIndexerJavaContext(
IJavaProject project,
CompilationUnit cu,
String docURI,
String file,
long lastModified,
AtomicReference<TextDocument> docRef,
String content,
List<CachedSymbol> generatedSymbols,
SCAN_PASS pass,
List<String> nextPassFiles,
Set<String> dependentTypes
) {
super();
this.project = project;
this.cu = cu;
@@ -48,6 +63,7 @@ public class SpringIndexerJavaContext {
this.generatedSymbols = generatedSymbols;
this.pass = pass;
this.nextPassFiles = nextPassFiles;
this.dependentTypes = dependentTypes;
}
public IJavaProject getProject() {
@@ -90,4 +106,16 @@ public class SpringIndexerJavaContext {
return nextPassFiles;
}
public Set<String> getDependencies() {
return dependencies;
}
public void addDependency(ITypeBinding dependsOn) {
dependencies.add(dependsOn.getKey());
}
public Set<String> getChangedTypes() {
return dependentTypes;
}
}

View File

@@ -105,7 +105,7 @@ public class SpringIndexerXML implements SpringIndexer {
long startTime = System.currentTimeMillis();
SymbolCacheKey cacheKey = getCacheKey(project);
CachedSymbol[] symbols = this.cache.retrieve(cacheKey, files);
CachedSymbol[] symbols = this.cache.retrieveSymbols(cacheKey, files);
if (symbols == null) {
List<CachedSymbol> generatedSymbols = new ArrayList<CachedSymbol>();
@@ -113,7 +113,7 @@ public class SpringIndexerXML implements SpringIndexer {
scanFile(project, file, generatedSymbols);
}
this.cache.store(cacheKey, files, generatedSymbols);
this.cache.store(cacheKey, files, generatedSymbols, null);
symbols = (CachedSymbol[]) generatedSymbols.toArray(new CachedSymbol[generatedSymbols.size()]);
}
@@ -148,7 +148,7 @@ public class SpringIndexerXML implements SpringIndexer {
SymbolCacheKey cacheKey = getCacheKey(project);
String file = new File(new URI(docURI)).getAbsolutePath();
this.cache.update(cacheKey, file, lastModified, generatedSymbols);
this.cache.update(cacheKey, file, lastModified, generatedSymbols, null);
for (CachedSymbol symbol : generatedSymbols) {
symbolHandler.addSymbol(project, symbol.getDocURI(), symbol.getEnhancedSymbol());

View File

@@ -11,18 +11,27 @@
package org.springframework.ide.vscode.boot.java.utils;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Multimap;
/**
* @author Martin Lippert
*/
public interface SymbolCache {
void store(SymbolCacheKey cacheKey, String[] files, List<CachedSymbol> generatedSymbols);
CachedSymbol[] retrieve(SymbolCacheKey cacheKey, String[] files);
void store(SymbolCacheKey cacheKey, String[] files, List<CachedSymbol> generatedSymbols, Multimap<String, String> dependencies);
Pair<CachedSymbol[], Multimap<String, String>> retrieve(SymbolCacheKey cacheKey, String[] files);
void update(SymbolCacheKey cacheKey, String file, long lastModified, List<CachedSymbol> generatedSymbols);
void update(SymbolCacheKey cacheKey, String file, long lastModified, List<CachedSymbol> generatedSymbols, Set<String> dependencies);
void remove(SymbolCacheKey cacheKey);
void removeFile(SymbolCacheKey symbolCacheKey, String file);
default CachedSymbol[] retrieveSymbols(SymbolCacheKey cacheKey, String[] files) {
Pair<CachedSymbol[], Multimap<String, String>> r = retrieve(cacheKey, files);
return r!=null ? r.getLeft() : null;
}
}

View File

@@ -17,18 +17,27 @@ import java.io.IOException;
import java.lang.reflect.Type;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.commons.lang3.tuple.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
@@ -69,7 +78,10 @@ public class SymbolCacheOnDisc implements SymbolCache {
}
@Override
public void store(SymbolCacheKey cacheKey, String[] files, List<CachedSymbol> generatedSymbols) {
public void store(SymbolCacheKey cacheKey, String[] files, List<CachedSymbol> generatedSymbols, Multimap<String, String> dependencies) {
if (dependencies==null) {
dependencies = ImmutableMultimap.of();
}
SortedMap<String, Long> timestampedFiles = new TreeMap<>();
timestampedFiles = Arrays.stream(files)
@@ -82,11 +94,11 @@ public class SymbolCacheOnDisc implements SymbolCache {
}
}, (v1,v2) -> { throw new RuntimeException(String.format("Duplicate key for values %s and %s", v1, v2));}, TreeMap::new));
save(cacheKey, generatedSymbols, timestampedFiles);
save(cacheKey, generatedSymbols, timestampedFiles, dependencies.asMap());
}
@Override
public CachedSymbol[] retrieve(SymbolCacheKey cacheKey, String[] files) {
public Pair<CachedSymbol[], Multimap<String, String>> retrieve(SymbolCacheKey cacheKey, String[] files) {
try {
File cacheStore = new File(cacheDirectory, cacheKey.toString() + ".json");
if (cacheStore.exists()) {
@@ -108,7 +120,15 @@ public class SymbolCacheOnDisc implements SymbolCache {
this.stores.put(cacheKey, store);
List<CachedSymbol> symbols = store.getSymbols();
return (CachedSymbol[]) symbols.toArray(new CachedSymbol[symbols.size()]);
Map<String, Collection<String>> storedDependencies = store.getDependencies();
Multimap<String, String> dependencies = MultimapBuilder.hashKeys().hashSetValues().build();
for (Entry<String, Collection<String>> entry : storedDependencies.entrySet()) {
dependencies.replaceValues(entry.getKey(), entry.getValue());
}
return Pair.of(
(CachedSymbol[]) symbols.toArray(new CachedSymbol[symbols.size()]),
MultimapBuilder.hashKeys().hashSetValues().build(dependencies)
);
}
}
}
@@ -130,8 +150,9 @@ public class SymbolCacheOnDisc implements SymbolCache {
List<CachedSymbol> cachedSymbols = cacheStore.getSymbols().stream()
.filter(cachedSymbol -> !cachedSymbol.getDocURI().equals(docURI))
.collect(Collectors.toList());
save(cacheKey, cachedSymbols, timestampedFiles);
Map<String, Collection<String>> changedDeps = new HashMap<>(cacheStore.getDependencies());
changedDeps.remove(file);
save(cacheKey, cachedSymbols, timestampedFiles, changedDeps);
}
}
@@ -145,7 +166,10 @@ public class SymbolCacheOnDisc implements SymbolCache {
}
@Override
public void update(SymbolCacheKey cacheKey, String file, long lastModified, List<CachedSymbol> generatedSymbols) {
public void update(SymbolCacheKey cacheKey, String file, long lastModified, List<CachedSymbol> generatedSymbols, Set<String> dependencies) {
if (dependencies==null) {
dependencies = ImmutableSet.of();
}
CacheStore cacheStore = this.stores.get(cacheKey);
if (cacheStore != null) {
@@ -159,14 +183,20 @@ public class SymbolCacheOnDisc implements SymbolCache {
.collect(Collectors.toList());
cachedSymbols.addAll(generatedSymbols);
save(cacheKey, cachedSymbols, timestampedFiles);
Map<String, Collection<String>> changedDependencies = new HashMap<>(cacheStore.getDependencies());
if (dependencies.isEmpty()) {
changedDependencies.remove(file);
} else {
changedDependencies.put(file, ImmutableSet.copyOf(dependencies));
}
save(cacheKey, cachedSymbols, timestampedFiles, changedDependencies);
}
}
private void save(SymbolCacheKey cacheKey, List<CachedSymbol> generatedSymbols,
SortedMap<String, Long> timestampedFiles) {
CacheStore store = new CacheStore(cacheKey.toString(), timestampedFiles, generatedSymbols);
SortedMap<String, Long> timestampedFiles, Map<String, Collection<String>> dependencies) {
CacheStore store = new CacheStore(timestampedFiles, generatedSymbols, dependencies);
this.stores.put(cacheKey, store);
try (FileWriter writer = new FileWriter(new File(cacheDirectory, cacheKey.toString() + ".json")))
@@ -216,19 +246,19 @@ public class SymbolCacheOnDisc implements SymbolCache {
*/
private static class CacheStore {
private final String cacheKey;
private final SortedMap<String, Long> timestampedFiles;
private final List<CachedSymbol> symbols;
private final Map<String, Collection<String>> dependencies;
public CacheStore(String cacheKey, SortedMap<String, Long> timestampedFiles, List<CachedSymbol> symbols) {
public CacheStore(SortedMap<String, Long> timestampedFiles, List<CachedSymbol> symbols, Map<String, Collection<String>> dependencies) {
super();
this.cacheKey = cacheKey;
this.timestampedFiles = timestampedFiles;
this.symbols = symbols;
this.dependencies = dependencies;
}
public String getCacheKey() {
return cacheKey;
public Map<String, Collection<String>> getDependencies() {
return dependencies;
}
public List<CachedSymbol> getSymbols() {

View File

@@ -11,6 +11,11 @@
package org.springframework.ide.vscode.boot.java.utils;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.tuple.Pair;
import com.google.common.collect.Multimap;
/**
* @author Martin Lippert
@@ -18,16 +23,16 @@ import java.util.List;
public class SymbolCacheVoid implements SymbolCache {
@Override
public void store(SymbolCacheKey cacheKey, String[] files, List<CachedSymbol> generatedSymbols) {
public void store(SymbolCacheKey cacheKey, String[] files, List<CachedSymbol> generatedSymbols, Multimap<String,String> dependencies) {
}
@Override
public CachedSymbol[] retrieve(SymbolCacheKey cacheKey, String[] files) {
public Pair<CachedSymbol[], Multimap<String, String>> retrieve(SymbolCacheKey cacheKey, String[] files) {
return null;
}
@Override
public void update(SymbolCacheKey cacheKey, String file, long lastModified, List<CachedSymbol> generatedSymbols) {
public void update(SymbolCacheKey cacheKey, String file, long lastModified, List<CachedSymbol> generatedSymbols, Set<String> dependencies) {
}
@Override
@@ -38,4 +43,5 @@ public class SymbolCacheVoid implements SymbolCache {
public void removeFile(SymbolCacheKey symbolCacheKey, String file) {
}
}

View File

@@ -63,9 +63,9 @@ public class AdHocSpringPropertyIndexProvider implements ProjectBasedPropertyInd
"**/application.properties",
"**/application.yml"
), changed -> {
log.info("File changed: "+changed);
log.debug("File changed: {}", changed);
projectFinder.find(new TextDocumentIdentifier(changed)).ifPresent(project -> {
log.info("=> Project changed: "+project.getElementName());
log.debug("=> Project changed: {}", project.getElementName());
indexes.invalidate(project);
});
});

View File

@@ -4,3 +4,7 @@ languageserver.completion-trigger-characters.xml:
languageserver.completion-trigger-characters.spring-boot-properties-yaml: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.
languageserver.completion-trigger-characters.spring-boot-properties: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.
spring.main.banner-mode: off
#logging.level.org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava=debug
#logging.level.org.springframework.ide.vscode.boot.app.SpringSymbolIndex=debug
#logging.level.org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp=off

View File

@@ -0,0 +1,165 @@
package org.springframework.ide.vscode.boot.java.requestmapping.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.eclipse.lsp4j.Location;
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.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
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 org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness.CustomizableProjectContent;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@BootLanguageServerTest
@Import(SymbolProviderTestConf.class)
public class RequestMappingDependentConstantChangedTest {
@Autowired private BootLanguageServerHarness harness;
@Autowired private JavaProjectFinder projectFinder;
@Autowired private SpringSymbolIndex indexer;
ProjectsHarness projects = ProjectsHarness.INSTANCE;
private MavenJavaProject project;
private Path directory;
@Before
public void setup() throws Exception {
harness.intialize(null);
project = (MavenJavaProject) projects.mavenProject("test-request-mapping-symbols", false, new ProjectsHarness.ProjectCustomizer() {
@Override
public void customize(CustomizableProjectContent projectContent) throws Exception {
//dummy (forces every test to use a new copy of the project, we do this because project
//files are being mutated!
}
});
directory = new File(project.getLocationUri()).toPath();
// trigger project creation
projectFinder.find(new TextDocumentIdentifier(project.getLocationUri().toString())).get();
CompletableFuture<Void> initProject = indexer.waitOperation();
initProject.get(5, TimeUnit.SECONDS);
}
@Test
public void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Exception {
String docUri = directory.resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends SymbolInformation> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertSymbol(docUri, "@/path/from/constant", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
TestFileScanListener fileScanListener = new TestFileScanListener();
indexer.getJavaIndexer().setFileScanListener(fileScanListener);
replaceInFile(constantsUri, "path/from/constant", "/changed-path");
fileScanListener.assertScannedUris(constantsUri, docUri);
fileScanListener.assertScannedUri(constantsUri, 1);
fileScanListener.assertScannedUri(docUri, 1);
symbols = indexer.getSymbols(docUri);
assertSymbolCount(1, symbols);
assertSymbol(docUri, "@/changed-path", "@RequestMapping(Constants.REQUEST_MAPPING_PATH)");
}
@Test public void testCyclicalDependency() throws Exception {
//cyclical dependency between two files (ping refers pong and vice versa)
String pingUri = directory.resolve("src/main/java/org/test/PingConstantRequestMapping.java").toUri().toString();
String pongUri = directory.resolve("src/main/java/org/test/PongConstantRequestMapping.java").toUri().toString();
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pingUri);
for (SymbolInformation s : symbols) {
System.out.println(s.getName());
}
assertSymbolCount(1, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
replaceInFile(pingUri, "/ping", "/changed");
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pingUri);
assertSymbolCount(1, symbols);
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
}
{
List<? extends SymbolInformation> symbols = indexer.getSymbols(pongUri);
assertSymbolCount(1, symbols);
assertSymbol(pongUri, "@/changed -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
private void assertSymbolCount(int expectedCount, List<? extends SymbolInformation> symbols) {
if (symbols.size()!=expectedCount) {
StringBuilder found = new StringBuilder();
for (SymbolInformation s : symbols) {
found.append(s.getName());
found.append("\n");
}
fail("Expected "+expectedCount+" symbols but found "+symbols.size()+":\n"+found);
}
}
private void assertSymbol(String docUri, String name, String coveredText) throws Exception {
List<? extends SymbolInformation> symbols = indexer.getSymbols(docUri);
Optional<? extends SymbolInformation> maybeSymbol = symbols.stream().filter(s -> name.equals(s.getName())).findFirst();
assertTrue(maybeSymbol.isPresent());
TextDocument doc = new TextDocument(docUri, LanguageId.JAVA);
doc.setText(FileUtils.readFileToString(UriUtil.toFile(docUri)));
SymbolInformation symbol = maybeSymbol.get();
Location loc = symbol.getLocation();
assertEquals(docUri, loc.getUri());
int start = doc.toOffset(loc.getRange().getStart());
int end = doc.toOffset(loc.getRange().getEnd());
String actualCoveredText = doc.textBetween(start, end);
assertEquals(coveredText, actualCoveredText);
}
public void replaceInFile(String docUri, String find, String replace) throws Exception {
File target = UriUtil.toFile(docUri);
String oldContent = FileUtils.readFileToString(target, "UTF8");
assertTrue(oldContent.contains(find));
String newContent = oldContent.replace(find, replace);
FileUtils.write(target, newContent, "UTF8");
indexer.updateDocument(docUri, null, "triggered by test code").get();
}
}

View File

@@ -16,9 +16,12 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.Before;
@@ -29,11 +32,17 @@ import org.springframework.context.annotation.Import;
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava.DependencyTracker;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
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 org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
import org.springframework.test.context.junit4.SpringRunner;
import com.google.common.collect.ImmutableSet;
/**
* @author Martin Lippert
*/
@@ -73,9 +82,45 @@ public class RequestMappingSymbolProviderTest {
@Test
public void testSimpleRequestMappingSymbolFromConstantInDifferentClass() throws Exception {
String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClassWithConstantInDifferentClass.java").toUri().toString();
String constantsUri = directory.toPath().resolve("src/main/java/org/test/Constants.java").toUri().toString();
List<? extends SymbolInformation> symbols = indexer.getSymbols(docUri);
assertEquals(1, symbols.size());
assertTrue(containsSymbol(symbols, "@/path/from/constant", docUri, 6, 1, 6, 48));
//Verify whether dependency tracker logics works properly for this example.
DependencyTracker dt = indexer.getJavaIndexer().getDependencyTracker();
assertEquals(ImmutableSet.of("Lorg/test/Constants;"), dt.getAllDependencies().get(UriUtil.toFileString(docUri)));
TestFileScanListener fileScanListener = new TestFileScanListener();
indexer.getJavaIndexer().setFileScanListener(fileScanListener);
indexer.updateDocument(constantsUri, FileUtils.readFileToString(UriUtil.toFile(constantsUri)), "test triggered").get();
fileScanListener.assertScannedUris(constantsUri, docUri);
fileScanListener.assertScannedUri(constantsUri, 1);
fileScanListener.assertScannedUri(docUri, 1);
}
@Test
public void testCyclicalRequestMappingDependency() throws Exception {
//Cyclical dependency:
//file a => file b => file a
//This has the potential to cause infinite loop.
String pingUri = directory.toPath().resolve("src/main/java/org/test/PingConstantRequestMapping.java").toUri().toString();
String pongUri = directory.toPath().resolve("src/main/java/org/test/PongConstantRequestMapping.java").toUri().toString();
assertSymbol(pingUri, "@/pong -- GET", "@GetMapping(PongConstantRequestMapping.PONG)");
assertSymbol(pongUri, "@/ping -- GET", "@GetMapping(PingConstantRequestMapping.PING)");
TestFileScanListener fileScanListener = new TestFileScanListener();
indexer.getJavaIndexer().setFileScanListener(fileScanListener);
indexer.updateDocument(pingUri, null, "test triggered").get();
fileScanListener.assertScannedUris(pingUri, pongUri);
fileScanListener.reset();
fileScanListener.assertScannedUris(/*none*/);
indexer.updateDocument(pongUri, null, "test triggered").get();
fileScanListener.assertScannedUris(pingUri, pongUri);
}
@Test
@@ -190,4 +235,21 @@ public class RequestMappingSymbolProviderTest {
return false;
}
private void assertSymbol(String docUri, String name, String coveredText) throws Exception {
List<? extends SymbolInformation> symbols = indexer.getSymbols(docUri);
Optional<? extends SymbolInformation> maybeSymbol = symbols.stream().filter(s -> name.equals(s.getName())).findFirst();
assertTrue(maybeSymbol.isPresent());
TextDocument doc = new TextDocument(docUri, LanguageId.JAVA);
doc.setText(FileUtils.readFileToString(UriUtil.toFile(docUri)));
SymbolInformation symbol = maybeSymbol.get();
Location loc = symbol.getLocation();
assertEquals(docUri, loc.getUri());
int start = doc.toOffset(loc.getRange().getStart());
int end = doc.toOffset(loc.getRange().getEnd());
String actualCoveredText = doc.textBetween(start, end);
assertEquals(coveredText, actualCoveredText);
}
}

View File

@@ -0,0 +1,58 @@
package org.springframework.ide.vscode.boot.java.requestmapping.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
import org.springframework.ide.vscode.boot.java.utils.FileScanListener;
import org.springframework.ide.vscode.commons.util.UriUtil;
public class TestFileScanListener implements FileScanListener {
public final List<String> scannedFiles = new ArrayList<String>();
@Override
public void fileScanned(String path) {
scannedFiles.add(path);
}
public void assertScanned(String path) {
assertTrue(scannedFiles.contains(path));
}
public void assertScanned(String path, int scanCount) {
assertEquals(scanCount, scannedFiles.stream().filter(e -> e.equals(path)).count());
}
public void assertScannedUris(String... docUris) {
List<String> docPaths = new ArrayList<>();
for (String docUri : docUris) {
docPaths.add(UriUtil.toFileString(docUri));
}
Collections.sort(docPaths);
StringBuilder expected = new StringBuilder();
for (String string : docPaths) {
expected.append(string+"\n");
}
TreeSet<String> actualPaths = new TreeSet<>();
for (String string : scannedFiles) {
actualPaths.add(string);
}
StringBuilder actual = new StringBuilder();
for (String string : actualPaths) {
actual.append(string+"\n");
}
assertEquals(expected.toString(), actual.toString());
}
public void assertScannedUri(String docUri, int scanCount) {
assertScanned(UriUtil.toFileString(docUri), scanCount);
}
public void reset() {
scannedFiles.clear();
}
}

View File

@@ -161,7 +161,7 @@ public class SpringIndexerTest {
assertTrue(containsSymbol(indexer.getSymbols(changedDocURI), "@/mapping1", changedDocURI));
String newContent = FileUtils.readFileToString(new File(new URI(changedDocURI))).replace("mapping1", "mapping1-CHANGED");
CompletableFuture<Void> updateFuture = indexer.updateDocument(changedDocURI, newContent);
CompletableFuture<Void> updateFuture = indexer.updateDocument(changedDocURI, newContent, "test triggered");
updateFuture.get(5, TimeUnit.SECONDS);
// check for updated index per document

View File

@@ -22,8 +22,10 @@ import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
@@ -40,6 +42,12 @@ import org.springframework.ide.vscode.boot.java.utils.SymbolCacheKey;
import org.springframework.ide.vscode.boot.java.utils.SymbolCacheOnDisc;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
public class SymbolCacheOnDiscTest {
private Path tempDir;
@@ -58,7 +66,7 @@ public class SymbolCacheOnDiscTest {
@Test
public void testEmptyCache() throws Exception {
CachedSymbol[] result = cache.retrieve(new SymbolCacheKey("something", "0"), new String[0]);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("something", "0"), new String[0]);
assertNull(result);
}
@@ -80,9 +88,15 @@ public class SymbolCacheOnDiscTest {
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
generatedSymbols.add(new CachedSymbol("", timeFile1.toMillis(), enhancedSymbol));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols, ImmutableMultimap.of(
file1.toString(), "file1dep1",
file2.toString(), "file2dep1",
file2.toString(), "file2dep2"
));
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
CachedSymbol[] cachedSymbols = result.getLeft();
assertNotNull(cachedSymbols);
assertEquals(1, cachedSymbols.length);
@@ -90,6 +104,11 @@ public class SymbolCacheOnDiscTest {
assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind());
assertEquals(new Location("docURI", new Range(new Position(3, 10), new Position(3, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation());
assertNull(cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation());
Multimap<String, String> dependencies = result.getRight();
assertEquals(2, dependencies.keySet().size());
assertEquals(dependencies.get(file1.toString()), ImmutableSet.of("file1dep1"));
assertEquals(dependencies.get(file2.toString()), ImmutableSet.of("file2dep1", "file2dep2"));
}
@Test
@@ -110,10 +129,10 @@ public class SymbolCacheOnDiscTest {
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
generatedSymbols.add(new CachedSymbol("", timeFile1.toMillis(), enhancedSymbol));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols, null);
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("otherkey", "1"), files);
assertNull(cachedSymbols);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("otherkey", "1"), files);
assertNull(result);
}
@Test
@@ -134,12 +153,15 @@ public class SymbolCacheOnDiscTest {
EnhancedSymbolInformation enhancedSymbol = new EnhancedSymbolInformation(symbol, null);
generatedSymbols.add(new CachedSymbol("", timeFile1.toMillis(), enhancedSymbol));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols, ImmutableMultimap.of(
file1.toString(), "file1dep",
file2.toString(), "file2dep"
));
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 1000));
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
assertNull(cachedSymbols);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
assertNull(result);
}
@Test
@@ -153,11 +175,10 @@ public class SymbolCacheOnDiscTest {
Files.createFile(file3);
String[] files = {file1.toString(), file2.toString()};
cache.store(new SymbolCacheKey("somekey", "1"), files, new ArrayList<>());
cache.store(new SymbolCacheKey("somekey", "1"), files, new ArrayList<>(), null);
String[] moreFiles = {file1.toString(), file2.toString(), file3.toString()};
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), moreFiles);
assertNull(cachedSymbols);
assertNull(cache.retrieve(new SymbolCacheKey("somekey", "1"), moreFiles));
}
@Test
@@ -171,21 +192,20 @@ public class SymbolCacheOnDiscTest {
Files.createFile(file3);
String[] files = {file1.toString(), file2.toString(), file3.toString()};
cache.store(new SymbolCacheKey("somekey", "1"), files, new ArrayList<>());
cache.store(new SymbolCacheKey("somekey", "1"), files, new ArrayList<>(), null);
String[] fewerFiles = {file1.toString(), file2.toString()};
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), fewerFiles);
assertNull(cachedSymbols);
assertNull(cache.retrieve(new SymbolCacheKey("somekey", "1"), fewerFiles));
}
@Test
public void testDeleteOldCacheFileIfNewOneIsStored() throws Exception {
SymbolCacheKey key1 = new SymbolCacheKey("somekey", "1");
cache.store(key1, new String[0], new ArrayList<>());
cache.store(key1, new String[0], new ArrayList<>(), null);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + ".json"))));
SymbolCacheKey key2 = new SymbolCacheKey("somekey", "2");
cache.store(key2, new String[0], new ArrayList<>());
cache.store(key2, new String[0], new ArrayList<>(), null);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key2.toString() + ".json"))));
assertFalse(Files.exists(tempDir.resolve(Paths.get(key1.toString() + ".json"))));
}
@@ -207,9 +227,9 @@ public class SymbolCacheOnDiscTest {
generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols, null);
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
CachedSymbol[] cachedSymbols = cache.retrieveSymbols(new SymbolCacheKey("somekey", "1"), files);
assertNotNull(cachedSymbols);
assertEquals(1, cachedSymbols.length);
@@ -244,7 +264,7 @@ public class SymbolCacheOnDiscTest {
EnhancedSymbolInformation enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null);
generatedSymbols1.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1, null);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
symbol1 = new SymbolInformation("symbol1", SymbolKind.Field, new Location(doc1URI, new Range(new Position(3, 10), new Position(3, 20))));
@@ -257,13 +277,36 @@ public class SymbolCacheOnDiscTest {
generatedSymbols2.add(new CachedSymbol(doc1URI, timeFile1.toMillis() + 2000, enhancedSymbol2));
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
cache.update(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2);
cache.update(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2, null);
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
CachedSymbol[] cachedSymbols = cache.retrieveSymbols(new SymbolCacheKey("somekey", "1"), files);
assertNotNull(cachedSymbols);
assertEquals(2, cachedSymbols.length);
}
@Test
public void testDependencyAddedToExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toAbsolutePath().toString()};
List<CachedSymbol> generatedSymbols = ImmutableList.of();
Multimap<String, String> dependencies = ImmutableMultimap.of(file1.toString(), "dep1");
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols, dependencies);
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
Set<String> dependencies2 = ImmutableSet.of("dep1", "dep2");
cache.update(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols, dependencies2);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
assertNotNull(result);
assertEquals(ImmutableSet.of("dep1", "dep2"), result.getRight().get(file1.toString()));
}
@Test
public void testSymbolRemovedFromExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
@@ -280,18 +323,47 @@ public class SymbolCacheOnDiscTest {
EnhancedSymbolInformation enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null);
generatedSymbols1.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1, null);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
cache.update(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2);
cache.update(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2, null);
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
CachedSymbol[] cachedSymbols = cache.retrieveSymbols(new SymbolCacheKey("somekey", "1"), files);
assertNotNull(cachedSymbols);
assertEquals(0, cachedSymbols.length);
}
@Test
public void testDependencyRemovedFromExistingFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Files.createFile(file1);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
String[] files = {file1.toAbsolutePath().toString()};
List<CachedSymbol> generatedSymbols1 = ImmutableList.of();
ImmutableMultimap<String, String> dependencies1 = ImmutableMultimap.of(
file1.toString(), "dep1",
file1.toString(), "dep2"
);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1, dependencies1);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
assertTrue(file1.toFile().setLastModified(timeFile1.toMillis() + 2000));
Set<String> dependencies2 = ImmutableSet.of("dep2");
cache.update(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString(), timeFile1.toMillis() + 2000, generatedSymbols2, dependencies2);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
assertNotNull(result);
assertEquals(ImmutableSet.of("dep2"), result.getRight().get(file1.toString()));
}
@Test
public void testSymbolAddedToNewFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
@@ -312,7 +384,7 @@ public class SymbolCacheOnDiscTest {
EnhancedSymbolInformation enhancedSymbol1 = new EnhancedSymbolInformation(symbol1, null);
generatedSymbols1.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1, null);
List<CachedSymbol> generatedSymbols2 = new ArrayList<>();
SymbolInformation symbol2 = new SymbolInformation("symbol2", SymbolKind.Interface, new Location(doc2URI, new Range(new Position(5, 5), new Position(5, 10))));
@@ -320,17 +392,47 @@ public class SymbolCacheOnDiscTest {
generatedSymbols2.add(new CachedSymbol(doc2URI, timeFile2.toMillis(), enhancedSymbol2));
cache.update(new SymbolCacheKey("somekey", "1"), file2.toString(), timeFile2.toMillis(), generatedSymbols2);
cache.update(new SymbolCacheKey("somekey", "1"), file2.toString(), timeFile2.toMillis(), generatedSymbols2, null);
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), new String[] {file1.toString(), file2.toString()});
CachedSymbol[] cachedSymbols = cache.retrieveSymbols(new SymbolCacheKey("somekey", "1"), new String[] {file1.toString(), file2.toString()});
assertNotNull(cachedSymbols);
assertEquals(2, cachedSymbols.length);
}
@Test
public void testDependencyAddedToNewFile() throws Exception {
Path file1 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile1");
Path file2 = Paths.get(tempDir.toAbsolutePath().toString(), "tempFile2");
Files.createFile(file1);
Files.createFile(file2);
FileTime timeFile1 = Files.getLastModifiedTime(file1);
FileTime timeFile2 = Files.getLastModifiedTime(file2);
String[] files = {file1.toString()};
String doc1URI = UriUtil.toUri(file1.toFile()).toString();
String doc2URI = UriUtil.toUri(file2.toFile()).toString();
List<CachedSymbol> generatedSymbols1 = ImmutableList.of();
Multimap<String, String> dependencies1 = ImmutableMultimap.of(
file1.toString(), "dep1"
);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols1, dependencies1);
Set<String> dependencies2 = ImmutableSet.of("dep2");
cache.update(new SymbolCacheKey("somekey", "1"), file2.toString(), timeFile2.toMillis(), generatedSymbols1, dependencies2);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("somekey", "1"), new String[] {file1.toString(), file2.toString()});
assertNotNull(result);
assertEquals(ImmutableSet.of("dep2"), result.getRight().get(file2.toString()));
assertEquals(ImmutableSet.of("dep1"), result.getRight().get(file1.toString()));
}
@Test
public void testProjectDeleted() throws Exception {
SymbolCacheKey key1 = new SymbolCacheKey("somekey", "1");
cache.store(key1, new String[0], new ArrayList<>());
cache.store(key1, new String[0], new ArrayList<>(), null);
assertTrue(Files.exists(tempDir.resolve(Paths.get(key1.toString() + ".json"))));
cache.remove(key1);
@@ -363,18 +465,26 @@ public class SymbolCacheOnDiscTest {
generatedSymbols.add(new CachedSymbol(doc1URI, timeFile1.toMillis(), enhancedSymbol1));
generatedSymbols.add(new CachedSymbol(doc2URI, timeFile2.toMillis(), enhancedSymbol2));
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols);
Multimap<String, String> dependencies = ImmutableMultimap.of(
file1.toString(), "dep1",
file2.toString(), "dep2"
);
cache.store(new SymbolCacheKey("somekey", "1"), files, generatedSymbols, dependencies);
cache.removeFile(new SymbolCacheKey("somekey", "1"), file1.toAbsolutePath().toString());
files = new String[] {file2.toAbsolutePath().toString()};
CachedSymbol[] cachedSymbols = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
assertNotNull(cachedSymbols);
Pair<CachedSymbol[], Multimap<String, String>> result = cache.retrieve(new SymbolCacheKey("somekey", "1"), files);
CachedSymbol[] cachedSymbols = result.getLeft();
assertNotNull(result);
assertEquals(1, cachedSymbols.length);
assertEquals("symbol2", cachedSymbols[0].getEnhancedSymbol().getSymbol().getName());
assertEquals(SymbolKind.Field, cachedSymbols[0].getEnhancedSymbol().getSymbol().getKind());
assertEquals(new Location(doc2URI, new Range(new Position(5, 10), new Position(5, 20))), cachedSymbols[0].getEnhancedSymbol().getSymbol().getLocation());
assertNull(cachedSymbols[0].getEnhancedSymbol().getAdditionalInformation());
Multimap<String, String> cachedDependencies = result.getRight();
assertEquals(ImmutableSet.of(), cachedDependencies.get(file1.toString()));
assertEquals(ImmutableSet.of("dep2"), cachedDependencies.get(file2.toString()));
}
}

View File

@@ -10,6 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.project.harness;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
@@ -73,7 +75,7 @@ public class ProjectsHarness {
File target = new File(projectRoot, path);
IOUtil.pipe(new ByteArrayInputStream(content.getBytes("UTF8")), target);
}
public void createType(String fqName, String sourceCode) throws Exception {
String sourceFile = sourceFolder()+"/"+fqName.replace('.', '/')+".java";
createFile(sourceFile, sourceCode);
@@ -98,7 +100,7 @@ public class ProjectsHarness {
this.fileObserver = fileObserver;
}
public IJavaProject project(ProjectType type, String name, ProjectCustomizer customizer, boolean build) throws Exception {
public IJavaProject project(ProjectType type, String name, boolean build, ProjectCustomizer customizer) throws Exception {
Tuple3<ProjectType, String, ProjectCustomizer> key = Tuples.of(type, name, customizer);
return cache.get(key, () -> {
Path baseProjectPath = getProjectPath(name);
@@ -122,7 +124,7 @@ public class ProjectsHarness {
}
}
public IJavaProject project(ProjectType type, String name, boolean build) throws Exception {
private IJavaProject project(ProjectType type, String name, boolean build) throws Exception {
return cache.get(type + "/" + name, () -> {
Path testProjectPath = getProjectPath(name);
return createProject(type, testProjectPath, build);
@@ -138,8 +140,12 @@ public class ProjectsHarness {
return Paths.get(resource);
}
public MavenJavaProject mavenProject(String name, boolean build, ProjectCustomizer customizer) throws Exception {
return (MavenJavaProject) project(ProjectType.MAVEN, name, build, customizer);
}
public MavenJavaProject mavenProject(String name, ProjectCustomizer customizer) throws Exception {
return (MavenJavaProject) project(ProjectType.MAVEN, name, customizer, true);
return (MavenJavaProject) project(ProjectType.MAVEN, name, true, customizer);
}
public MavenJavaProject mavenProject(String name) throws Exception {

View File

@@ -0,0 +1,15 @@
package org.test;
import org.springframework.web.bind.annotation.GetMapping;
public class PingConstantRequestMapping {
public final static String PING = "/ping";
@GetMapping(PongConstantRequestMapping.PONG)
public String hello() {
return "oy?";
}
}

View File

@@ -0,0 +1,14 @@
package org.test;
import org.springframework.web.bind.annotation.GetMapping;
public class PongConstantRequestMapping {
public static final String PONG="/pong";
@GetMapping(PingConstantRequestMapping.PING)
public String hello() {
return "oy?";
}
}