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:
Kris De Volder
2018-08-01 15:23:57 -07:00
parent 0d4b6eedde
commit 0b1b5cdf2a
25 changed files with 783 additions and 91 deletions

View File

@@ -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);
}

View File

@@ -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);
}
};
}
}

View File

@@ -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);
}
};
}
}

View File

@@ -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"/>

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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++);
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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));
}
}

View File

@@ -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>