Merge pull request #306 from spring-projects/symbol-dependencies
Track symbol dependencies ...
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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?";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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?";
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user