simplified spring property index handling by removing a lot of value-specific stuff

This commit is contained in:
Martin Lippert
2017-02-14 11:43:51 +01:00
parent 5e61656faa
commit 32d44e60a9
20 changed files with 75 additions and 1483 deletions

View File

@@ -19,7 +19,7 @@ import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
@@ -31,9 +31,9 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
*/
public class ValueCompletionProcessor {
private FuzzyMap<PropertyInfo> index;
private FuzzyMap<ConfigurationMetadataProperty> index;
public ValueCompletionProcessor(FuzzyMap<PropertyInfo> index) {
public ValueCompletionProcessor(FuzzyMap<ConfigurationMetadataProperty> index) {
this.index = index;
}
@@ -43,9 +43,9 @@ public class ValueCompletionProcessor {
try {
// case: @Value(<*>)
if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) {
List<Match<PropertyInfo>> matches = findMatches("");
List<Match<ConfigurationMetadataProperty>> matches = findMatches("");
for (Match<PropertyInfo> match : matches) {
for (Match<ConfigurationMetadataProperty> match : matches) {
DocumentEdits edits = new DocumentEdits(doc);
edits.replace(offset, offset, "\"${" + match.data.getId() + "}\"");
@@ -64,9 +64,9 @@ public class ValueCompletionProcessor {
String proposalPrefix = "\"";
String proposalPostfix = "\"";
List<Match<PropertyInfo>> matches = findMatches(prefix);
List<Match<ConfigurationMetadataProperty>> matches = findMatches(prefix);
for (Match<PropertyInfo> match : matches) {
for (Match<ConfigurationMetadataProperty> match : matches) {
DocumentEdits edits = new DocumentEdits(doc);
edits.replace(startOffset, endOffset, proposalPrefix + "${" + match.data.getId() + "}" + proposalPostfix);
@@ -101,9 +101,9 @@ public class ValueCompletionProcessor {
String fullNodeContent = doc.get(node.getStartPosition(), node.getLength());
String postCompletion = isClosingBracketMissing(fullNodeContent + preCompletion) ? "}" : "";
List<Match<PropertyInfo>> matches = findMatches(prefix);
List<Match<ConfigurationMetadataProperty>> matches = findMatches(prefix);
for (Match<PropertyInfo> match : matches) {
for (Match<ConfigurationMetadataProperty> match : matches) {
DocumentEdits edits = new DocumentEdits(doc);
edits.replace(startOffset, endOffset, preCompletion + match.data.getId() + postCompletion);
@@ -150,8 +150,8 @@ public class ValueCompletionProcessor {
return result;
}
private List<Match<PropertyInfo>> findMatches(String prefix) {
List<Match<PropertyInfo>> matches = index.find(camelCaseToHyphens(prefix));
private List<Match<ConfigurationMetadataProperty>> findMatches(String prefix) {
List<Match<ConfigurationMetadataProperty>> matches = index.find(camelCaseToHyphens(prefix));
return matches;
}

View File

@@ -1,148 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016 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.metadata;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader.InvalidCacheLoadException;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
/**
* A abstract {@link ValueProviderStrategy} that is mean to help speedup successive invocations of
* content assist with a similar 'query' string.
* <p>
* This implementation is meant to be used for providers that use potentially lenghty/expensive searches
* to determine hints. Since content assist hints are requested by Eclipse CA framework directly on
* the UI thread, they can not simply perform a lengthy search and block UI thread until it finished.
* <p>
* This implementation therefore does the following:
* <ul>
* <li>Limit the duration of time spent on the UI thread.
* <li>Cache results of searches for a limited time.
* <li>Speedup queries for successive queries by using the already cached result of a similar (prefix) query.
* <li>When the time spent on UI thread waiting for a current search exceeds the allowed time limit,
* return immediately with whatever results have been found so far.
* </ul>
*
* TODO: rather than an abstract class this should really be 'Wrapper' class that delegates to another
* {@link ValueProviderStrategy} and adds a cache in front of it.
*
* @author Kris De Volder
*/
public abstract class CachingValueProvider implements ValueProviderStrategy {
private static final Duration DEFAULT_TIMEOUT = Duration.ofMillis(1000);
/**
* Content assist is called inside UI thread and so doing something lenghty things
* like a JavaSearch will block the UI thread completely freezing the UI. So, we
* only return as many results as can be obtained within this hard TIMEOUT limit.
*/
public static Duration TIMEOUT = DEFAULT_TIMEOUT;
/**
* The maximum number of results returned for a single request. Used to limit the
* values that are cached per entry.
*/
private int MAX_RESULTS = 500;
private Cache<Tuple2<String,String>, CacheEntry> cache = createCache();
private class CacheEntry {
boolean isComplete = false;
int count = 0;
Flux<StsValueHint> values;
public CacheEntry(String query, Flux<StsValueHint> producer) {
values = producer
.take(MAX_RESULTS)
.cache(MAX_RESULTS);
values.subscribe(); // create infinite demand so that we actually force cache entries to be fetched upto the max.
}
@Override
public String toString() {
return "CacheEntry [isComplete=" + isComplete + ", count=" + count + "]";
}
}
@Override
public final Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
Tuple2<String, String> key = key(javaProject, query);
CacheEntry cached = null;
try {
cached = cache.get(key, () -> new CacheEntry(query, getValuesIncremental(javaProject, query)));
} catch (ExecutionException e) {
Log.log(e);
}
return cached.values;
}
/**
* Tries to use an already cached, complete result for a query that is a prefix of the current query to speed things up.
* <p>
* Falls back on doing a full-blown search if there's no usable 'prefix-query' in the cache.
*/
private Flux<StsValueHint> getValuesIncremental(IJavaProject javaProject, String query) {
// debug("trying to solve "+query+" incrementally");
String subquery = query;
while (subquery.length()>=1) {
subquery = subquery.substring(0, subquery.length()-1);
CacheEntry cached = null;
try {
cached = cache.get(key(javaProject, subquery), () -> null);
} catch (ExecutionException | InvalidCacheLoadException e) {
// Log.log(e);
}
if (cached!=null) {
System.out.println("cached "+subquery+": "+cached);
if (cached.isComplete) {
return cached.values
// .doOnNext((hint) -> debug("filter["+query+"]: "+hint.getValue()))
.filter((hint) -> 0!=FuzzyMatcher.matchScore(query, hint.getValue().toString()));
} else {
// debug("subquery "+subquery+" cached but is incomplete");
}
}
}
// debug("full search for: "+query);
return getValuesAsync(javaProject, query);
}
protected abstract Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query);
private Tuple2<String,String> key(IJavaProject javaProject, String query) {
return Tuples.of(javaProject==null?null:javaProject.getElementName(), query);
}
protected <K,V> Cache<K,V> createCache() {
return CacheBuilder.newBuilder().expireAfterWrite(1, TimeUnit.MINUTES).expireAfterAccess(1, TimeUnit.MINUTES).build();
}
public static void restoreDefaults() {
TIMEOUT = DEFAULT_TIMEOUT;
}
}

View File

@@ -1,154 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.metadata;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.Flags;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.StringUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import reactor.core.publisher.Flux;
/**
* Provides the algorithm for 'class-reference' valueProvider.
* <p>
* See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc
*
* @author Kris De Volder
* @author Alex Boyko
*/
public class ClassReferenceProvider extends CachingValueProvider {
/**
* Default value for the 'concrete' parameter.
*/
private static final boolean DEFAULT_CONCRETE = true;
private static final ClassReferenceProvider UNTARGETTED_INSTANCE = new ClassReferenceProvider(null, DEFAULT_CONCRETE);
public static final Function<Map<String, Object>, ValueProviderStrategy> FACTORY = applyOn(
1, TimeUnit.MINUTES,
(params) -> {
String target = getTarget(params);
Boolean concrete = getConcrete(params);
if (target!=null || concrete!=null) {
if (concrete==null) {
concrete = DEFAULT_CONCRETE;
}
return new ClassReferenceProvider(target, concrete);
}
return UNTARGETTED_INSTANCE;
}
);
private static <K,V> Function<K,V> applyOn(long duration, TimeUnit unit, Function<K,V> func) {
Cache<K,V> cache = CacheBuilder.newBuilder().expireAfterAccess(duration, unit).expireAfterWrite(duration, unit).build();
return (k) -> {
try {
return cache.get(k, () -> func.apply(k));
} catch (ExecutionException e) {
Log.log(e);
return null;
}
};
}
private static String getTarget(Map<String, Object> params) {
if (params!=null) {
Object obj = params.get("target");
if (obj instanceof String) {
String target = (String) obj;
if (StringUtil.hasText(target)) {
return target;
}
}
}
return null;
}
private static boolean isAbstract(IType type) {
try {
return type.isInterface() || Flags.isAbstract(type.getFlags());
} catch (Exception e) {
Log.log(e);
return false;
}
}
private static Boolean getConcrete(Map<String, Object> params) {
try {
if (params!=null) {
Object obj = params.get("concrete");
if (obj instanceof String) {
String concrete = (String) obj;
return Boolean.valueOf(concrete);
} else if (obj instanceof Boolean) {
return (Boolean) obj;
}
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
/**
* Optional, fully qualified name of the 'target' type. Suggested hints should be a subtype of this type.
*/
private String target;
/**
* Optional parameter, whether only concrete types should be suggested. Default value is true.
*/
private boolean concrete;
private ClassReferenceProvider(String target, boolean concrete) {
this.target = target;
this.concrete = concrete;
}
@Override
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
IType targetType = target == null || target.isEmpty() ? javaProject.getClasspath().findType("java.lang.Object") : javaProject.getClasspath().findType(target);
if (targetType == null) {
return Flux.empty();
}
Set<IType> allSubclasses = javaProject.getClasspath()
.allSubtypesOf(targetType)
.filter(t -> Flags.isPublic(t.getFlags()) && !concrete || !isAbstract(t))
.collect(Collectors.toSet())
.block();
if (allSubclasses.isEmpty()) {
return Flux.empty();
} else {
return javaProject.getClasspath()
.fuzzySearchTypes(query, type -> allSubclasses.contains(type))
.collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2()))
.flatMap(l -> Flux.fromIterable(l))
.map(t -> StsValueHint.create(t.getT1()));
}
}
}

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.boot.metadata;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
@@ -19,10 +20,10 @@ 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 static final FuzzyMap<ConfigurationMetadataProperty> EMPTY_INDEX = new SpringPropertyIndex(null);
private JavaProjectFinder javaProjectFinder;
private SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault());
private SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager();
private ProgressService progressService = (id, msg) -> { /*ignore*/ };
@@ -31,7 +32,7 @@ public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexPr
}
@Override
public FuzzyMap<PropertyInfo> getIndex(IDocument doc) {
public FuzzyMap<ConfigurationMetadataProperty> getIndex(IDocument doc) {
IJavaProject jp = javaProjectFinder.find(doc);
if (jp!=null) {
return indexManager.get(jp, progressService);

View File

@@ -1,133 +0,0 @@
/*******************************************************************************
* Copyright (c) 2015 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.metadata;
import static org.springframework.ide.vscode.commons.util.StringUtil.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap.Match;
import org.springframework.ide.vscode.commons.util.StringUtil;
/**
* An index navigator allows selecting subset of a property index as if
* navigating the index by selecting on a property
*
* @author Kris De Volder
*/
public class IndexNavigator {
//Possible opitmization: we could cache prefix match candidate and extended match candidate
// since it is assumed that the index is immutable for the lifetime of
// the index navigator.
private static final char NAV_CHAR = '.';
/**
* Property access in this navigator are interpreted relative
* to this prefix
*/
private String prefix = null;
private FuzzyMap<PropertyInfo> index;
private IndexNavigator(FuzzyMap<PropertyInfo> index) {
this.index = index;
}
private IndexNavigator(FuzzyMap<PropertyInfo> index, String prefix) {
this.index = index;
this.prefix = prefix;
}
public static IndexNavigator with(FuzzyMap<PropertyInfo> index) {
return new IndexNavigator(index);
}
public IndexNavigator selectSubProperty(String name) {
return new IndexNavigator(index, join(prefix, name));
}
protected String join(String prefix, String postfix) {
if (!hasText(prefix)) {
return postfix;
} else {
return prefix + NAV_CHAR + postfix;
}
}
/**
* @return property info that is an exact match with the current prefix or
* null if there's no exact match
*/
public PropertyInfo getExactMatch() {
if (prefix!=null) {
PropertyInfo candidate = index.findLongestCommonPrefixEntry(prefix);
if (candidate.getId().equals(prefix)) {
return candidate;
}
}
return null;
}
/**
* Get a property that has the current prefix as a 'true' prefix. A true prefix
* is a String that has the current prefix as a prefix and continues onward with
* a navigation operation.
*/
public PropertyInfo getExtensionCandidate() {
//If current prefix is null then all entries in the index are candidates since
// the index is at the 'root' of the tree and we don't need a '.' to navigate
String extendedPrefix = prefix==null?"":prefix + NAV_CHAR;
PropertyInfo candidate = index.findLongestCommonPrefixEntry(extendedPrefix);
if (candidate.getId().startsWith(extendedPrefix)) {
return candidate;
}
return null;
}
public String getPrefix() {
return prefix;
}
public List<Match<PropertyInfo>> findMatching(String query) {
if (!StringUtil.hasText(prefix)) {
return index.find(query);
} else {
String dottedPrefix = prefix +".";
List<Match<PropertyInfo>> candidates = index.find(dottedPrefix + query);
if (!candidates.isEmpty()) {
//TODO: we can do better than this using treemap to narrow based on
// prefix
List<Match<PropertyInfo>> matches = new ArrayList<Match<PropertyInfo>>(candidates.size());
for (Match<PropertyInfo> match : candidates) {
if (match.data.getId().startsWith(dottedPrefix)){
matches.add(match);
}
}
return matches;
}
}
return Collections.emptyList();
}
@Override
public String toString() {
return "IndexNavigator("+prefix+")";
}
public boolean isEmpty() {
return getExactMatch()==null && getExtensionCandidate()==null;
}
}

View File

@@ -1,52 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.metadata;
import java.util.Map;
import java.util.function.Function;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuples;
/**
* Provides the algorithm for 'logger-name' valueProvider.
* <p>
* See: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-docs/src/main/asciidoc/appendix-configuration-metadata.adoc
*
* @author Kris De Volder
* @author Alex Boyko
*/
public class LoggerNameProvider extends CachingValueProvider {
private static final ValueProviderStrategy INSTANCE = new LoggerNameProvider();
public static final Function<Map<String, Object>, ValueProviderStrategy> FACTORY = (params) -> INSTANCE;
@Override
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
return Flux.concat(
javaProject.getClasspath()
.fuzzySearchPackages(query)
.map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2())),
javaProject.getClasspath()
.fuzzySearchTypes(query, null)
.map(t -> Tuples.of(StsValueHint.create(t.getT1()), t.getT2()))
)
.collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2()))
.flatMap(l -> Flux.fromIterable(l))
.map(t -> t.getT1());
}
}

View File

@@ -1,235 +0,0 @@
/*******************************************************************************
* Copyright (c) 2015 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.metadata;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import org.json.JSONArray;
import org.json.JSONObject;
/**
* Helper class to manipulate data in a file presumed to contain
* spring-boot configuration data.
*
* @author Kris De Volder
* @author Alex Boyko
*/
public class MetadataManipulator {
private abstract class Content {
public abstract String toString();
public abstract void addProperty(JSONObject jsonObject) throws Exception;
}
/**
* Content was parse as JSONObject.
*/
private class ParsedContent extends Content {
private JSONObject object;
public ParsedContent(JSONObject o) {
this.object = o;
}
public String toString() {
try {
return object.toString(indentFactor);
}
catch (Exception e) {
return null;
}
}
@Override
public void addProperty(JSONObject propertyData) throws Exception {
JSONArray properties = object.getJSONArray("properties");
properties.put(properties.length(), propertyData);
}
}
/**
* Content that is 'unparsed' and just a bunch of text.
* Used only as a fallback when data in file can't
* be parsed.
* <p>
* This content is manipulated by string manipulation.
* It is less reliable, but can be done even if the
* file data is not parseable.
*/
private class RawContent extends Content {
private StringBuilder doc;
public RawContent(String content) {
this.doc = new StringBuilder(content);
}
@Override
public String toString() {
return doc.toString();
}
@Override
public void addProperty(JSONObject propertyData) throws Exception {
int insertAt = findLast(']');
if (insertAt<0) {
//although we're not looking for much, we didn't find it!
//Funky file contents. Let's just insert something at end of file in a 'best effort' spirit.
insertAt = doc.length();
}
insert(insertAt, "\n");
insert(insertAt, propertyData.toString(indentFactor));
int insertComma = findInsertCommaPos(insertAt);
if (insertComma>=0) {
insert(insertComma, ",");
}
}
/**
* Maybe we need to add a comma in front of the new entry. This
* method finds if/where to stick this comma.
* @throws Exception
*/
private int findInsertCommaPos(int pos) throws Exception {
pos--;
while (pos>=0 && Character.isWhitespace(doc.charAt(pos))) {
pos--;
}
if (pos>=0) {
char c = doc.charAt(pos);
if (c == '}') {
//Add a comma after a '}'
return pos+1;
}
}
return -1;
}
private int insert(int insertAt, String str) throws Exception {
if (insertAt < doc.length()) {
doc.replace(insertAt, insertAt, str);
} else {
doc.append(str);
}
return insertAt + str.length();
}
private int findLast(char toFind) throws Exception {
int pos = doc.length()-1;
while (pos>=0 && doc.charAt(pos)!=toFind) {
pos--;
}
//We got here either because
// - we found char at pos or..
// - we reached position *before* start of file (i.e. -1)
return pos;
}
}
public interface ContentStore {
String getContents() throws Exception;
void setContents(String content) throws Exception;
}
private static final String INITIAL_CONTENT =
"{\"properties\": [\n" +
"]}";
private static final String ENCODING = "UTF8";
private ContentStore contentStore;
private Content fContent;
private int indentFactor = 2;
public MetadataManipulator(ContentStore contentStore) {
this.contentStore = contentStore;
}
public MetadataManipulator(final File file) {
this(new ContentStore() {
@Override
public String getContents() throws Exception {
return new String(Files.readAllBytes(Paths.get(file.toURI())), ENCODING);
}
@Override
public void setContents(String content) throws Exception {
Files.write(Paths.get(file.toURI()), content.getBytes(ENCODING));
}
});
}
private Content getContent() throws Exception {
if (fContent==null) {
fContent = readContent();
}
return fContent;
}
private Content readContent() throws Exception {
String content = contentStore.getContents();
if (content.trim().isEmpty()) {
JSONObject o = initialContent();
return new ParsedContent(o);
} else {
try {
return new ParsedContent(new JSONObject(content));
} catch (Exception e) {
//couldn't parse?
return new RawContent(content);
}
}
}
public void addDefaultInfo(String propertyName) throws Exception {
getContent().addProperty(createDefaultData(propertyName));
}
private JSONObject createDefaultData(String propertyName) throws Exception {
JSONObject obj = new JSONObject(new LinkedHashMap<String, Object>());
obj.put("name", propertyName);
obj.put("type", String.class.getName());
obj.put("description", "A description for '"+propertyName+"'");
return obj;
}
/**
* Generate the initial content (must be generated rather than being a constant to respect newline conventions
* on user's system.
*/
private JSONObject initialContent() throws Exception {
return new JSONObject(INITIAL_CONTENT);
}
/**
* After manipulating the data, use this to persist changes back to the file.
*/
public void save() throws Exception {
contentStore.setContents(getContent().toString());
}
/**
* Determines whether the 'reliable' manipulations can be used (which is the case
* only if the data in the file is valid json).
*/
public boolean isReliable() throws Exception {
return getContent() instanceof ParsedContent;
}
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2016-2017 Pivotal, Inc.
* Copyright (c) 2016, 2017 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

View File

@@ -1,190 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014-2016 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.metadata;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataSource;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.boot.configurationmetadata.ValueProvider;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
/**
* Information about a spring property, basically, this is the same as
*
* {@link ConfigurationMetadataProperty} but augmented with information
* about {@link ConfigurationMetadataSource}s that declare the property.
*
* @author Kris De Volder
*/
public class PropertyInfo {
/**
* Identifies a 'Source'. This is essentially the sames as {@link ConfigurationMetadataSource}.
* We could use {@link ConfigurationMetadataSource} directly, but this only contains
* the info that we actually use so takes less memory.
*/
public static class PropertySource {
private final String sourceType;
private final String sourceMethod;
public PropertySource(ConfigurationMetadataSource source) {
String st = source.getSourceType();
this.sourceType = st!=null?st:source.getType();
this.sourceMethod = source.getSourceMethod();
}
@Override
public String toString() {
return sourceType+"::"+sourceMethod;
}
public String getSourceType() {
return sourceType;
}
public String getSourceMethod() {
return sourceMethod;
}
}
final private String id;
private String type;
final private String name;
final private Object defaultValue;
final private String description;
private List<PropertySource> sources;
private Deprecation deprecation;
private ImmutableList<ValueHint> valueHints;
private ImmutableList<ValueHint> keyHints;
private ValueProviderStrategy valueProvider;
private ValueProviderStrategy keyProvider;
public PropertyInfo(String id, String type, String name,
Object defaultValue, String description,
Deprecation deprecation,
List<ValueHint> valueHints,
List<ValueHint> keyHints,
ValueProviderStrategy valueProvider,
ValueProviderStrategy keyProvider,
List<PropertySource> sources) {
super();
this.id = id;
this.type = type;
this.name = name;
this.defaultValue = defaultValue;
this.description = description;
this.deprecation = deprecation;
this.valueHints = valueHints==null?null:ImmutableList.copyOf(valueHints);
this.keyHints = keyHints==null?null:ImmutableList.copyOf(keyHints);
this.valueProvider = valueProvider;
this.keyProvider = keyProvider;
this.sources = sources;
}
public PropertyInfo(ValueProviderRegistry valueProviders, ConfigurationMetadataProperty prop) {
this(
prop.getId(),
prop.getType(),
prop.getName(),
prop.getDefaultValue(),
prop.getDescription(),
prop.getDeprecation(),
prop.getHints().getValueHints(),
prop.getHints().getKeyHints(),
valueProviders.resolve(prop.getHints().getValueProviders()),
valueProviders.resolve(prop.getHints().getKeyProviders()),
null
);
for (ValueProvider h : prop.getHints().getValueProviders()) {
if (h.getName().equals("handle-as")) {
handleAs(h.getParameters().get("target"));
}
}
}
private void handleAs(Object targetObject) {
// debug("handle-as "+this.getId()+" -> "+targetObject);
if (targetObject instanceof String) {
this.type = (String)targetObject;
}
}
public String getId() {
return id;
}
public String getType() {
return type;
}
public String getName() {
return name;
}
public Object getDefaultValue() {
return defaultValue;
}
public String getDescription() {
return description;
}
public List<PropertySource> getSources() {
if (sources!=null) {
return sources;
}
return Collections.emptyList();
}
@Override
public String toString() {
return "PropertyInfo("+getId()+")";
}
public void addSource(ConfigurationMetadataSource source) {
if (sources==null) {
sources = new ArrayList<PropertySource>();
}
sources.add(new PropertySource(source));
}
public PropertyInfo withId(String alias) {
if (alias.equals(id)) {
return this;
}
return new PropertyInfo(alias, type, name, defaultValue, description, deprecation, valueHints, keyHints, valueProvider, keyProvider, sources);
}
public void setDeprecation(Deprecation d) {
this.deprecation = d;
}
public boolean isDeprecated() {
return deprecation!=null;
}
public String getDeprecationReason() {
return deprecation == null ? null : deprecation.getReason();
}
public String getDeprecationReplacement() {
return deprecation == null ? null : deprecation.getReplacement();
}
public void addValueHints(List<ValueHint> hints) {
Builder<ValueHint> builder = ImmutableList.builder();
builder.addAll(valueHints);
builder.addAll(hints);
valueHints = builder.build();
}
public void addKeyHints(List<ValueHint> hints) {
Builder<ValueHint> builder = ImmutableList.builder();
builder.addAll(keyHints);
builder.addAll(hints);
keyHints = builder.build();
}
}

View File

@@ -1,71 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.metadata;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry.ValueProviderStrategy;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import com.google.common.collect.ImmutableList;
import reactor.core.publisher.Flux;
/**
* @author Kris De Volder
*/
public class ResourceHintProvider implements ValueProviderStrategy {
private static String[] CLASSPATH_PREFIXES = {
"classpath:",
"classpath*:"
};
private static final String[] URL_PREFIXES = new String[] {
"classpath:",
"classpath*:",
"file:",
"http://",
"https://"
};
@Override
public Flux<StsValueHint> getValues(IJavaProject javaProject, String query) {
for (String prefix : CLASSPATH_PREFIXES) {
if (query.startsWith(prefix)) {
return classpathHints
.getValues(javaProject, query.substring(prefix.length()))
.map((hint) -> hint.prefixWith(prefix));
}
}
return Flux.fromIterable(urlPrefixHints);
}
final private ImmutableList<StsValueHint> urlPrefixHints = ImmutableList.copyOf(
Arrays.stream(URL_PREFIXES)
.map(StsValueHint::create)
.collect(Collectors.toList())
);
private ClasspathHints classpathHints = new ClasspathHints();
private static class ClasspathHints extends CachingValueProvider {
@Override
protected Flux<StsValueHint> getValuesAsync(IJavaProject javaProject, String query) {
return Flux.fromStream(javaProject.getClasspath().getClasspathResources().distinct().map(StsValueHint::create));
}
}
}

View File

@@ -13,9 +13,8 @@ package org.springframework.ide.vscode.boot.metadata;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
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;
@@ -27,20 +26,19 @@ import org.springframework.ide.vscode.commons.languageserver.ProgressService;
*
* @author Kris De Volder
*/
public class SpringPropertiesIndexManager extends ListenerManager<Listener<SpringPropertiesIndexManager>> {
public class SpringPropertiesIndexManager {
private Map<IJavaProject, SpringPropertyIndex> indexes = null;
private final ValueProviderRegistry valueProviders;
private static int progressIdCt = 0;
public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders) {
this.valueProviders = valueProviders;
public SpringPropertiesIndexManager() {
}
public synchronized FuzzyMap<PropertyInfo> get(IJavaProject project, ProgressService progressService) {
public synchronized FuzzyMap<ConfigurationMetadataProperty> get(IJavaProject project, ProgressService progressService) {
if (indexes==null) {
indexes = new HashMap<>();
}
SpringPropertyIndex index = indexes.get(project);
if (index==null) {
String progressId = getProgressId();
@@ -48,7 +46,7 @@ public class SpringPropertiesIndexManager extends ListenerManager<Listener<Sprin
progressService.progressEvent(progressId, "Indexing Spring Boot Properties...");
}
index = new SpringPropertyIndex(valueProviders, project.getClasspath());
index = new SpringPropertyIndex(project.getClasspath());
indexes.put(project, index);
if (progressService != null) {
@@ -57,15 +55,6 @@ public class SpringPropertiesIndexManager extends ListenerManager<Listener<Sprin
}
return index;
}
public synchronized void clear() {
if (indexes!=null) {
indexes.clear();
for (Listener<SpringPropertiesIndexManager> l : getListeners()) {
l.changed(this);
}
}
}
private static synchronized String getProgressId() {
return DefaultSpringPropertyIndexProvider.class.getName()+ (progressIdCt++);

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* Copyright (c) 2015, 2017 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
@@ -11,71 +11,41 @@
package org.springframework.ide.vscode.boot.metadata;
import java.util.Collection;
import java.util.List;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataGroup;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataRepository;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataSource;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.util.StringUtil;
public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
public class SpringPropertyIndex extends FuzzyMap<ConfigurationMetadataProperty> {
private ValueProviderRegistry valueProviders;
public SpringPropertyIndex(ValueProviderRegistry valueProviders, IClasspath projectPath) {
this.valueProviders = valueProviders;
public SpringPropertyIndex(IClasspath projectPath) {
if (projectPath!=null) {
// try {
PropertiesLoader loader = new PropertiesLoader();
ConfigurationMetadataRepository metadata = loader.load(projectPath);
//^^^ Should be done in bg? It seems fast enough for now.
Collection<ConfigurationMetadataProperty> allEntries = metadata.getAllProperties().values();
for (ConfigurationMetadataProperty item : allEntries) {
add(new PropertyInfo(valueProviders, item));
}
for (ConfigurationMetadataGroup group : metadata.getAllGroups().values()) {
for (ConfigurationMetadataSource source : group.getSources().values()) {
for (ConfigurationMetadataProperty prop : source.getProperties().values()) {
PropertyInfo info = get(prop.getId());
info.addSource(source);
}
}
}
// System.out.println(">>> spring properties metadata loaded "+this.size()+" items===");
// dumpAsTestData();
// System.out.println(">>> spring properties metadata loaded "+this.size()+" items===");
// } catch (Exception e) {
// LOG.log
// }
PropertiesLoader loader = new PropertiesLoader();
ConfigurationMetadataRepository metadata = loader.load(projectPath);
Collection<ConfigurationMetadataProperty> allEntries = metadata.getAllProperties().values();
for (ConfigurationMetadataProperty item : allEntries) {
add(item);
}
}
}
public void add(ConfigurationMetadataProperty propertyInfo) {
add(new PropertyInfo(valueProviders, propertyInfo));
}
/**
* Dumps out 'test data' based on the current contents of the index. This is not meant to be
* used in 'production' code. The idea is to call this method during development to dump a
* 'snapshot' of the index onto System.out. The data is printed in a forma so that it can be easily
* pasted/used into JUNit testing code.
*/
public void dumpAsTestData() {
List<Match<PropertyInfo>> allData = this.find("");
for (Match<PropertyInfo> match : allData) {
PropertyInfo d = match.data;
System.out.println("data("
+dumpString(d.getId())+", "
+dumpString(d.getType())+", "
+dumpString(d.getDefaultValue())+", "
+dumpString(d.getDescription()) +");"
);
// public void dumpAsTestData() {
// List<Match<PropertyInfo>> allData = this.find("");
// for (Match<PropertyInfo> match : allData) {
// PropertyInfo d = match.data;
// System.out.println("data("
// +dumpString(d.getId())+", "
// +dumpString(d.getType())+", "
// +dumpString(d.getDefaultValue())+", "
// +dumpString(d.getDescription()) +");"
// );
// for (PropertySource source : d.getSources()) {
// String st = source.getSourceType();
// String sm = source.getSourceMethod();
@@ -83,15 +53,15 @@ public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
// System.out.println(d.getId() +" from: "+st+"::"+sm);
// }
// }
}
}
// }
// }
private String dumpString(Object v) {
if (v==null) {
return "null";
}
return dumpString(""+v);
}
// private String dumpString(Object v) {
// if (v==null) {
// return "null";
// }
// return dumpString(""+v);
// }
private String dumpString(String s) {
if (s==null) {
@@ -123,7 +93,7 @@ public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
}
@Override
protected String getKey(PropertyInfo entry) {
protected String getKey(ConfigurationMetadataProperty entry) {
return entry.getId();
}
@@ -132,25 +102,25 @@ public class SpringPropertyIndex extends FuzzyMap<PropertyInfo> {
* 'string prefix' but a prefix in the sense of treating '.' as a kind of separators. So
* 'prefix' is not allowed to end in the middle of a 'segment'.
*/
public static PropertyInfo findLongestValidProperty(FuzzyMap<PropertyInfo> index, String name) {
int bracketPos = name.indexOf('[');
int endPos = bracketPos>=0?bracketPos:name.length();
PropertyInfo prop = null;
String prefix = null;
while (endPos>0 && prop==null) {
prefix = name.substring(0, endPos);
String canonicalPrefix = StringUtil.camelCaseToHyphens(prefix);
prop = index.get(canonicalPrefix);
if (prop==null) {
endPos = name.lastIndexOf('.', endPos-1);
}
}
if (prop!=null) {
//We should meet caller's expectation that matched properties returned by this method
// match the names exactly even if we found them using relaxed name matching.
return prop.withId(prefix);
}
return null;
}
// public static PropertyInfo findLongestValidProperty(FuzzyMap<PropertyInfo> index, String name) {
// int bracketPos = name.indexOf('[');
// int endPos = bracketPos>=0?bracketPos:name.length();
// PropertyInfo prop = null;
// String prefix = null;
// while (endPos>0 && prop==null) {
// prefix = name.substring(0, endPos);
// String canonicalPrefix = StringUtil.camelCaseToHyphens(prefix);
// prop = index.get(canonicalPrefix);
// if (prop==null) {
// endPos = name.lastIndexOf('.', endPos-1);
// }
// }
// if (prop!=null) {
// //We should meet caller's expectation that matched properties returned by this method
// // match the names exactly even if we found them using relaxed name matching.
// return prop.withId(prefix);
// }
// return null;
// }
}

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2015 Pivotal, Inc.
* Copyright (c) 2015, 2017 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -10,11 +10,12 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.metadata;
import org.springframework.boot.configurationmetadata.ConfigurationMetadataProperty;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.commons.util.text.IDocument;
@FunctionalInterface
public interface SpringPropertyIndexProvider {
FuzzyMap<PropertyInfo> getIndex(IDocument doc);
FuzzyMap<ConfigurationMetadataProperty> getIndex(IDocument doc);
}

View File

@@ -1,98 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016 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.metadata;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.springframework.boot.configurationmetadata.ValueProvider;
import org.springframework.ide.vscode.boot.metadata.hints.StsValueHint;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import reactor.core.publisher.Flux;
/**
* An instance of this class serves as a 'registry' that associates known
* {@link ValueProvider} ids to strategy objects used in the computation of completions
* for properties to which the provider is attached.
*
* @author Kris De Volder
*/
public class ValueProviderRegistry {
private static ValueProviderRegistry DEFAULT;
/**
* Creates a default {@link ValueProviderRegistry} which is initialized with all the known
* providers. (This is the one production code should use, test code might make use
* something else for mocking purposes).
*/
public synchronized static ValueProviderRegistry getDefault() {
if (DEFAULT==null) {
DEFAULT = new ValueProviderRegistry();
DEFAULT.initializeDefaults(DEFAULT);
}
return DEFAULT;
}
protected void initializeDefaults(ValueProviderRegistry r) {
def("logger-name", LoggerNameProvider.FACTORY);
def("class-reference", ClassReferenceProvider.FACTORY);
}
private Map<String, Function<Map<String, Object>, ValueProviderStrategy>> registry = new HashMap<>();
public interface ValueProviderStrategy {
Flux<StsValueHint> getValues(IJavaProject javaProject, String query);
default Collection<StsValueHint> getValuesNow(IJavaProject javaProject, String query) {
return this.getValues(javaProject, query)
.take(CachingValueProvider.TIMEOUT)
.collectList()
.block();
}
}
/**
* Defines a value provider by binding its id to a strategy.
*/
public void def(String id, Function<Map<String, Object>, ValueProviderStrategy> algo) {
registry.put(id, algo);
}
/**
* Resolve a list of {@link ValueProvider}s to a {@link ValueProviderStrategy}.
* <p>
* Essentially this finds the first provider from the list which has a known name
* and uses that to iinstantiate a ValueProviderStrategy. Spring boot assumes that
* a list is provided to allow new providers to be defined that override older ones
* and these are added at the top of the list. Thus an older IDE can continue to
* function using the older provider further down the list whereas newer IDEs will
* use a 'better' one from higher up the list.
*/
public ValueProviderStrategy resolve(List<ValueProvider> providerDescriptors) {
if (CollectionUtil.hasElements(providerDescriptors)) {
for (ValueProvider descriptor : providerDescriptors) {
Function<Map<String, Object>, ValueProviderStrategy> factory = registry.get(descriptor.getName());
if (factory!=null) {
Map<String, Object> params = descriptor.getParameters();
return factory.apply(params);
}
}
}
return null;
}
}

View File

@@ -1,137 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.metadata.hints;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.ide.vscode.boot.metadata.util.DeprecationUtil;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.javadoc.IJavadoc;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
/**
* Sts version of {@link ValueHint} contains similar data, but accomoates
* a html snippet to be computed lazyly for the description.
* <p>
* This is meant to support using data pulled from JavaDoc in enums as description.
* This data is a html snippet, whereas the data derived from spring-boot metadata is
* just plain text.
*
* @author Kris De Volder
*/
public class StsValueHint {
private final String value;
private final Renderable description;
private final Deprecation deprecation;
/**
* Create a hint with a textual description.
* <p>
* This constructor is private. Use one of the provided
* static 'create' methods instead.
*/
private StsValueHint(String value, Renderable description, Deprecation deprecation) {
this.value = value==null?"null":value.toString();
Assert.isLegal(!this.value.startsWith("StsValueHint"));
this.description = description;
this.deprecation = deprecation;
}
/**
* Creates a hint out of an IJavaElement.
*/
public static StsValueHint create(String value, IJavaElement javaElement) {
return new StsValueHint(value, javaDocSnippet(javaElement), DeprecationUtil.extract(javaElement)) {
@Override
public IJavaElement getJavaElement() {
return javaElement;
}
};
}
public static StsValueHint create(String value) {
return new StsValueHint(value, Renderables.NO_DESCRIPTION, null);
}
public static StsValueHint create(ValueHint hint) {
return new StsValueHint(""+hint.getValue(), textSnippet(hint.getDescription()), null);
}
public static StsValueHint create(IType klass) {
return new StsValueHint(klass.getFullyQualifiedName(), javaDocSnippet(klass), DeprecationUtil.extract(klass)) {
@Override
public IJavaElement getJavaElement() {
return klass;
}
};
}
/**
* Create a html snippet from a text snippet.
*/
private static Renderable textSnippet(String description) {
if (StringUtil.hasText(description)) {
return Renderables.text(description);
}
return Renderables.NO_DESCRIPTION;
}
public String getValue() {
return value;
}
public Renderable getDescription() {
return description;
}
private static Renderable javaDocSnippet(IJavaElement je) {
return Renderables.lazy(() -> {
IJavadoc jdoc = je.getJavaDoc();
if (jdoc != null) {
return jdoc.getRenderable();
} else {
return Renderables.NO_DESCRIPTION;
}
});
}
@Override
public String toString() {
return "StsValueHint("+value+")";
}
public Deprecation getDeprecation() {
return deprecation;
}
public IJavaElement getJavaElement() {
return null;
}
public StsValueHint prefixWith(String prefix) {
StsValueHint it = this;
return new StsValueHint(prefix+getValue(), description, deprecation) {
@Override
public IJavaElement getJavaElement() {
return it.getJavaElement();
}
};
}
}

View File

@@ -1,33 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016-2017 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.metadata.hints;
import java.util.List;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import static org.springframework.ide.vscode.commons.util.Renderables.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
public class ValueHintHoverInfo {
public static Renderable create(StsValueHint hint) {
Builder<Renderable> builder = ImmutableList.builder();
builder.add(bold(""+hint.getValue()));
builder.add(paragraph(hint.getDescription()));
return concat(builder.build());
}
}

View File

@@ -1,59 +0,0 @@
/*******************************************************************************
* Copyright (c) 2016 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.metadata.util;
import java.util.Optional;
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.ide.vscode.commons.java.IAnnotatable;
import org.springframework.ide.vscode.commons.java.IJavaElement;
import com.google.common.collect.ImmutableSet;
public class DeprecationUtil {
private static final ImmutableSet<String> DEPRECATED_ANOT_NAMES = ImmutableSet.of(
"org.springframework.boot.context.properties.DeprecatedConfigurationProperty",
"DeprecatedConfigurationProperty",
"java.lang.Deprecated",
"Deprecated"
);
/**
* Extract {@link Deprecation} info from annotations on a {@link IJavaElement}
*/
public static Deprecation extract(IJavaElement je) {
Optional<Deprecation> deprecation = Optional.empty();
if (je instanceof IAnnotatable) {
deprecation = extract((IAnnotatable)je);
}
return deprecation.isPresent() ? deprecation.get() : null;
}
/**
* Extract {@link Deprecation} info from annotations on a {@link IJavaElement}
*/
private static Optional<Deprecation> extract(IAnnotatable m) {
return m.getAnnotations().filter(a -> DEPRECATED_ANOT_NAMES.contains(a.getElementName())).map(a -> {
Deprecation d = new Deprecation();
a.getMemberValuePairs().forEach(pair -> {
String name = pair.getMemberName();
if (name.equals("reason")) {
d.setReason((String) pair.getValue());
} else if (name.equals("replacement")) {
d.setReplacement((String) pair.getValue());
}
});
return d;
}).findFirst();
}
}

View File

@@ -1,20 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 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.metadata.util;
/**
* @author Kris De Volder
*/
public interface Listener<T> {
void changed(T info);
}

View File

@@ -1,36 +0,0 @@
/*******************************************************************************
* Copyright (c) 2014 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.metadata.util;
import java.util.Arrays;
import org.springframework.ide.vscode.commons.util.ListenerList;
public class ListenerManager<T> {
private ListenerList<T> listeners = new ListenerList<>(ListenerList.IDENTITY);
public void addListener(T l) {
listeners.add(l);
}
public void removeListener(T l) {
listeners.remove(l);
}
@SuppressWarnings("unchecked")
public Iterable<T> getListeners() {
return (Iterable<T>) Arrays.asList(listeners.getListeners());
}
}

View File

@@ -18,10 +18,8 @@ import org.springframework.boot.configurationmetadata.ConfigurationMetadataPrope
import org.springframework.boot.configurationmetadata.Deprecation;
import org.springframework.boot.configurationmetadata.ValueHint;
import org.springframework.boot.configurationmetadata.ValueProvider;
import org.springframework.ide.vscode.boot.metadata.PropertyInfo;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndex;
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry;
import org.springframework.ide.vscode.boot.metadata.util.FuzzyMap;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -33,17 +31,16 @@ import org.springframework.ide.vscode.commons.util.text.IDocument;
public class PropertyIndexHarness {
private Map<String, ConfigurationMetadataProperty> datas = new LinkedHashMap<>();
private ValueProviderRegistry valueProviders = ValueProviderRegistry.getDefault();
private SpringPropertyIndex index = null;
private IJavaProject testProject = null;
protected final SpringPropertyIndexProvider indexProvider = new SpringPropertyIndexProvider() {
@Override
public FuzzyMap<PropertyInfo> getIndex(IDocument doc) {
public FuzzyMap<ConfigurationMetadataProperty> getIndex(IDocument doc) {
synchronized (PropertyIndexHarness.this) {
if (index==null) {
IClasspath classpath = testProject == null ? null : testProject.getClasspath();
index = new SpringPropertyIndex(valueProviders, classpath);
index = new SpringPropertyIndex(classpath);
for (ConfigurationMetadataProperty propertyInfo : datas.values()) {
index.add(propertyInfo);
}