Completions inside of @Value(...) context now also suggest 'ad-hoc' properties that are defined in application.properties or application.yml (in addition to regular properties defined via @ConfigurationProperties).
This commit is contained in:
@@ -22,6 +22,7 @@ import org.eclipse.lsp4j.Unregistration;
|
||||
import org.eclipse.lsp4j.UnregistrationParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.json.DidChangeWatchedFilesRegistrationOptions;
|
||||
import org.springframework.ide.vscode.commons.languageserver.json.FileSystemWatcher;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BasicFileObserver;
|
||||
|
||||
/**
|
||||
@@ -62,18 +63,22 @@ public class SimpleServerFileObserver extends BasicFileObserver {
|
||||
}
|
||||
|
||||
private void subscribe(String subscriptionId, List<String> globPattern, int kind) {
|
||||
if (server.canRegisterFileWatchersDynamically()) {
|
||||
List<FileSystemWatcher> watchers = globPattern.stream().map(pattern -> new FileSystemWatcher(pattern, kind)).collect(Collectors.toList());
|
||||
Registration registration = new Registration(subscriptionId, WORKSPACE_DID_CHANGE_WATCHED_FILES, new DidChangeWatchedFilesRegistrationOptions(watchers));
|
||||
server.getClient().registerCapability(new RegistrationParams(Arrays.asList(registration)));
|
||||
}
|
||||
server.onInitialized(() -> {
|
||||
if (server.canRegisterFileWatchersDynamically()) {
|
||||
List<FileSystemWatcher> watchers = globPattern.stream().map(pattern -> new FileSystemWatcher(pattern, kind)).collect(Collectors.toList());
|
||||
Registration registration = new Registration(subscriptionId, WORKSPACE_DID_CHANGE_WATCHED_FILES, new DidChangeWatchedFilesRegistrationOptions(watchers));
|
||||
server.getClient().registerCapability(new RegistrationParams(Arrays.asList(registration)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unsubscribe(String subscriptionId) {
|
||||
if (server.canRegisterFileWatchersDynamically()) {
|
||||
server.getClient().unregisterCapability(new UnregistrationParams(Arrays.asList(new Unregistration(subscriptionId, WORKSPACE_DID_CHANGE_WATCHED_FILES))));
|
||||
}
|
||||
server.onInitialized(() -> {
|
||||
if (server.canRegisterFileWatchersDynamically()) {
|
||||
server.getClient().unregisterCapability(new UnregistrationParams(Arrays.asList(new Unregistration(subscriptionId, WORKSPACE_DID_CHANGE_WATCHED_FILES))));
|
||||
}
|
||||
});
|
||||
return super.unsubscribe(subscriptionId);
|
||||
}
|
||||
|
||||
|
||||
@@ -97,18 +97,4 @@ public class BasicFileObserver implements FileObserver {
|
||||
.forEach(pair -> pair.right.accept(uri));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Disposable onAnyChange(List<String> globPattern, Consumer<String> handler) {
|
||||
String[] ids = {
|
||||
onFileChanged(globPattern, handler),
|
||||
onFileCreated(globPattern, handler),
|
||||
onFileDeleted(globPattern, handler)
|
||||
};
|
||||
return () -> {
|
||||
for (String id : ids) {
|
||||
unsubscribe(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.util;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@@ -31,6 +32,17 @@ public interface FileObserver {
|
||||
|
||||
boolean unsubscribe(String subscriptionId);
|
||||
|
||||
Disposable onAnyChange(List<String> globPattern, Consumer<String> handler);
|
||||
default Disposable onAnyChange(List<String> globPattern, Consumer<String> handler) {
|
||||
String[] ids = {
|
||||
onFileChanged(globPattern, handler),
|
||||
onFileCreated(globPattern, handler),
|
||||
onFileDeleted(globPattern, handler)
|
||||
};
|
||||
return () -> {
|
||||
for (String id : ids) {
|
||||
unsubscribe(id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,11 @@
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" path="src/main/resources"/>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding//src/main/resources=UTF-8
|
||||
encoding//src/test/java=UTF-8
|
||||
encoding//src/test/resources=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
|
||||
@@ -21,7 +21,9 @@ import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsService;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JavaProjectsServiceWithFallback;
|
||||
import org.springframework.ide.vscode.boot.jdt.ls.JdtLsProjectCache;
|
||||
import org.springframework.ide.vscode.boot.metadata.AdHocSpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtil;
|
||||
import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider;
|
||||
@@ -58,6 +60,7 @@ public class BootLanguageServerParams {
|
||||
public final JavaProjectFinder projectFinder;
|
||||
public final ProjectObserver projectObserver;
|
||||
public final SpringPropertyIndexProvider indexProvider;
|
||||
public final SpringPropertyIndexProvider adHocIndexProvider;
|
||||
|
||||
//Boot Properies
|
||||
public final TypeUtilProvider typeUtilProvider;
|
||||
@@ -70,6 +73,7 @@ public class BootLanguageServerParams {
|
||||
JavaProjectFinder projectFinder,
|
||||
ProjectObserver projectObserver,
|
||||
SpringPropertyIndexProvider indexProvider,
|
||||
SpringPropertyIndexProvider adHocIndexProvider,
|
||||
TypeUtilProvider typeUtilProvider,
|
||||
RunningAppProvider runningAppProvider,
|
||||
Duration watchDogInterval
|
||||
@@ -79,6 +83,7 @@ public class BootLanguageServerParams {
|
||||
this.projectFinder = projectFinder;
|
||||
this.projectObserver = projectObserver;
|
||||
this.indexProvider = indexProvider;
|
||||
this.adHocIndexProvider = adHocIndexProvider;
|
||||
this.typeUtilProvider = typeUtilProvider;
|
||||
this.runningAppProvider = runningAppProvider;
|
||||
this.watchDogInterval = watchDogInterval;
|
||||
@@ -93,12 +98,14 @@ public class BootLanguageServerParams {
|
||||
() -> createFallbackProjectCache(server)
|
||||
);
|
||||
DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(jdtProjectCache, jdtProjectCache);
|
||||
SpringPropertyIndexProvider adHocProvider = new AdHocSpringPropertyIndexProvider(jdtProjectCache, jdtProjectCache, server.getWorkspaceService().getFileObserver());
|
||||
indexProvider.setProgressService(server.getProgressService());
|
||||
|
||||
return new BootLanguageServerParams(
|
||||
jdtProjectCache.filter(BootProjectUtil::isBootProject),
|
||||
jdtProjectCache,
|
||||
indexProvider,
|
||||
adHocProvider,
|
||||
(IDocument doc) -> new TypeUtil(jdtProjectCache.find(new TextDocumentIdentifier(doc.getUri()))),
|
||||
RunningAppProvider.createDefault(server),
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
@@ -161,6 +168,7 @@ public class BootLanguageServerParams {
|
||||
javaProjectFinder.filter(BootProjectUtil::isBootProject),
|
||||
projectObserver,
|
||||
indexProvider,
|
||||
(doc) -> SpringPropertyIndex.EMPTY_INDEX,
|
||||
typeUtilProvider,
|
||||
RunningAppProvider.NULL,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
@@ -189,6 +197,7 @@ public class BootLanguageServerParams {
|
||||
javaProjectFinder.filter(BootProjectUtil::isBootProject),
|
||||
projectObserver,
|
||||
indexProvider,
|
||||
(doc) -> SpringPropertyIndex.EMPTY_INDEX,
|
||||
(IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))),
|
||||
RunningAppProvider.NULL,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
|
||||
@@ -95,6 +95,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private final BootLanguageServerParams serverParams;
|
||||
private final SpringIndexer indexer;
|
||||
private final SpringPropertyIndexProvider propertyIndexProvider;
|
||||
private final SpringPropertyIndexProvider adHocPropertyIndexProvider;
|
||||
private final SpringLiveHoverWatchdog liveHoverWatchdog;
|
||||
private final SpringLiveChangeDetectionWatchdog liveChangeDetectionWatchdog;
|
||||
private final ProjectObserver projectObserver;
|
||||
@@ -117,6 +118,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
cuCache = new CompilationUnitCache(projectFinder, server.getTextDocumentService(), projectObserver);
|
||||
|
||||
propertyIndexProvider = serverParams.indexProvider;
|
||||
adHocPropertyIndexProvider = serverParams.adHocIndexProvider;
|
||||
|
||||
SimpleWorkspaceService workspaceService = server.getWorkspaceService();
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
@@ -205,7 +207,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
|
||||
@Override
|
||||
public ICompletionEngine getCompletionEngine() {
|
||||
return createCompletionEngine(projectFinder, propertyIndexProvider);
|
||||
return createCompletionEngine(projectFinder, propertyIndexProvider, adHocPropertyIndexProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -244,13 +246,16 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
this.cuCache.dispose();
|
||||
}
|
||||
|
||||
protected ICompletionEngine createCompletionEngine(JavaProjectFinder javaProjectFinder,
|
||||
SpringPropertyIndexProvider indexProvider) {
|
||||
protected ICompletionEngine createCompletionEngine(
|
||||
JavaProjectFinder javaProjectFinder,
|
||||
SpringPropertyIndexProvider indexProvider,
|
||||
SpringPropertyIndexProvider adHocIndexProvider
|
||||
) {
|
||||
Map<String, CompletionProvider> providers = new HashMap<>();
|
||||
providers.put(org.springframework.ide.vscode.boot.java.scope.Constants.SPRING_SCOPE,
|
||||
new ScopeCompletionProcessor());
|
||||
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE,
|
||||
new ValueCompletionProcessor(indexProvider));
|
||||
new ValueCompletionProcessor(indexProvider, adHocIndexProvider));
|
||||
|
||||
JavaSnippetManager snippetManager = new JavaSnippetManager(server::createSnippetBuilder);
|
||||
snippetManager.add(
|
||||
|
||||
@@ -14,7 +14,9 @@ import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseTo
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
@@ -22,7 +24,6 @@ import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.MemberValuePair;
|
||||
import org.eclipse.jdt.core.dom.SimpleName;
|
||||
import org.eclipse.jdt.core.dom.StringLiteral;
|
||||
import org.springframework.ide.vscode.boot.configurationmetadata.ConfigurationMetadataProperty;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
@@ -39,9 +40,11 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
public class ValueCompletionProcessor implements CompletionProvider {
|
||||
|
||||
private final SpringPropertyIndexProvider indexProvider;
|
||||
private SpringPropertyIndexProvider adHocIndexProvider;
|
||||
|
||||
public ValueCompletionProcessor(SpringPropertyIndexProvider indexProvider) {
|
||||
public ValueCompletionProcessor(SpringPropertyIndexProvider indexProvider, SpringPropertyIndexProvider adHocIndexProvider) {
|
||||
this.indexProvider = indexProvider;
|
||||
this.adHocIndexProvider = adHocIndexProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -51,41 +54,39 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
List<ICompletionProposal> result = new ArrayList<>();
|
||||
|
||||
try {
|
||||
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc);
|
||||
|
||||
// case: @Value(<*>)
|
||||
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
|
||||
List<Match<PropertyInfo>> matches = findMatches("", index);
|
||||
List<Match<PropertyInfo>> matches = findMatches("", doc);
|
||||
|
||||
for (Match<PropertyInfo> match : matches) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(offset, offset, "\"${" + match.data.getId() + "}\"");
|
||||
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null);
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match);
|
||||
result.add(proposal);
|
||||
}
|
||||
}
|
||||
// case: @Value(prefix<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof Annotation) {
|
||||
computeProposalsForSimpleName(node, result, offset, doc, index);
|
||||
computeProposalsForSimpleName(node, result, offset, doc);
|
||||
}
|
||||
// case: @Value(value=<*>)
|
||||
else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
computeProposalsForSimpleName(node, result, offset, doc, index);
|
||||
computeProposalsForSimpleName(node, result, offset, doc);
|
||||
}
|
||||
// case: @Value("prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(node, result, offset, doc, index);
|
||||
computeProposalsForStringLiteral(node, result, offset, doc);
|
||||
}
|
||||
}
|
||||
// case: @Value(value="prefix<*>")
|
||||
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
|
||||
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
|
||||
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
|
||||
computeProposalsForStringLiteral(node, result, offset, doc, index);
|
||||
computeProposalsForStringLiteral(node, result, offset, doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +98,7 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
}
|
||||
|
||||
private void computeProposalsForSimpleName(ASTNode node, List<ICompletionProposal> completions, int offset,
|
||||
IDocument doc, FuzzyMap<PropertyInfo> index) {
|
||||
IDocument doc) {
|
||||
String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition());
|
||||
|
||||
int startOffset = node.getStartPosition();
|
||||
@@ -106,20 +107,20 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
String proposalPrefix = "\"";
|
||||
String proposalPostfix = "\"";
|
||||
|
||||
List<Match<PropertyInfo>> matches = findMatches(prefix, index);
|
||||
List<Match<PropertyInfo>> matches = findMatches(prefix, doc);
|
||||
|
||||
for (Match<PropertyInfo> match : matches) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(startOffset, endOffset, proposalPrefix + "${" + match.data.getId() + "}" + proposalPostfix);
|
||||
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null);
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match);
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
|
||||
private void computeProposalsForStringLiteral(ASTNode node, List<ICompletionProposal> completions, int offset,
|
||||
IDocument doc, FuzzyMap<PropertyInfo> index) throws BadLocationException {
|
||||
IDocument doc) throws BadLocationException {
|
||||
String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1));
|
||||
|
||||
int startOffset = offset - prefix.length();
|
||||
@@ -141,14 +142,14 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
String fullNodeContent = doc.get(node.getStartPosition(), node.getLength());
|
||||
String postCompletion = isClosingBracketMissing(fullNodeContent + preCompletion) ? "}" : "";
|
||||
|
||||
List<Match<PropertyInfo>> matches = findMatches(prefix, index);
|
||||
List<Match<PropertyInfo>> matches = findMatches(prefix, doc);
|
||||
|
||||
for (Match<PropertyInfo> match : matches) {
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
edits.replace(startOffset, endOffset, preCompletion + match.data.getId() + postCompletion);
|
||||
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match.data.getId(), match.data.getName(), null);
|
||||
ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match);
|
||||
completions.add(proposal);
|
||||
}
|
||||
}
|
||||
@@ -184,8 +185,23 @@ public class ValueCompletionProcessor implements CompletionProvider {
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Match<PropertyInfo>> findMatches(String prefix, FuzzyMap<PropertyInfo> index) {
|
||||
List<Match<PropertyInfo>> matches = index.find(camelCaseToHyphens(prefix));
|
||||
private List<Match<PropertyInfo>> findMatches(String prefix, IDocument doc) {
|
||||
FuzzyMap<PropertyInfo> index = indexProvider.getIndex(doc);
|
||||
List<Match<PropertyInfo>> matches =index.find(camelCaseToHyphens(prefix));
|
||||
|
||||
//First the 'real' properties.
|
||||
Set<String> suggestedKeys = new HashSet<>();
|
||||
for (Match<PropertyInfo> m : matches) {
|
||||
suggestedKeys.add(m.data.getId());
|
||||
}
|
||||
|
||||
//Then also add 'ad-hoc' properties (see https://www.pivotaltracker.com/story/show/153107266).
|
||||
index = adHocIndexProvider.getIndex(doc);
|
||||
for (Match<PropertyInfo> m : index.find(prefix)) {
|
||||
if (suggestedKeys.add(m.data.getId())) {
|
||||
matches.add(m);
|
||||
}
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,25 +11,36 @@
|
||||
package org.springframework.ide.vscode.boot.java.value;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.boot.common.InformationTemplates;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ScoreableProposal;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap.Match;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.Renderables;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class ValuePropertyKeyProposal implements ICompletionProposal {
|
||||
public class ValuePropertyKeyProposal extends ScoreableProposal {
|
||||
|
||||
private DocumentEdits edits;
|
||||
private String label;
|
||||
private String detail;
|
||||
private Renderable documentation;
|
||||
private double score;
|
||||
|
||||
public ValuePropertyKeyProposal(DocumentEdits edits, String label, String detail, Renderable documentation) {
|
||||
private ValuePropertyKeyProposal(DocumentEdits edits, String label, String detail, double score, Renderable documentation) {
|
||||
this.edits = edits;
|
||||
this.label = label;
|
||||
this.detail = detail;
|
||||
this.documentation = documentation;
|
||||
this.score = score;
|
||||
}
|
||||
|
||||
public ValuePropertyKeyProposal(DocumentEdits edits, Match<PropertyInfo> match) {
|
||||
this(edits, match.data.getId(), match.data.getType(), match.score, InformationTemplates.createCompletionDocumentation(match.data));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -57,4 +68,9 @@ public class ValuePropertyKeyProposal implements ICompletionProposal {
|
||||
return this.documentation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getBaseScore() {
|
||||
return score;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package org.springframework.ide.vscode.boot.metadata;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.util.FileObserver;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.nodes.MappingNode;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
public class AdHocSpringPropertyIndexProvider implements SpringPropertyIndexProvider {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AdHocSpringPropertyIndexProvider.class);
|
||||
|
||||
private static class SimplePropertyIndex extends FuzzyMap<PropertyInfo> {
|
||||
@Override
|
||||
protected String getKey(PropertyInfo entry) {
|
||||
return entry.getId();
|
||||
}
|
||||
}
|
||||
|
||||
private Cache<IJavaProject, SimplePropertyIndex> indexes;
|
||||
final private JavaProjectFinder projectFinder;
|
||||
|
||||
public AdHocSpringPropertyIndexProvider(JavaProjectFinder projectFinder, ProjectObserver projectObserver, FileObserver fileObserver) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.indexes = CacheBuilder.newBuilder().build();
|
||||
if (projectObserver != null) {
|
||||
projectObserver.addListener(ProjectObserver.onAny(project -> indexes.invalidate(project)));
|
||||
}
|
||||
if (fileObserver!=null) {
|
||||
fileObserver.onAnyChange(ImmutableList.of(
|
||||
"**/application.properties",
|
||||
"**/application.yml"
|
||||
), changed -> {
|
||||
log.info("File changed: "+changed);
|
||||
projectFinder.find(new TextDocumentIdentifier(changed)).ifPresent(project -> {
|
||||
log.info("=> Project changed: "+project.getElementName());
|
||||
indexes.invalidate(project);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public FuzzyMap<PropertyInfo> getIndex(IDocument doc) {
|
||||
Optional<IJavaProject> jp = projectFinder.find(new TextDocumentIdentifier(doc.getUri()));
|
||||
if (jp.isPresent()) {
|
||||
return getIndex(jp.get());
|
||||
}
|
||||
return SpringPropertyIndex.EMPTY_INDEX;
|
||||
}
|
||||
|
||||
|
||||
private FuzzyMap<PropertyInfo> getIndex(IJavaProject jp) {
|
||||
try {
|
||||
return indexes.get(jp, () -> {
|
||||
SimplePropertyIndex index = new SimplePropertyIndex();
|
||||
IClasspathUtil.getSourceFolders(jp.getClasspath()).forEach(sourceFolder -> {
|
||||
processFile(this::parseProperties, new File(sourceFolder, "application.properties"), index);
|
||||
processFile(this::parseYaml, new File(sourceFolder, "application.yml"), index);
|
||||
});
|
||||
return index;
|
||||
});
|
||||
} catch (ExecutionException e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void processFile(Function<File, Properties> parserFunction, File file, SimplePropertyIndex index) {
|
||||
Properties props = parserFunction.apply(file);
|
||||
if (props!=null) {
|
||||
for (Object p : props.keySet()) {
|
||||
if (p instanceof String) {
|
||||
index.add(new PropertyInfo((String)p));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Properties parseProperties(File propsFile) {
|
||||
try {
|
||||
if (propsFile.isFile()) {
|
||||
Properties props = new Properties();
|
||||
try (InputStream reader = new FileInputStream(propsFile)) {
|
||||
props.load(reader);
|
||||
}
|
||||
return props;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//ignore failed attempt to read bad file
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Properties parseYaml(File yamlFile) {
|
||||
if (yamlFile.isFile()) {
|
||||
Yaml yaml = new Yaml();
|
||||
try (Reader reader = new InputStreamReader(new FileInputStream(yamlFile), "UTF8")) {
|
||||
Properties props = new Properties();
|
||||
for (Node node : yaml.composeAll(reader)) {
|
||||
flattenProperties("", node, props);
|
||||
}
|
||||
return props;
|
||||
} catch (Exception e ) {
|
||||
//ignore failed attempt to read bad file
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private void flattenProperties(String prefix, Node node, Properties props) {
|
||||
switch (node.getNodeId()) {
|
||||
case mapping:
|
||||
if (!prefix.isEmpty()) {
|
||||
prefix = prefix +".";
|
||||
}
|
||||
MappingNode mapping = (MappingNode)node;
|
||||
for (NodeTuple tup : mapping.getValue()) {
|
||||
String key = NodeUtil.asScalar(tup.getKeyNode());
|
||||
if (key!=null) {
|
||||
flattenProperties(prefix+key, tup.getValueNode(), props);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case scalar:
|
||||
//End of the line.
|
||||
props.put(prefix, NodeUtil.asScalar(node));
|
||||
break;
|
||||
default:
|
||||
//Ignore other cases, might implement later if it makes sense.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,8 +23,6 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexProvider {
|
||||
|
||||
private static final FuzzyMap<PropertyInfo> EMPTY_INDEX = new SpringPropertyIndex(null, null);
|
||||
|
||||
private JavaProjectFinder javaProjectFinder;
|
||||
private SpringPropertiesIndexManager indexManager;
|
||||
|
||||
@@ -41,7 +39,7 @@ public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexPr
|
||||
if (jp.isPresent()) {
|
||||
return indexManager.get(jp.get(), progressService);
|
||||
}
|
||||
return EMPTY_INDEX;
|
||||
return SpringPropertyIndex.EMPTY_INDEX;
|
||||
}
|
||||
|
||||
public void setProgressService(ProgressService progressService) {
|
||||
|
||||
@@ -117,6 +117,9 @@ public class PropertyInfo {
|
||||
}
|
||||
}
|
||||
}
|
||||
public PropertyInfo(String p) {
|
||||
this(p, null, null, null, null, null, null, null, null, null, null);
|
||||
}
|
||||
private void handleAs(Object targetObject) {
|
||||
// debug("handle-as "+this.getId()+" -> "+targetObject);
|
||||
if (targetObject instanceof String) {
|
||||
@@ -165,7 +168,7 @@ public class PropertyInfo {
|
||||
private HintProvider valueHints(TypeUtil typeUtil) {
|
||||
return HintProviders.basic(typeUtil.getJavaProject(), valueHints, valueProvider);
|
||||
}
|
||||
|
||||
|
||||
public List<PropertySource> getSources() {
|
||||
if (sources!=null) {
|
||||
return sources;
|
||||
|
||||
@@ -11,16 +11,19 @@
|
||||
package org.springframework.ide.vscode.boot.metadata;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.ide.vscode.boot.metadata.util.Listener;
|
||||
import org.springframework.ide.vscode.boot.metadata.util.ListenerManager;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.util.FileObserver;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
/**
|
||||
* Support for Reconciling, Content Assist and Hover Text in spring properties
|
||||
@@ -38,28 +41,11 @@ public class SpringPropertiesIndexManager extends ListenerManager<Listener<Sprin
|
||||
|
||||
public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders, ProjectObserver projectObserver) {
|
||||
this.valueProviders = valueProviders;
|
||||
this.indexes = CacheBuilder.newBuilder().build();
|
||||
this.indexes = CacheBuilder.newBuilder()
|
||||
.build();
|
||||
if (projectObserver != null) {
|
||||
projectObserver.addListener(new ProjectObserver.Listener() {
|
||||
|
||||
@Override
|
||||
public void created(IJavaProject project) {
|
||||
indexes.invalidate(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changed(IJavaProject project) {
|
||||
indexes.invalidate(project);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleted(IJavaProject project) {
|
||||
indexes.invalidate(project);
|
||||
}
|
||||
|
||||
});
|
||||
projectObserver.addListener(ProjectObserver.onAny(project -> indexes.invalidate(project)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public synchronized SpringPropertyIndex get(IJavaProject project, ProgressService progressService) {
|
||||
@@ -70,7 +56,7 @@ public class SpringPropertiesIndexManager extends ListenerManager<Listener<Sprin
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private SpringPropertyIndex initIndex(IJavaProject project, ProgressService progressService) {
|
||||
Log.info("Indexing Spring Boot Properties for "+project.getElementName());
|
||||
|
||||
@@ -78,16 +64,16 @@ public class SpringPropertiesIndexManager extends ListenerManager<Listener<Sprin
|
||||
if (progressService != null) {
|
||||
progressService.progressEvent(progressId, "Indexing Spring Boot Properties...");
|
||||
}
|
||||
|
||||
|
||||
SpringPropertyIndex index = new SpringPropertyIndex(valueProviders, project.getClasspath());
|
||||
|
||||
|
||||
if (progressService != null) {
|
||||
progressService.progressEvent(progressId, null);
|
||||
}
|
||||
|
||||
|
||||
Log.info("Indexing Spring Boot Properties for "+project.getElementName()+" DONE");
|
||||
Log.info("Indexed "+index.size()+" properties.");
|
||||
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -99,7 +85,7 @@ public class SpringPropertiesIndexManager extends ListenerManager<Listener<Sprin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static synchronized String getProgressId() {
|
||||
return DefaultSpringPropertyIndexProvider.class.getName()+ (progressIdCt++);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
|
||||
public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
|
||||
|
||||
|
||||
public static final FuzzyMap<PropertyInfo> EMPTY_INDEX = new SpringPropertyIndex(null, null);
|
||||
|
||||
private ValueProviderRegistry valueProviders;
|
||||
|
||||
public SpringPropertyIndex(ValueProviderRegistry valueProviders, IClasspath projectPath) {
|
||||
@@ -126,7 +128,7 @@ public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
|
||||
protected String getKey(PropertyInfo entry) {
|
||||
return entry.getId();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the longest known property that is a prefix of the given name. Here prefix does not mean
|
||||
* 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value.test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.boot.java.value.test.MockProjects.MockProject;
|
||||
import org.springframework.ide.vscode.boot.metadata.AdHocSpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.commons.util.FuzzyMap;
|
||||
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.ProjectsHarness.ProjectCustomizer;
|
||||
|
||||
public class AdHocSpringPropertyIndexProviderTest {
|
||||
|
||||
private MockProjects projects = new MockProjects();
|
||||
|
||||
private static final ProjectCustomizer WITH_PROPERTIES = (projectContent) -> {
|
||||
projectContent.createFile("src/main/resources/application.properties",
|
||||
"some-adhoc-foo=somefoo\n" +
|
||||
"some-adhoc-bar=somebar\n"
|
||||
);
|
||||
};
|
||||
|
||||
@Test
|
||||
public void parseProperties() throws Exception {
|
||||
MockProject project = projects.create("test-project");
|
||||
project.ensureFile("src/main/resources/application.properties",
|
||||
"some-adhoc-foo=somefoo\n" +
|
||||
"some-adhoc-bar=somebar\n"
|
||||
);
|
||||
AdHocSpringPropertyIndexProvider indexer = new AdHocSpringPropertyIndexProvider(projects.finder, projects.observer, null);
|
||||
|
||||
TextDocument doc = new TextDocument(project.uri("src/main/java/SomeClass.java"), LanguageId.JAVA);
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
//alphabetic order
|
||||
"some-adhoc-bar",
|
||||
"some-adhoc-foo"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseYaml() throws Exception {
|
||||
MockProject project = projects.create("test-project");
|
||||
project.ensureFile("src/main/resources/application.yml",
|
||||
"from-yaml:\n" +
|
||||
" adhoc:\n" +
|
||||
" foo: somefoo\n" +
|
||||
" bar: somebar\n"
|
||||
);
|
||||
AdHocSpringPropertyIndexProvider indexer = new AdHocSpringPropertyIndexProvider(projects.finder, projects.observer, null);
|
||||
|
||||
TextDocument doc = new TextDocument(project.uri("src/main/java/SomeClass.java"), LanguageId.JAVA);
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
//alphabetic order
|
||||
"from-yaml.adhoc.bar",
|
||||
"from-yaml.adhoc.foo"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void respondsToClasspathChanges() throws Exception {
|
||||
MockProject project = projects.create("test-project");
|
||||
project.ensureFile("src/main/resources/application.properties",
|
||||
"initial-property=somefoo\n"
|
||||
);
|
||||
|
||||
AdHocSpringPropertyIndexProvider indexer = new AdHocSpringPropertyIndexProvider(projects.finder, projects.observer, null);
|
||||
TextDocument doc = new TextDocument(project.uri("src/main/java/SomeClass.java"), LanguageId.JAVA);
|
||||
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
"initial-property"
|
||||
);
|
||||
|
||||
project.ensureFile("new-sourcefolder/application.properties", "new-property=whatever");
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
"initial-property"
|
||||
);
|
||||
|
||||
project.createSourceFolder("new-sourcefolder");
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
"initial-property",
|
||||
"new-property"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void respondsToFileChanges() throws Exception {
|
||||
MockProject project = projects.create("test-project");
|
||||
project.ensureFile("src/main/resources/application.properties",
|
||||
"initial-property=somefoo\n"
|
||||
);
|
||||
|
||||
AdHocSpringPropertyIndexProvider indexer = new AdHocSpringPropertyIndexProvider(projects.finder, projects.observer, projects.fileObserver);
|
||||
TextDocument doc = new TextDocument(project.uri("src/main/java/SomeClass.java"), LanguageId.JAVA);
|
||||
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
"initial-property"
|
||||
);
|
||||
|
||||
project.ensureFile("src/main/resources/application.properties", "from-properties=whatever");
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
"from-properties"
|
||||
);
|
||||
|
||||
project.ensureFile("src/main/resources/application.yml", "from-yaml: whatever");
|
||||
assertProperties(indexer.getIndex(doc),
|
||||
"from-properties",
|
||||
"from-yaml"
|
||||
);
|
||||
}
|
||||
|
||||
private void assertProperties(FuzzyMap<PropertyInfo> index, String... expectedProps) {
|
||||
StringBuilder foundProps = new StringBuilder();
|
||||
for (PropertyInfo p : index) {
|
||||
foundProps.append(p.getId()+"\n");
|
||||
}
|
||||
StringBuilder expecteds = new StringBuilder();
|
||||
for (String string : expectedProps) {
|
||||
expecteds.append(string+"\n");
|
||||
}
|
||||
assertEquals(expecteds.toString(), foundProps.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.value.test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.PathMatcher;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.junit.Assert;
|
||||
import org.springframework.ide.vscode.commons.java.ClasspathIndex;
|
||||
import org.springframework.ide.vscode.commons.java.IClasspath;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener;
|
||||
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath;
|
||||
import org.springframework.ide.vscode.commons.languageserver.jdt.ls.Classpath.CPE;
|
||||
import org.springframework.ide.vscode.commons.util.FileObserver;
|
||||
import org.springframework.ide.vscode.commons.util.IOUtil;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
public class MockProjects {
|
||||
|
||||
private final Map<String, MockProject> projectsByName = new HashMap<String, MockProjects.MockProject>();
|
||||
|
||||
public final MockProjectObserver observer = new MockProjectObserver();
|
||||
public final JavaProjectFinder finder = new JavaProjectFinder() {
|
||||
@Override
|
||||
public Optional<IJavaProject> find(TextDocumentIdentifier doc) {
|
||||
synchronized (projectsByName) {
|
||||
try {
|
||||
File file = new File(new URI(doc.getUri()));
|
||||
for (MockProject project : projectsByName.values()) {
|
||||
if (project.contains(file)) {
|
||||
return Optional.of(project);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public MockFileObserver fileObserver = new MockFileObserver();
|
||||
|
||||
public class MockProject implements IJavaProject {
|
||||
|
||||
final private File root;
|
||||
final private String name;
|
||||
final private List<File> sourceFolders = new ArrayList<File>();
|
||||
|
||||
final private IClasspath classpath = new IClasspath() {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<CPE> getClasspathEntries() throws Exception {
|
||||
List<CPE> cp = new ArrayList<>();
|
||||
for (File sf : sourceFolders) {
|
||||
cp.add(new CPE(Classpath.ENTRY_KIND_SOURCE, sf.getAbsolutePath()));
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
};
|
||||
|
||||
public MockProject(String name) {
|
||||
synchronized (projectsByName) {
|
||||
Assert.assertFalse(projectsByName.containsKey(name));
|
||||
this.name = name;
|
||||
this.root = Files.createTempDir();
|
||||
createSourceFolder("src/main/java");
|
||||
createSourceFolder("src/main/resources");
|
||||
projectsByName.put(name, this);
|
||||
}
|
||||
synchronized (observer.listeners) {
|
||||
for (Listener l : observer.listeners) {
|
||||
l.created(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(File file) {
|
||||
return file.toPath().startsWith(root.toPath());
|
||||
}
|
||||
|
||||
public void createSourceFolder(String projectRelativePath) {
|
||||
File sourceFolder = new File(root, projectRelativePath);
|
||||
sourceFolder.mkdirs();
|
||||
sourceFolders.add(sourceFolder);
|
||||
synchronized (observer.listeners) {
|
||||
for (Listener l : observer.listeners) {
|
||||
l.changed(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IClasspath getClasspath() {
|
||||
return classpath;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClasspathIndex getIndex() {
|
||||
//TODO: the fact we have to implement this probably means something is a bit off with the
|
||||
// framework api, because this info should not really depend on anything but a project's classpath.
|
||||
// So why should every type of project need to implement its own mechanic for indexing classpath?
|
||||
throw new IllegalStateException("Not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getLocationUri() {
|
||||
return root.toURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return root.isDirectory();
|
||||
}
|
||||
|
||||
public void ensureFile(String projectRelativePath, String contents) throws Exception {
|
||||
File target = new File(root, projectRelativePath);
|
||||
boolean existed = target.exists();
|
||||
IOUtil.pipe(new ByteArrayInputStream(contents.getBytes("UTF8")), target);
|
||||
if (existed) {
|
||||
fileObserver.fileChanged(target);
|
||||
} else {
|
||||
fileObserver.fileCreated(target);
|
||||
}
|
||||
}
|
||||
|
||||
public String uri(String projectRelativePath) {
|
||||
return new File(root, projectRelativePath).toURI().toString();
|
||||
}
|
||||
}
|
||||
|
||||
public class MockProjectObserver implements ProjectObserver {
|
||||
|
||||
public final LinkedHashSet<Listener> listeners = new LinkedHashSet<>();
|
||||
|
||||
@Override
|
||||
public void addListener(Listener l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeListener(Listener l) {
|
||||
listeners.remove(l);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class FileListener {
|
||||
final PathMatcher matcher;
|
||||
final Consumer<String> handler;
|
||||
FileListener(List<String> globPatterns, Consumer<String> listener) {
|
||||
super();
|
||||
this.matcher = buildPathMatcher(globPatterns);
|
||||
this.handler = listener;
|
||||
}
|
||||
private PathMatcher buildPathMatcher(List<String> globPatterns) {
|
||||
if (globPatterns.size()==0) {
|
||||
return path -> true;
|
||||
} else if (globPatterns.size()==1) {
|
||||
return FileSystems.getDefault().getPathMatcher("glob:"+globPatterns.get(0));
|
||||
} else {
|
||||
PathMatcher[] matchers = new PathMatcher[globPatterns.size()];
|
||||
for (int i = 0; i < matchers.length; i++) {
|
||||
matchers[i] = FileSystems.getDefault().getPathMatcher("glob:"+globPatterns.get(i));
|
||||
}
|
||||
return (path) -> {
|
||||
for (int i = 0; i < matchers.length; i++) {
|
||||
if (matchers[i].matches(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class MockFileObserver implements FileObserver {
|
||||
|
||||
final AtomicLong idGen = new AtomicLong();
|
||||
|
||||
final Map<String,FileListener> create_listeners = new HashMap<>();
|
||||
final Map<String,FileListener> change_listeners = new HashMap<>();
|
||||
final Map<String,FileListener> delete_listeners = new HashMap<>();
|
||||
|
||||
private String add(Map<String, FileListener> listeners, List<String> globPatterns, Consumer<String> handler) {
|
||||
String id = ""+idGen.incrementAndGet();
|
||||
synchronized (listeners) {
|
||||
listeners.put(id, new FileListener(globPatterns, handler));
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public void fileChanged(File target) {
|
||||
notify(change_listeners, target);
|
||||
}
|
||||
|
||||
public void fileCreated(File target) {
|
||||
notify(create_listeners, target);
|
||||
}
|
||||
|
||||
private void notify(Map<String, FileListener> listeners, File target) {
|
||||
Path path = target.toPath();
|
||||
synchronized (listeners) {
|
||||
for (FileListener l : listeners.values()) {
|
||||
if (l.matcher.matches(path)) {
|
||||
l.handler.accept(target.toURI().toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onFileCreated(List<String> globPattern, Consumer<String> handler) {
|
||||
return add(create_listeners, globPattern, handler);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String onFileChanged(List<String> globPattern, Consumer<String> handler) {
|
||||
return add(change_listeners, globPattern, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String onFileDeleted(List<String> globPattern, Consumer<String> handler) {
|
||||
return add(delete_listeners, globPattern, handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unsubscribe(String subscriptionId) {
|
||||
return
|
||||
remove(create_listeners, subscriptionId) ||
|
||||
remove(change_listeners, subscriptionId) ||
|
||||
remove(delete_listeners, subscriptionId);
|
||||
}
|
||||
|
||||
private boolean remove(Map<String, FileListener> listeners, String subscriptionId) {
|
||||
synchronized (listeners) {
|
||||
return listeners.remove(subscriptionId) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MockProject create(String name) {
|
||||
return new MockProject(name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public class ValueCompletionTest {
|
||||
|
||||
@Test
|
||||
public void testPrefixIdentification() {
|
||||
ValueCompletionProcessor processor = new ValueCompletionProcessor(null);
|
||||
ValueCompletionProcessor processor = new ValueCompletionProcessor(null, null);
|
||||
|
||||
assertEquals("pre", processor.identifyPropertyPrefix("pre", 3));
|
||||
assertEquals("pre", processor.identifyPropertyPrefix("prefix", 3));
|
||||
@@ -262,6 +262,52 @@ public class ValueCompletionTest {
|
||||
"@Value(\"#{345${spring.prop1<*>}}\")");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adHoc() throws Exception {
|
||||
prepareDefaultIndexData();
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA,
|
||||
"package org.test;\n" +
|
||||
"\n" +
|
||||
"import org.springframework.beans.factory.annotation.Value;\n" +
|
||||
"\n" +
|
||||
"public class TestValueCompletion {\n" +
|
||||
" \n" +
|
||||
" @Value(\"<*>\")\n" +
|
||||
" private String value1;\n" +
|
||||
"}"
|
||||
);
|
||||
|
||||
//There are no 'ad-hoc' properties yet. So should only suggest the default ones.
|
||||
editor.assertContextualCompletions(
|
||||
"<*>"
|
||||
, //==>
|
||||
"${data.prop2}<*>",
|
||||
"${else.prop3}<*>",
|
||||
"${spring.prop1}<*>"
|
||||
);
|
||||
|
||||
indexHarness.adHoc("spring.ad-hoc.thingy");
|
||||
indexHarness.adHoc("spring.ad-hoc.other-thingy");
|
||||
indexHarness.adHoc("spring.prop1"); //should not suggest this twice!
|
||||
editor.assertContextualCompletions(
|
||||
"<*>"
|
||||
, //==>
|
||||
"${data.prop2}<*>",
|
||||
"${else.prop3}<*>",
|
||||
"${spring.ad-hoc.other-thingy}<*>",
|
||||
"${spring.ad-hoc.thingy}<*>",
|
||||
"${spring.prop1}<*>"
|
||||
);
|
||||
|
||||
editor.assertContextualCompletions(
|
||||
"adhoc<*>"
|
||||
, //==>
|
||||
"${spring.ad-hoc.other-thingy}<*>",
|
||||
"${spring.ad-hoc.thingy}<*>"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
private void prepareDefaultIndexData() {
|
||||
indexHarness.data("spring.prop1", "java.lang.String", null, null);
|
||||
indexHarness.data("data.prop2", "java.lang.String", null, null);
|
||||
|
||||
@@ -33,6 +33,7 @@ import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
|
||||
import org.springframework.ide.vscode.boot.metadata.CachingValueProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertiesLoader;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.IType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.ComposableLanguageServer;
|
||||
@@ -1627,6 +1628,7 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
|
||||
javaProjectFinder,
|
||||
ProjectObserver.NULL,
|
||||
md.getIndexProvider(),
|
||||
(doc) -> SpringPropertyIndex.EMPTY_INDEX,
|
||||
typeUtilProvider,
|
||||
RunningAppProvider.NULL,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
|
||||
@@ -30,6 +30,7 @@ import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
|
||||
import org.springframework.ide.vscode.boot.metadata.CachingValueProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.ComposableLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
@@ -3866,6 +3867,7 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
|
||||
javaProjectFinder,
|
||||
ProjectObserver.NULL,
|
||||
md.getIndexProvider(),
|
||||
(doc) -> SpringPropertyIndex.EMPTY_INDEX,
|
||||
typeUtilProvider,
|
||||
RunningAppProvider.NULL,
|
||||
SpringLiveHoverWatchdog.DEFAULT_INTERVAL
|
||||
|
||||
@@ -54,6 +54,7 @@ public class BootJavaLanguageServerHarness extends LanguageServerHarness<Composa
|
||||
private JavaProjectFinder projectFinder = null;
|
||||
private ProjectObserver projectObserver = null;
|
||||
private SpringPropertyIndexProvider indexProvider = null;
|
||||
private SpringPropertyIndexProvider adHocIndexProvider = null;
|
||||
private RunningAppProvider runningAppProvider = null;
|
||||
private PropertyIndexHarness indexHarness = null;
|
||||
private Duration watchDogInterval = null;
|
||||
@@ -68,6 +69,7 @@ public class BootJavaLanguageServerHarness extends LanguageServerHarness<Composa
|
||||
indexHarness = new PropertyIndexHarness();
|
||||
projectFinder = indexHarness.getProjectFinder();
|
||||
indexProvider = indexHarness.getIndexProvider();
|
||||
adHocIndexProvider = indexHarness.adHocIndexProvider;
|
||||
projectObserver = ProjectObserver.NULL;
|
||||
runningAppProvider = RunningAppProvider.NULL;
|
||||
return this;
|
||||
@@ -105,6 +107,7 @@ public class BootJavaLanguageServerHarness extends LanguageServerHarness<Composa
|
||||
builder.projectFinder==null?defaults.projectFinder:builder.projectFinder,
|
||||
builder.projectObserver==null?defaults.projectObserver:builder.projectObserver,
|
||||
builder.indexProvider==null?defaults.indexProvider:builder.indexProvider,
|
||||
builder.adHocIndexProvider==null?defaults.adHocIndexProvider:builder.adHocIndexProvider,
|
||||
builder.typeUtilProvider==null?defaults.typeUtilProvider:builder.typeUtilProvider,
|
||||
builder.runningAppProvider==null?defaults.runningAppProvider:builder.runningAppProvider,
|
||||
builder.watchDogInterval==null?defaults.watchDogInterval:builder.watchDogInterval
|
||||
|
||||
@@ -88,7 +88,7 @@ public class ProjectsHarness {
|
||||
MAVEN
|
||||
// GRADLE?
|
||||
}
|
||||
|
||||
|
||||
public static final IJavaProject dummyProject() throws URISyntaxException {
|
||||
return new LegacyJavaProject(new BasicFileObserver(), new URI("file:///someplace/nonexistent"), null,
|
||||
new DelegatingCachedClasspath(() -> null, null), (uri, cpe) -> JavaDocProviders.createFor(cpe));
|
||||
@@ -143,5 +143,5 @@ public class ProjectsHarness {
|
||||
public MavenJavaProject mavenProject(String name) throws Exception {
|
||||
return (MavenJavaProject) project(ProjectType.MAVEN, name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -36,6 +36,12 @@ public class PropertyIndexHarness {
|
||||
|
||||
private Map<String, ConfigurationMetadataProperty> datas = new LinkedHashMap<>();
|
||||
private SpringPropertyIndex index = null;
|
||||
private FuzzyMap<PropertyInfo> adHocProperties = new FuzzyMap<PropertyInfo>() {
|
||||
@Override
|
||||
protected String getKey(PropertyInfo entry) {
|
||||
return entry.getId();
|
||||
}
|
||||
};
|
||||
private IJavaProject testProject = null;
|
||||
|
||||
protected final SpringPropertyIndexProvider indexProvider = new SpringPropertyIndexProvider() {
|
||||
@@ -54,6 +60,8 @@ public class PropertyIndexHarness {
|
||||
}
|
||||
};
|
||||
|
||||
protected final SpringPropertyIndexProvider adHocIndexProvider = doc -> adHocProperties;
|
||||
|
||||
public synchronized void useProject(IJavaProject p) throws Exception {
|
||||
index = null;
|
||||
this.testProject = p;
|
||||
@@ -563,8 +571,16 @@ public class PropertyIndexHarness {
|
||||
return indexProvider;
|
||||
}
|
||||
|
||||
public SpringPropertyIndexProvider getAdHocIndexProvider() {
|
||||
return adHocIndexProvider;
|
||||
}
|
||||
|
||||
public JavaProjectFinder getProjectFinder() {
|
||||
return (doc) -> Optional.ofNullable(testProject);
|
||||
}
|
||||
|
||||
public void adHoc(String adHocPropertyId) {
|
||||
adHocProperties.add(new PropertyInfo(adHocPropertyId, null, null, null, null, null, null, null, null, null, null));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
logging.pattern.level=
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
logging:
|
||||
level:
|
||||
auto
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>test-scope-annotation</artifactId>
|
||||
<artifactId>test-annotation</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user