More tweaks to dynamic releases attributes reconcile and CA

This commit is contained in:
Kris De Volder
2017-07-28 12:01:13 -07:00
parent ca93d93d15
commit 724ae4aed4
9 changed files with 310 additions and 118 deletions

View File

@@ -20,16 +20,20 @@ import org.apache.commons.lang3.tuple.Pair;
import org.springframework.ide.vscode.bosh.models.CachingModelProvider;
import org.springframework.ide.vscode.bosh.models.CloudConfigModel;
import org.springframework.ide.vscode.bosh.models.DynamicModelProvider;
import org.springframework.ide.vscode.bosh.models.ReleaseData;
import org.springframework.ide.vscode.bosh.models.ReleasesModel;
import org.springframework.ide.vscode.bosh.models.StemcellData;
import org.springframework.ide.vscode.bosh.models.StemcellModel;
import org.springframework.ide.vscode.bosh.models.StemcellsModel;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.PartialCollection;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlAstCache;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
@@ -49,6 +53,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
import org.springframework.ide.vscode.commons.yaml.schema.YamlSchema;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -150,9 +155,12 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
t_stemcell_alias_def = f.yatomic("StemcellAlias")
.parseWith(ValueParsers.NE_STRING);
t_stemcell_alias_ref = f.yenumFromDynamicValues("StemcellAlias", (dc) -> astTypes.getDefinedNames(dc, t_stemcell_alias_def));
t_release_name_def = f.yenumFromDynamicValues("ReleaseName", (dc) -> releasesProvider.getModel(dc).getReleaseNames());
t_release_name_ref = f.yenumFromDynamicValues("ReleaseName", (dc) -> astTypes.getDefinedNames(dc, t_release_name_def));
t_stemcell_alias_ref = f.yenumFromDynamicValues("StemcellAlias", (dc) -> PartialCollection.compute(() -> astTypes.getDefinedNames(dc, t_stemcell_alias_def)));
t_release_name_def = f.yenumFromDynamicValues("ReleaseName", (dc) -> {
PartialCollection<String> releaseNames = PartialCollection.compute(() -> releasesProvider.getModel(dc).getReleaseNames());
return StringUtil.hasText(getCurrentEntityProperty(dc, "url")) ? releaseNames.addUncertainty() : releaseNames;
});
t_release_name_ref = f.yenumFromDynamicValues("ReleaseName", (dc) -> PartialCollection.compute(() -> astTypes.getDefinedNames(dc, t_release_name_def)));
t_var_name_def = f.yatomic("VariableName")
.parseWith(ValueParsers.NE_STRING);
@@ -164,12 +172,16 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
YAtomicType t_url = f.yatomic("URL");
t_url.parseWith(BoshValueParsers.url("http", "https", "file"));
YAtomicType t_network_name = f.yenumFromDynamicValues("NetworkName", (dc) -> cloudConfigProvider.getModel(dc).getNetworkNames());
YAtomicType t_disk_type = f.yenumFromDynamicValues("DiskType", (dc) -> cloudConfigProvider.getModel(dc).getDiskTypes());
YAtomicType t_vm_extension = f.yenumFromDynamicValues("VMExtension", (dc) -> cloudConfigProvider.getModel(dc).getVMExtensions());
YAtomicType t_vm_type = f.yenumFromDynamicValues("VMType", (dc) -> cloudConfigProvider.getModel(dc).getVMTypes());
YAtomicType t_az = f.yenumFromDynamicValues("AvailabilityZone", (dc) -> cloudConfigProvider.getModel(dc).getAvailabilityZones());
YAtomicType t_network_name = f.yenumFromDynamicValues("NetworkName",
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getNetworkNames()));
YAtomicType t_disk_type = f.yenumFromDynamicValues("DiskType",
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getDiskTypes()));
YAtomicType t_vm_extension = f.yenumFromDynamicValues("VMExtension",
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getVMExtensions()));
YAtomicType t_vm_type = f.yenumFromDynamicValues("VMType",
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getVMTypes()));
YAtomicType t_az = f.yenumFromDynamicValues("AvailabilityZone",
(dc) -> PartialCollection.compute(() -> cloudConfigProvider.getModel(dc).getAvailabilityZones()));
YBeanType t_network = f.ybean("Network");
addProp(t_network, "name", t_network_name).isRequired(true);
@@ -180,9 +192,36 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
addProp(t_instance_group_env, "bosh", t_params);
addProp(t_instance_group_env, "password", t_ne_string);
YAtomicType t_release_version = f.yenumFromDynamicValues("ReleaseVersion", dc -> releasesProvider.getModel(dc).getVersions());
t_release_version.addHints("latest");
t_release_version.alsoAccept("latest");
YType t_release_version = f.contextAware("ReleaseVersion", new SchemaContextAware<YType>() {
private AbstractType no_dynamic_checks = f.yenumFromDynamicValues("ReleaseVersion",
dc -> PartialCollection.compute(() -> releasesProvider.getModel(dc).getVersions())
)
.addHints("latest")
.parseWith(ValueParsers.NE_STRING);
private AbstractType base_type = f.yenumFromDynamicValues("ReleaseVersion",
dc -> PartialCollection.compute(() -> releasesProvider.getModel(dc).getVersions())
)
.addHints("latest")
.alsoAccept("latest");
@Override
public YType withContext(DynamicSchemaContext dc) throws Exception {
if (StringUtil.hasText(getCurrentEntityProperty(dc,"url"))) {
return no_dynamic_checks;
} else {
String name = getCurrentEntityProperty(dc, "name");
if (StringUtil.hasText(name)) {
return f.yenumFromDynamicValues("ReleaseVersion[name="+name+"]", (_dc) ->
PartialCollection.compute(() -> releasesProvider.getModel(dc).getReleases())
.map(r -> name.equals(r.getName()) ? r.getVersion() : null)
)
.addHints("latest")
.alsoAccept("latest");
}
}
return base_type;
}
}).treatAsAtomic();
YBeanType t_release = f.ybean("Release");
addProp(t_release, "name", t_release_name_def).isPrimary(true);
@@ -200,14 +239,16 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
YBeanType t_stemcell = f.ybean("Stemcell");
YType t_stemcell_name_ref = f.yenumFromDynamicValues("StemcellName", (dc) ->
stemcellsProvider.getModel(dc).getStemcellNames()
PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getStemcellNames())
);
YType t_stemcell_os_ref = f.yenumFromDynamicValues("StemcellOs", (dc) ->
stemcellsProvider.getModel(dc).getStemcellOss()
PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getStemcellOss())
);
YType t_stemcell_version_ref = f.contextAware("StemcellVersion", new SchemaContextAware<YType>() {
YAtomicType baseType = f.yenumFromDynamicValues("StemcellVersion", (dc) -> stemcellsProvider.getModel(dc).getVersions());
YAtomicType baseType = f.yenumFromDynamicValues("StemcellVersion", (dc) ->
PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getVersions())
);
{
baseType.addHints("latest");
baseType.alsoAccept("latest");
@@ -220,11 +261,9 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
Predicate<StemcellData> filter = currentStemcell.createVersionFilter();
YAtomicType filteredType = f.yenumFromDynamicValues("StemcellVersion["+filter+"]", (_dc) -> {
//Note: it doesn't really matter whether we use _dc or dc in code below as they should be the same.
return stemcellsProvider.getModel(dc).getStemcells().stream()
.filter(sc -> StringUtil.hasText(sc.getVersion()))
.filter(currentStemcell.createVersionFilter())
.map(sc -> sc.getVersion())
.collect(CollectorUtil.toImmutableSet());
Predicate<StemcellData> versionFilter = currentStemcell.createVersionFilter();
return PartialCollection.compute(() -> stemcellsProvider.getModel(dc).getStemcells())
.map(sc -> versionFilter.test(sc) ? sc.getVersion() : null);
});
filteredType.addHints("latest");
filteredType.alsoAccept("latest");
@@ -309,6 +348,16 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
return new StemcellModel(path.dropLast().traverseToNode(ast));
}
private String getCurrentEntityProperty(DynamicSchemaContext dc, String propName) {
YamlPath path = dc.getPath();
YamlFileAST ast = asts.getSafeAst(dc.getDocument(), true);
if (ast!=null) {
return NodeUtil.asScalar(path.dropLast().thenValAt(propName).traverseToNode(ast));
}
return null;
}
@Override
public YType getTopLevelType() {
return TOPLEVEL_TYPE;

View File

@@ -92,6 +92,12 @@ public class CachingModelProvider<T> implements DynamicModelProvider<T> {
@SuppressWarnings("unchecked")
private T wrapWithCachingProxy(T model) {
if (model==null) {
//Special case for 'no model' we'll create a model that always returns null
return (T) Proxy.newProxyInstance(modelInterface.getClassLoader(), new Class[] {modelInterface}, (o, m, a) -> {
return null;
});
}
Cache<String, CompletableFuture<Object>> attributesCache = CacheBuilder.newBuilder().build();
return (T) Proxy.newProxyInstance(modelInterface.getClassLoader(), new Class[] {modelInterface}, (o, m, a) -> {
//We only support caching results for methods that have no arguments (for now, its all we need).

View File

@@ -1173,7 +1173,9 @@ public class BoshEditorTest {
"releases:\n" +
"- name: foo\n" +
"- name: bar\n" +
"- name: bogus\n"
"- name: bogus\n" +
"- name: url-makes-this-ok\n" +
" url: file://blah"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
editor.assertProblems("bogus|unknown 'ReleaseName'. Valid values are: [foo, bar]");
@@ -1197,9 +1199,24 @@ public class BoshEditorTest {
"333.3<*>",
"latest<*>"
);
//Still get all suggestions even when 'url' property is added
editor = harness.newEditor(
"releases:\n" +
"- version: <*>\n" +
" url: blah"
);
editor.assertContextualCompletions("<*>",
"123.4<*>",
"222.2<*>",
"333.3<*>",
"latest<*>"
);
}
@Test public void reconcileReleaseVersion() throws Exception {
Editor editor;
releasesProvider = provideReleasesFrom(
new ReleaseData("foo", "123.4"),
new ReleaseData("foo", "222.2"),
@@ -1207,9 +1224,19 @@ public class BoshEditorTest {
new ReleaseData("bar", "333.3")
);
Editor editor = harness.newEditor(
editor = harness.newEditor(
"releases:\n" +
"- version: bogus\n" +
"- version: url-makes-this-possibly-correct\n" +
" url: file:///relesease-folder/blah-release.tar.gz"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
editor.assertProblems("bogus|unknown 'ReleaseVersion'. Valid values are: [123.4, 222.2, 333.3]");
editor = harness.newEditor(
"releases:\n" +
"- version: 123.4\n" +
"- version: latest\n" +
"- version: bogus\n"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);

View File

@@ -26,7 +26,7 @@ import com.google.common.collect.ImmutableSet;
public class EnumValueParser implements ValueParser {
private String typeName;
private Provider<Collection<String>> values;
private Provider<PartialCollection<String>> values;
public EnumValueParser(String typeName, String... values) {
this(typeName, ImmutableSet.copyOf(values));
@@ -36,11 +36,19 @@ public class EnumValueParser implements ValueParser {
this(typeName, provider(values));
}
private static <T> Provider<PartialCollection<T>> provider(Collection<T> values) {
return () -> PartialCollection.compute(() -> values);
}
private static <T> Provider<PartialCollection<T>> provider(Callable<Collection<T>> values) {
return () -> PartialCollection.compute(() -> values.call());
}
public EnumValueParser(String typeName, Callable<Collection<String>> values) {
this(typeName, provider(values));
}
public EnumValueParser(String typeName, Provider<Collection<String>> values) {
public EnumValueParser(String typeName, Provider<PartialCollection<String>> values) {
this.typeName = typeName;
this.values = values;
}
@@ -54,13 +62,13 @@ public class EnumValueParser implements ValueParser {
throw errorOnBlank(createBlankTextErrorMessage());
}
Collection<String> values = this.values.get();
PartialCollection<String> values = this.values.get();
// If values is not known (null) then just assume the str is acceptable.
if (values == null || values.contains(str)) {
if (values == null || !values.isComplete() || values.getElements().contains(str)) {
return str;
} else {
throw errorOnParse(createErrorMessage(str, values));
throw errorOnParse(createErrorMessage(str, values.getElements()));
}
}
@@ -80,19 +88,5 @@ public class EnumValueParser implements ValueParser {
return new ValueParseException(message);
}
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

@@ -0,0 +1,135 @@
/*******************************************************************************
* Copyright (c) 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.commons.util;
import java.util.Collection;
import java.util.concurrent.Callable;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableSet;
/**
* A partial collection instance represents collection of
* elements which may not be entirely known.
* <p>
* For unknown collection, an optional explanation, in the
* form of a caught exception may be stored as well.
*/
public class PartialCollection<T> {
private static final PartialCollection<?> UNKNOWN = new PartialCollection<>(ImmutableSet.of(), false);
private static final PartialCollection<?> EMPTY = new PartialCollection<>(ImmutableSet.of(), true);
final private ImmutableCollection<T> knownElements;
final private boolean isComplete;
final private Throwable explanation;
private PartialCollection(ImmutableCollection<T> knownElements, boolean isComplete, Throwable error) {
this.knownElements = knownElements;
this.isComplete = isComplete;
this.explanation = error;
}
private PartialCollection(ImmutableCollection<T> knownElements, boolean isComplete) {
this.knownElements = knownElements;
this.isComplete = isComplete;
this.explanation = null;
}
private PartialCollection(ImmutableCollection<T> knownElements, Throwable error) {
this.knownElements = knownElements;
this.isComplete = error==null;
this.explanation = error;
}
/**
* Create a {@link PartialCollection} by executing some computation that returs a collectioon.
* If the computation throws the resulting collection will be completely unknown, otherwise
* it will be completely known.
*/
public static <T> PartialCollection<T> compute(Callable<Collection<T>> computer) {
try {
Collection<T> allValues = computer.call();
if (allValues==null) {
return PartialCollection.unknown();
}
return new PartialCollection<>(ImmutableSet.copyOf(allValues), true);
} catch (Exception e) {
return new PartialCollection<>(ImmutableSet.of(), e);
}
}
/**
* @return All the known elements of this partial collection.
*/
public Collection<T> getElements() {
return knownElements;
}
public boolean isComplete() {
return isComplete;
}
/**
* Returns the totally unknown collection. I.e. a unknown collection with no known elements
*/
@SuppressWarnings("unchecked")
public static <T> PartialCollection<T> unknown() {
return (PartialCollection<T>) UNKNOWN;
}
/**
* Like map on streams, but silently drops any null elements returned by the mapper.
*/
public <R> PartialCollection<R> map(Function<? super T, ? extends R> mapper) {
ImmutableSet<R> mappedElements = getElements().stream().map((x) -> mapper.apply(x)).filter(x -> x!=null).collect(CollectorUtil.toImmutableSet());
return new PartialCollection<R>(mappedElements, isComplete, explanation);
}
/**
* Returns a empty collection (i.e. the collection is know to be empty).
*/
@SuppressWarnings("unchecked")
public static <T> PartialCollection<T> empty() {
return (PartialCollection<T>) EMPTY;
}
/**
* Make a copy of this collection that has the same known elements but also has unknown elements.
*/
public PartialCollection<T> addUncertainty() {
if (!this.isComplete()) {
return this; //No need to make a copy. Current collection is already only partially known.
}
return new PartialCollection<>(knownElements, false);
}
/**
* A completely unknown collection with a given exception explaining the reason.
*/
public static <T> PartialCollection<T> unknown(Exception e) {
Assert.isLegal(e!=null);
return new PartialCollection<>(ImmutableSet.of(), e);
}
public PartialCollection<T> addAll(Collection<T> moreElements) {
ImmutableSet.Builder<T> elements = ImmutableSet.builder();
elements.addAll(getElements());
elements.addAll(moreElements);
return new PartialCollection<>(elements.build(), isComplete, explanation);
}
public Throwable getExplanation() {
return explanation;
}
}

View File

@@ -30,6 +30,7 @@ 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.PartialCollection;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.ValueParseException;
import org.springframework.ide.vscode.commons.yaml.completion.DefaultCompletionFactory.ValueProposal;
@@ -197,16 +198,11 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
}
private List<ICompletionProposal> getValueCompletions(YamlDocument doc, SNode node, int offset, String query) {
YValueHint[] values=null;
try {
values = typeUtil.getHintValues(type, getSchemaContext());
} catch (Exception e) {
if (!Boolean.getBoolean("lsp.yaml.completions.errors.disable")) {
return ImmutableList.of(completionFactory().errorMessage(query, getMessage(e)));
} else {
Log.warn(query, e);
}
PartialCollection<YValueHint> _values = typeUtil.getHintValues(type, getSchemaContext());
if (_values.getExplanation()!=null && _values.getElements().isEmpty() && !Boolean.getBoolean("lsp.yaml.completions.errors.disable")) {
return ImmutableList.of(completionFactory().errorMessage(query, getMessage(_values.getExplanation())));
}
Collection<YValueHint> values = _values.getElements();
if (values!=null) {
ArrayList<ICompletionProposal> completions = new ArrayList<>();
YamlIndentUtil indenter = new YamlIndentUtil(doc);
@@ -243,7 +239,7 @@ public class YTypeAssistContext extends AbstractYamlAssistContext {
return Collections.emptyList();
}
private String getMessage(Exception _e) {
private String getMessage(Throwable _e) {
Throwable e = ExceptionUtil.getDeepestCause(_e);
// If value parse exception, do not append any additional information

View File

@@ -29,8 +29,11 @@ import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileException;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReplacementQuickfix;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.PartialCollection;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParser;
@@ -38,6 +41,7 @@ import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableMap;
@@ -185,7 +189,7 @@ public class YTypeFactory {
}
@Override
public YValueHint[] getHintValues(YType type, DynamicSchemaContext dc) throws Exception {
public PartialCollection<YValueHint> getHintValues(YType type, DynamicSchemaContext dc) {
return ((AbstractType)type).getHintValues(dc);
}
@@ -257,7 +261,7 @@ public class YTypeFactory {
private List<YTypedProperty> propertyList = new ArrayList<>();
private List<YValueHint> hints = new ArrayList<>();
private Map<String, YTypedProperty> cachedPropertyMap;
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
private SchemaContextAware<PartialCollection<YValueHint>> hintProvider;
//TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware<Collection<YValueHint>>
private List<Constraint> constraints = new ArrayList<>(2);
@@ -293,47 +297,19 @@ public class YTypeFactory {
}
public AbstractType setHintProvider(Callable<Collection<YValueHint>> hintProvider) {
setHintProvider((DynamicSchemaContext dc) -> hintProvider);
setHintProvider((DynamicSchemaContext dc) -> PartialCollection.compute(hintProvider));
return this;
}
public AbstractType setHintProvider(SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider) {
public AbstractType setHintProvider(SchemaContextAware<PartialCollection<YValueHint>> hintProvider) {
//TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware<Collection<YValueHint>>
this.hintProvider = hintProvider;
return this;
}
public YValueHint[] getHintValues(DynamicSchemaContext dc) throws Exception {
Collection<YValueHint> providerHints = null;
try {
providerHints=getProviderHints(dc);
} catch (Exception e) {
if (!hints.isEmpty()) {
Log.log(e);
//Recover from error returning just the static hints.
return hints.toArray(new YValueHint[hints.size()]);
} else {
throw e;
}
}
if (providerHints == null || providerHints.isEmpty()) {
return hints.toArray(new YValueHint[hints.size()]);
} else {
// Only merge if there are provider hints to merge
Set<YValueHint> mergedHints = new LinkedHashSet<>();
// Add type hints first
for (YValueHint val : hints) {
mergedHints.add(val);
}
// merge the provider hints
for (YValueHint val : providerHints) {
mergedHints.add(val);
}
return mergedHints.toArray(new YValueHint[mergedHints.size()]);
}
public PartialCollection<YValueHint> getHintValues(DynamicSchemaContext dc) {
return getProviderHints(dc)
.addAll(hints);
}
/**
@@ -343,14 +319,15 @@ public class YTypeFactory {
hints = ImmutableList.copyOf(hints);
}
private Collection<YValueHint> getProviderHints(DynamicSchemaContext dc) throws Exception {
private PartialCollection<YValueHint> getProviderHints(DynamicSchemaContext dc) {
if (hintProvider != null) {
Callable<Collection<YValueHint>> withContext = hintProvider.withContext(dc);
if (withContext != null) {
return withContext.call();
try {
return hintProvider.withContext(dc);
} catch (Exception e) {
return PartialCollection.unknown(e);
}
}
return ImmutableList.of();
return PartialCollection.empty();
}
public List<Constraint> getConstraints() {
@@ -417,7 +394,7 @@ public class YTypeFactory {
parseWith((DynamicSchemaContext dc) -> parser);
return this;
}
private SchemaContextAware<ValueParser> getParser() {
public SchemaContextAware<ValueParser> getParser() {
return parser;
}
@@ -878,22 +855,26 @@ public class YTypeFactory {
return ((YTypedPropertyImpl)prop).copy();
}
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<YValueHint>> values) {
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<PartialCollection<YValueHint>> values) {
YAtomicType t = yatomic(name);
t.setHintProvider((dc) -> () -> values.withContext(dc));
t.setHintProvider(values);
t.parseWith((DynamicSchemaContext dc) -> {
Collection<String> strings = YTypeFactory.values(values.withContext(dc));
return new EnumValueParser(name, strings) {
@Override
protected String createErrorMessage(String parseString, Collection<String> values) {
return errorMessageFormatter.apply(parseString, values);
}
};
PartialCollection<YValueHint> hints = values.withContext(dc);
if (hints.isComplete()) {
Collection<String> strings = values(hints.getElements());
return new EnumValueParser(name, strings) {
@Override
protected String createErrorMessage(String parseString, Collection<String> values) {
return errorMessageFormatter.apply(parseString, values);
}
};
}
return null;
});
return t;
}
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<Collection<String>> values) {
public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware<PartialCollection<String>> values) {
return yenumFromHints(name,
//Error message formatter:
(parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
@@ -909,12 +890,8 @@ public class YTypeFactory {
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
YAtomicType t = yatomic(name);
t.setHintProvider((dc) -> {
Collection<String> strings = values.withContext(dc);
return strings==null
? null
: () -> strings.stream()
.map((s) -> new BasicYValueHint(s))
.collect(Collectors.toSet());
return PartialCollection.compute(() -> values.withContext(dc))
.map(BasicYValueHint::new);
});
t.parseWith((DynamicSchemaContext dc) -> {
EnumValueParser enumParser = new EnumValueParser(name, values.withContext(dc)) {
@@ -929,7 +906,7 @@ public class YTypeFactory {
}
public static Collection<String> values(Collection<YValueHint> hints) {
return hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
return hints == null ? null : hints.stream().map(YValueHint::getValue).collect(Collectors.toList());
}
public YAtomicType yenum(String name, String... values) {
@@ -960,10 +937,16 @@ public class YTypeFactory {
}
public static Collection<YValueHint> hints(Collection<String> values) {
return values.stream()
.map(YTypeFactory::hint)
.collect(CollectorUtil.toMultiset());
}
public static PartialCollection<YValueHint> hints(PartialCollection<String> values) {
if (values!=null) {
return values.stream().map(YTypeFactory::hint).collect(Collectors.toList());
return values.map(YTypeFactory::hint);
}
return null;
return PartialCollection.unknown();
}
public YTypeFactory enableTieredProposals(boolean enable) {

View File

@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.schema;
import java.util.List;
import java.util.Map;
import org.springframework.ide.vscode.commons.util.PartialCollection;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
@@ -30,7 +31,7 @@ public interface YTypeUtil {
boolean isSequencable(YType type);
boolean isBean(YType type);
YType getDomainType(YType type);
YValueHint[] getHintValues(YType yType, DynamicSchemaContext dc) throws Exception;
PartialCollection<YValueHint> getHintValues(YType yType, DynamicSchemaContext dc);
String niceTypeName(YType type);
YType getKeyType(YType type);
SchemaContextAware<ValueParser> getValueParser(YType type);
@@ -47,9 +48,9 @@ public interface YTypeUtil {
*/
YType inferMoreSpecificType(YType type, DynamicSchemaContext dc);
List<Constraint> getConstraints(YType type);
ISubCompletionEngine getCustomContentAssistant(YType type);
/**
* Config option for type-based completion engine. This enables the
* 'tiered' proposals feature (so that optional properties are not
@@ -58,7 +59,7 @@ public interface YTypeUtil {
boolean tieredOptionalPropertyProposals();
/**
* Config option for type-based completion engine. This enables/disables
* whether engine should generate proposals for deprecated properties (true),
* whether engine should generate proposals for deprecated properties (true),
* or suppress them (false).
*/
boolean suggestDeprecatedProperties();

View File

@@ -19,6 +19,7 @@ import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.MimeTypes;
import org.springframework.ide.vscode.commons.util.PartialCollection;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParseException;
@@ -174,7 +175,7 @@ public class PipelineYmlSchema implements YamlSchema {
return "The '"+parseString+"' Resource Type does not exist. Existing types: "+validValues;
},
(DynamicSchemaContext dc) -> {
return models.getResourceTypeNameHints(dc);
return PartialCollection.compute(() -> models.getResourceTypeNameHints(dc));
}
);
@@ -190,8 +191,8 @@ public class PipelineYmlSchema implements YamlSchema {
t_maybe_resource_name.setHintProvider((DynamicSchemaContext dc) -> {
//Putting the Callable into a local variable is strange, but the compiler doesn't like it if
// we return it directly. Too much complexity for Java type-inference?
Callable<Collection<YValueHint>> callable = () -> YTypeFactory.hints(models.getResourceNames(dc));
return callable;
PartialCollection<YValueHint> hints = PartialCollection.compute(() -> YTypeFactory.hints(models.getResourceNames(dc)));
return hints;
});
t_maybe_resource_name.parseWith(ValueParsers.NE_STRING);