From 724ae4aed446c3a0ad12a738ed42c64de6580dc9 Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 28 Jul 2017 12:01:13 -0700 Subject: [PATCH] More tweaks to dynamic releases attributes reconcile and CA --- .../bosh/BoshDeploymentManifestSchema.java | 89 +++++++++--- .../bosh/models/CachingModelProvider.java | 6 + .../ide/vscode/bosh/BoshEditorTest.java | 31 +++- .../vscode/commons/util/EnumValueParser.java | 32 ++--- .../commons/util/PartialCollection.java | 135 ++++++++++++++++++ .../yaml/completion/YTypeAssistContext.java | 16 +-- .../commons/yaml/schema/YTypeFactory.java | 103 ++++++------- .../vscode/commons/yaml/schema/YTypeUtil.java | 9 +- .../vscode/concourse/PipelineYmlSchema.java | 7 +- 9 files changed, 310 insertions(+), 118 deletions(-) create mode 100644 headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java index 371c22440..8e2b1a08c 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/BoshDeploymentManifestSchema.java @@ -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 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() { + 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() { - 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 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 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; diff --git a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java index a1fbef3e6..c5679b43f 100644 --- a/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java +++ b/headless-services/bosh-language-server/src/main/java/org/springframework/ide/vscode/bosh/models/CachingModelProvider.java @@ -92,6 +92,12 @@ public class CachingModelProvider implements DynamicModelProvider { @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> 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). diff --git a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java index 859ee7603..1a2a19f92 100644 --- a/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java +++ b/headless-services/bosh-language-server/src/test/java/org/springframework/ide/vscode/bosh/BoshEditorTest.java @@ -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); diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java index 0edd2a742..eec309abf 100644 --- a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/EnumValueParser.java @@ -26,7 +26,7 @@ import com.google.common.collect.ImmutableSet; public class EnumValueParser implements ValueParser { private String typeName; - private Provider> values; + private Provider> 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 Provider> provider(Collection values) { + return () -> PartialCollection.compute(() -> values); + } + private static Provider> provider(Callable> values) { + return () -> PartialCollection.compute(() -> values.call()); + } + public EnumValueParser(String typeName, Callable> values) { this(typeName, provider(values)); } - public EnumValueParser(String typeName, Provider> values) { + + public EnumValueParser(String typeName, Provider> values) { this.typeName = typeName; this.values = values; } @@ -54,13 +62,13 @@ public class EnumValueParser implements ValueParser { throw errorOnBlank(createBlankTextErrorMessage()); } - Collection values = this.values.get(); + PartialCollection 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 Provider provider(T values) { - return () -> values; - } - - private static Provider provider(Callable values) { - return () -> { - try { - return values.call(); - } catch (Exception e) { - // Ignore - return null; - } - }; - } } diff --git a/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java new file mode 100644 index 000000000..48d1e2ac3 --- /dev/null +++ b/headless-services/commons/commons-util/src/main/java/org/springframework/ide/vscode/commons/util/PartialCollection.java @@ -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. + *

+ * For unknown collection, an optional explanation, in the + * form of a caught exception may be stored as well. + */ +public class PartialCollection { + + private static final PartialCollection UNKNOWN = new PartialCollection<>(ImmutableSet.of(), false); + private static final PartialCollection EMPTY = new PartialCollection<>(ImmutableSet.of(), true); + + final private ImmutableCollection knownElements; + final private boolean isComplete; + final private Throwable explanation; + + private PartialCollection(ImmutableCollection knownElements, boolean isComplete, Throwable error) { + this.knownElements = knownElements; + this.isComplete = isComplete; + this.explanation = error; + } + + private PartialCollection(ImmutableCollection knownElements, boolean isComplete) { + this.knownElements = knownElements; + this.isComplete = isComplete; + this.explanation = null; + } + + private PartialCollection(ImmutableCollection 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 PartialCollection compute(Callable> computer) { + try { + Collection 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 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 PartialCollection unknown() { + return (PartialCollection) UNKNOWN; + } + + /** + * Like map on streams, but silently drops any null elements returned by the mapper. + */ + public PartialCollection map(Function mapper) { + ImmutableSet mappedElements = getElements().stream().map((x) -> mapper.apply(x)).filter(x -> x!=null).collect(CollectorUtil.toImmutableSet()); + return new PartialCollection(mappedElements, isComplete, explanation); + } + + /** + * Returns a empty collection (i.e. the collection is know to be empty). + */ + @SuppressWarnings("unchecked") + public static PartialCollection empty() { + return (PartialCollection) EMPTY; + } + + /** + * Make a copy of this collection that has the same known elements but also has unknown elements. + */ + public PartialCollection 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 PartialCollection unknown(Exception e) { + Assert.isLegal(e!=null); + return new PartialCollection<>(ImmutableSet.of(), e); + } + + public PartialCollection addAll(Collection moreElements) { + ImmutableSet.Builder elements = ImmutableSet.builder(); + elements.addAll(getElements()); + elements.addAll(moreElements); + return new PartialCollection<>(elements.build(), isComplete, explanation); + } + + public Throwable getExplanation() { + return explanation; + } + +} diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java index 34e70f62b..45ede6c40 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/completion/YTypeAssistContext.java @@ -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 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 _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 values = _values.getElements(); if (values!=null) { ArrayList 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 diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java index eff8b1eb2..5832b8b05 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeFactory.java @@ -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 getHintValues(YType type, DynamicSchemaContext dc) { return ((AbstractType)type).getHintValues(dc); } @@ -257,7 +261,7 @@ public class YTypeFactory { private List propertyList = new ArrayList<>(); private List hints = new ArrayList<>(); private Map cachedPropertyMap; - private SchemaContextAware>> hintProvider; + private SchemaContextAware> hintProvider; //TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware> private List constraints = new ArrayList<>(2); @@ -293,47 +297,19 @@ public class YTypeFactory { } public AbstractType setHintProvider(Callable> hintProvider) { - setHintProvider((DynamicSchemaContext dc) -> hintProvider); + setHintProvider((DynamicSchemaContext dc) -> PartialCollection.compute(hintProvider)); return this; } - public AbstractType setHintProvider(SchemaContextAware>> hintProvider) { + public AbstractType setHintProvider(SchemaContextAware> hintProvider) { //TODO: SchemaContextAware now allows throwing exceptions so should be able to simplify the above to SchemaContextAware> this.hintProvider = hintProvider; return this; } - public YValueHint[] getHintValues(DynamicSchemaContext dc) throws Exception { - Collection 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 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 getHintValues(DynamicSchemaContext dc) { + return getProviderHints(dc) + .addAll(hints); } /** @@ -343,14 +319,15 @@ public class YTypeFactory { hints = ImmutableList.copyOf(hints); } - private Collection getProviderHints(DynamicSchemaContext dc) throws Exception { + private PartialCollection getProviderHints(DynamicSchemaContext dc) { if (hintProvider != null) { - Callable> 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 getConstraints() { @@ -417,7 +394,7 @@ public class YTypeFactory { parseWith((DynamicSchemaContext dc) -> parser); return this; } - private SchemaContextAware getParser() { + public SchemaContextAware getParser() { return parser; } @@ -878,22 +855,26 @@ public class YTypeFactory { return ((YTypedPropertyImpl)prop).copy(); } - public YAtomicType yenumFromHints(String name, BiFunction, String> errorMessageFormatter, SchemaContextAware> values) { + public YAtomicType yenumFromHints(String name, BiFunction, String> errorMessageFormatter, SchemaContextAware> values) { YAtomicType t = yatomic(name); - t.setHintProvider((dc) -> () -> values.withContext(dc)); + t.setHintProvider(values); t.parseWith((DynamicSchemaContext dc) -> { - Collection strings = YTypeFactory.values(values.withContext(dc)); - return new EnumValueParser(name, strings) { - @Override - protected String createErrorMessage(String parseString, Collection values) { - return errorMessageFormatter.apply(parseString, values); - } - }; + PartialCollection hints = values.withContext(dc); + if (hints.isComplete()) { + Collection strings = values(hints.getElements()); + return new EnumValueParser(name, strings) { + @Override + protected String createErrorMessage(String parseString, Collection values) { + return errorMessageFormatter.apply(parseString, values); + } + }; + } + return null; }); return t; } - public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware> values) { + public YAtomicType yenumFromDynamicValues(String name, SchemaContextAware> 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> errorMessageFormatter, SchemaContextAware> values) { YAtomicType t = yatomic(name); t.setHintProvider((dc) -> { - Collection 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 values(Collection 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 hints(Collection values) { + return values.stream() + .map(YTypeFactory::hint) + .collect(CollectorUtil.toMultiset()); + } + + public static PartialCollection hints(PartialCollection 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) { diff --git a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java index 7688e9099..4a572ba61 100644 --- a/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java +++ b/headless-services/commons/commons-yaml/src/main/java/org/springframework/ide/vscode/commons/yaml/schema/YTypeUtil.java @@ -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 getHintValues(YType yType, DynamicSchemaContext dc); String niceTypeName(YType type); YType getKeyType(YType type); SchemaContextAware getValueParser(YType type); @@ -47,9 +48,9 @@ public interface YTypeUtil { */ YType inferMoreSpecificType(YType type, DynamicSchemaContext dc); List 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(); diff --git a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index f14a9c75e..2f141b2d0 100644 --- a/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/headless-services/concourse-language-server/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -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> callable = () -> YTypeFactory.hints(models.getResourceNames(dc)); - return callable; + PartialCollection hints = PartialCollection.compute(() -> YTypeFactory.hints(models.getResourceNames(dc))); + return hints; }); t_maybe_resource_name.parseWith(ValueParsers.NE_STRING);