Allow exceptions in hint providers to be handled by the framework

Changed the hint providers to throw exception when resolving hints, and
the framework now handles the errors by creating a suitable error
completion.
This commit is contained in:
nsingh
2017-01-19 13:36:56 -08:00
parent edb4ed1f2e
commit 6ce2947663
10 changed files with 91 additions and 38 deletions

View File

@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.util;
import java.util.Collection;
import java.util.concurrent.Callable;
import javax.inject.Provider;
@@ -32,15 +33,18 @@ public class EnumValueParser implements ValueParser {
}
public EnumValueParser(String typeName, Collection<String> values) {
this(typeName, () -> values);
this(typeName, provider(values));
}
public EnumValueParser(String typeName, Callable<Collection<String>> values) {
this(typeName, provider(values));
}
public EnumValueParser(String typeName, Provider<Collection<String>> values) {
this.typeName = typeName;
this.values = values;
}
public Object parse(String str) {
public Object parse(String str) {
// IMPORTANT: check the text FIRST before fetching values
// from the hints provider, as the hints provider may be expensive when resolving values
if (!StringUtil.hasText(str)) {
@@ -48,6 +52,7 @@ public class EnumValueParser implements ValueParser {
}
Collection<String> values = this.values.get();
//If values is not known (null) then just assume the str is acceptable.
if (values==null || values.contains(str)) {
return str;
@@ -63,5 +68,21 @@ public class EnumValueParser implements ValueParser {
protected String createErrorMessage(String parseString, Collection<String> values) {
return "'"+parseString+"' is not valid for Enum '"+typeName+"'. Valid values are: "+values;
}
private static <T> Provider<T> provider(T values) {
return () -> values;
}
private static <T> Provider<T> provider(Callable<T> values) {
return () -> {
try {
return values.call();
} catch (Exception e) {
// Ignore
return null;
}
};
}
}

View File

@@ -24,5 +24,17 @@ public interface CompletionFactory {
ICompletionProposal beanProperty(IDocument doc, String contextProperty, YType contextType, String query, YTypedProperty p, double score, DocumentEdits edits, YTypeUtil typeUtil);
ICompletionProposal valueProposal(String value, String query, String label, YType type, double score, DocumentEdits edits, YTypeUtil typeUtil);
/**
* Creates a completion with an EMPTY value and a display message indicating some error condition. The purpose of this is
* to show the user a completion with some meaningful information on why completion values were not fetched.
*
* @param message the error message to display to the user in the completion
* @param query
* @param type
* @param edits
* @param typeUtil
* @return
*/
ICompletionProposal errorMessage(String message, String query, YType type, DocumentEdits edits, YTypeUtil typeUtil);
}

View File

@@ -11,11 +11,11 @@
package org.springframework.ide.vscode.commons.yaml.completion;
import org.apache.commons.lang3.reflect.TypeUtils;
import org.eclipse.lsp4j.CompletionItemKind;
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.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.hover.YPropertyInfoTemplates;
@@ -145,4 +145,11 @@ public class DefaultCompletionFactory implements CompletionFactory {
return new ValueProposal(value, query, label, type, score, edits, typeUtil);
}
@Override
public ICompletionProposal errorMessage(String message, String query, YType type, DocumentEdits edits,
YTypeUtil typeUtil) {
String value = ""; // Empty value for the proposal. Purpose is to show a message with no value to fill in.
double score = FuzzyMatcher.matchScore(query, value);
return new ValueProposal(value, query, message, type, score, edits, typeUtil);
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.Document
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.FuzzyMatcher;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.Renderable;
@@ -41,6 +42,8 @@ import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser
import org.springframework.ide.vscode.commons.yaml.structure.YamlStructureParser.SNode;
import org.springframework.ide.vscode.commons.yaml.util.YamlIndentUtil;
import com.google.common.collect.ImmutableList;
public class YTypeAssistContext extends AbstractYamlAssistContext {
final static Logger logger = LoggerFactory.getLogger(YTypeAssistContext.class);
@@ -147,7 +150,13 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
}
private List<ICompletionProposal> getValueCompletions(YamlDocument doc, int offset, String query) {
YValueHint[] values = typeUtil.getHintValues(type, getSchemaContext());
YValueHint[] values=null;
try {
values = typeUtil.getHintValues(type, getSchemaContext());
} catch (Exception e) {
DocumentEdits edits = new DocumentEdits(doc.getDocument());
return ImmutableList.of(completionFactory().errorMessage(ExceptionUtil.getMessage(e), query, type, edits, typeUtil));
}
if (values!=null) {
ArrayList<ICompletionProposal> completions = new ArrayList<>();
for (YValueHint value : values) {

View File

@@ -11,7 +11,6 @@
package org.springframework.ide.vscode.commons.yaml.schema;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
@@ -20,11 +19,10 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import javax.inject.Provider;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.Renderable;
@@ -101,7 +99,7 @@ public class YTypeFactory {
}
@Override
public YValueHint[] getHintValues(YType type, DynamicSchemaContext dc) {
public YValueHint[] getHintValues(YType type, DynamicSchemaContext dc) throws Exception {
return ((AbstractType)type).getHintValues(dc);
}
@@ -147,7 +145,7 @@ public class YTypeFactory {
private List<YTypedProperty> propertyList = new ArrayList<>();
private final List<YValueHint> hints = new ArrayList<>();
private Map<String, YTypedProperty> cachedPropertyMap;
private SchemaContextAware<Collection<YValueHint>> hintProvider;
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
public boolean isSequenceable() {
return false;
@@ -169,17 +167,17 @@ public class YTypeFactory {
return null;
}
public void addHintProvider(Provider<Collection<YValueHint>> hintProvider) {
addHintProvider((DynamicSchemaContext dc) -> hintProvider.get());
public void addHintProvider(Callable<Collection<YValueHint>> hintProvider) {
addHintProvider((DynamicSchemaContext dc) -> hintProvider);
}
public void addHintProvider(SchemaContextAware<Collection<YValueHint>> hintProvider) {
public void addHintProvider(SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider) {
this.hintProvider = hintProvider;
}
public YValueHint[] getHintValues(DynamicSchemaContext dc) {
Collection<YValueHint> providerHints = hintProvider != null ? hintProvider.withContext(dc) : null;
public YValueHint[] getHintValues(DynamicSchemaContext dc) throws Exception {
Collection<YValueHint> providerHints=getProviderHints(dc);
if (providerHints == null || providerHints.isEmpty()) {
return hints.toArray(new YValueHint[hints.size()]);
} else {
@@ -199,6 +197,16 @@ public class YTypeFactory {
}
}
private Collection<YValueHint> getProviderHints(DynamicSchemaContext dc) throws Exception {
if (hintProvider != null) {
Callable<Collection<YValueHint>> withContext = hintProvider.withContext(dc);
if (withContext != null) {
return withContext.call();
}
}
return ImmutableList.of();
}
public List<YTypedProperty> getProperties(DynamicSchemaContext dc) {
return Collections.unmodifiableList(propertyList);
}
@@ -627,7 +635,7 @@ public class YTypeFactory {
Collection<String> strings = values.withContext(dc);
return strings==null
? null
: strings.stream()
: () -> strings.stream()
.map((s) -> new BasicYValueHint(s))
.collect(Collectors.toSet());
});

View File

@@ -29,7 +29,7 @@ public interface YTypeUtil {
boolean isSequencable(YType type);
boolean isBean(YType type);
YType getDomainType(YType type);
YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc);
YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc) throws Exception;
String niceTypeName(YType type);
YType getKeyType(YType type);
ValueParser getValueParser(YType type, DynamicSchemaContext dc);

View File

@@ -14,11 +14,10 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Provider;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTarget;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.CFTargetCache;
import org.springframework.ide.vscode.commons.cloudfoundry.client.cftarget.NoTargetsException;
@@ -27,7 +26,7 @@ import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
public abstract class AbstractCFHintsProvider implements Provider<Collection<YValueHint>> {
public abstract class AbstractCFHintsProvider implements Callable<Collection<YValueHint>> {
public static final String EMPTY_VALUE = "";
protected final CFTargetCache targetCache;
@@ -40,7 +39,7 @@ public abstract class AbstractCFHintsProvider implements Provider<Collection<YVa
}
@Override
public Collection<YValueHint> get() {
public Collection<YValueHint> call() throws Exception {
Collection<YValueHint> hints = new ArrayList<>();
// TODO: Probably not the most ideal thing to do, but for now show any
// CF errors

View File

@@ -11,8 +11,7 @@
package org.springframework.ide.vscode.manifest.yaml;
import java.util.Collection;
import javax.inject.Provider;
import java.util.concurrent.Callable;
import org.eclipse.lsp4j.CompletionOptions;
import org.eclipse.lsp4j.ServerCapabilities;
@@ -56,8 +55,8 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
YamlASTProvider parser = new YamlParser(yaml);
Provider<Collection<YValueHint>> buildPacksProvider = getBuildpacksProvider();
Provider<Collection<YValueHint>> servicesProvider = getServicesProvider();
Callable<Collection<YValueHint>> buildPacksProvider = getBuildpacksProvider();
Callable<Collection<YValueHint>> servicesProvider = getServicesProvider();
schema = new ManifestYmlSchema(buildPacksProvider, servicesProvider);
@@ -100,11 +99,11 @@ public class ManifestYamlLanguageServer extends SimpleLanguageServer {
return cfTargetCache;
}
private Provider<Collection<YValueHint>> getBuildpacksProvider() {
private Callable<Collection<YValueHint>> getBuildpacksProvider() {
return new ManifestYamlCFBuildpacksProvider(getCfTargetCache());
}
private Provider<Collection<YValueHint>> getServicesProvider() {
private Callable<Collection<YValueHint>> getServicesProvider() {
return new ManifestYamlCFServicesProvider(getCfTargetCache());
}

View File

@@ -12,8 +12,7 @@ package org.springframework.ide.vscode.manifest.yaml;
import java.util.Collection;
import java.util.Set;
import javax.inject.Provider;
import java.util.concurrent.Callable;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
@@ -35,13 +34,13 @@ public class ManifestYmlSchema implements YamlSchema {
private final YBeanType TOPLEVEL_TYPE;
private final YTypeUtil TYPE_UTIL;
private final Provider<Collection<YValueHint>> buildpackProvider;
private final Callable<Collection<YValueHint>> buildpackProvider;
private static final Set<String> TOPLEVEL_EXCLUDED = ImmutableSet.of(
"name", "host", "hosts"
);
public ManifestYmlSchema(Provider<Collection<YValueHint>> buildpackProvider, Provider<Collection<YValueHint>> servicesProvider) {
public ManifestYmlSchema(Callable<Collection<YValueHint>> buildpackProvider, Callable<Collection<YValueHint>> servicesProvider) {
this.buildpackProvider = buildpackProvider;
YTypeFactory f = new YTypeFactory();
TYPE_UTIL = f.TYPE_UTIL;

View File

@@ -12,8 +12,7 @@ package org.springframework.ide.vscode.manifest.yaml;
import java.util.Collection;
import java.util.Set;
import javax.inject.Provider;
import java.util.concurrent.Callable;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
@@ -93,9 +92,9 @@ public class ManifestYmlValueParsers {
};
}
public static ValueParser fromHints(String typeName, Provider<Collection<YValueHint>> hintProvider) {
Provider<Collection<String>> values= () -> {
Collection<YValueHint> hints = hintProvider.get();
public static ValueParser fromHints(String typeName, Callable<Collection<YValueHint>> hintProvider) {
Callable<Collection<String>> values= () -> {
Collection<YValueHint> hints = hintProvider.call();
if (hints != null) {
Builder<String> builder = ImmutableSet.builder();