This commit is contained in:
BoykoAlex
2017-07-25 19:31:58 -04:00
7 changed files with 188 additions and 59 deletions

View File

@@ -14,11 +14,13 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.Predicate;
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.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;
@@ -33,6 +35,7 @@ import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
import org.springframework.ide.vscode.commons.yaml.reconcile.ASTTypeCache;
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
@@ -193,20 +196,40 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
YBeanType t_stemcell = f.ybean("Stemcell");
YType t_stemcell_name_ref = f.yenumFromDynamicValues("StemcellName", (dc) -> stemcellsProvider.getModel(dc).getStemcellNames());
YType t_stemcell_os_ref = f.yenumFromDynamicValues("StemcellOs", (dc) -> stemcellsProvider.getModel(dc).getStemcellOss());
YType t_stemcell_version_ref = f.yenumFromDynamicValues("StemcellVersion", (dc) -> {
StemcellModel currentStemcell = getCurrentStemcell(dc);
if (currentStemcell!=null) {
return stemcellsProvider.getModel(dc).getStemcells().stream()
.filter(sc -> StringUtil.hasText(sc.getVersion()))
.filter(currentStemcell.createVersionFilter())
.map(sc -> sc.getVersion())
.collect(CollectorUtil.toImmutableSet());
YType t_stemcell_name_ref = f.yenumFromDynamicValues("StemcellName", (dc) ->
stemcellsProvider.getModel(dc).getStemcellNames()
);
YType t_stemcell_os_ref = f.yenumFromDynamicValues("StemcellOs", (dc) ->
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());
{
baseType.addHints("latest");
baseType.alsoAccept("latest");
}
//Troubles determining the filter. So return all stemcells.
return stemcellsProvider.getModel(dc).getVersions();
});
@Override
public YType withContext(DynamicSchemaContext dc) throws Exception {
StemcellModel currentStemcell = getCurrentStemcell(dc);
if (StringUtil.hasText(currentStemcell.getName())||StringUtil.hasText(currentStemcell.getOs())) {
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());
});
filteredType.addHints("latest");
filteredType.alsoAccept("latest");
return filteredType;
}
return baseType;
}
}).treatAsAtomic();
addProp(t_stemcell, "alias", t_stemcell_alias_def).isRequired(true);
addProp(t_stemcell, "version", t_stemcell_version_ref).isRequired(true);
@@ -260,7 +283,7 @@ public class BoshDeploymentManifestSchema implements YamlSchema {
YBeanType t_variable = f.ybean("Variable");
addProp(t_variable, "name", t_var_name_def).isPrimary(true);
YType t_variable_type = f.yenum("VariableType", "certificate", "password", "rsa", "ssh")
.parseWith(ValueParsers.NE_STRING); //Overrid the parser -> no errors / warnings... in theory there could be other valid values.
.parseWith(ValueParsers.NE_STRING); //Override the parser -> no errors / warnings... in theory there could be other valid values.
addProp(t_variable, "type", t_variable_type).isRequired(true);
addProp(t_variable, "options", t_params);
addProp(v2Schema, "variables", f.yseq(t_variable));

View File

@@ -13,13 +13,14 @@ package org.springframework.ide.vscode.bosh;
import java.io.IOException;
import org.springframework.ide.vscode.bosh.models.BoshCommandCloudConfigProvider;
import org.springframework.ide.vscode.bosh.models.BoshCommandStemcellsProvider;
import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp;
public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
LaunguageServerApp.start(() -> new BoshLanguageServer(
new BoshCommandCloudConfigProvider(),
(dc) -> null //TODO: real model provider here!
new BoshCommandStemcellsProvider()
));
}
}

View File

@@ -45,17 +45,50 @@ public class StemcellModel {
if (StringUtil.hasText(name) && StringUtil.hasText(os)) {
//User shouldn't really specify both of these... but if they do... then
// let's do our best to generate proposals for the both of them.
return (sc) ->
name.equals(sc.getName()) || os.equals(sc.getOs());
return new Predicate<StemcellData>() {
@Override
public boolean test(StemcellData sc) {
return name.equals(sc.getName()) || os.equals(sc.getOs());
}
@Override
public String toString() {
return "name="+name+", os="+os;
}
};
} else if (StringUtil.hasText(name)) {
return (sc) ->
name.equals(sc.getName());
return new Predicate<StemcellData>() {
@Override
public boolean test(StemcellData sc) {
return name.equals(sc.getName());
}
@Override
public String toString() {
return "name="+name;
}
};
} else if (StringUtil.hasText(os)) {
return (sc) ->
os.equals(sc.getOs());
return new Predicate<StemcellData>() {
@Override
public boolean test(StemcellData sc) {
return os.equals(sc.getOs());
}
@Override
public String toString() {
return "os="+os;
}
};
} else {
return (sc) ->
true;
return new Predicate<StemcellData>() {
@Override
public boolean test(StemcellData sc) {
return true;
}
@Override
public String toString() {
return "true";
}
};
}
}

View File

@@ -954,6 +954,22 @@ public class BoshEditorTest {
);
}
@SuppressWarnings("unchecked")
@Test public void contentAssistStemcellVersionNoDirector() throws Exception {
Editor editor;
stemcellsProvider = mock(DynamicModelProvider.class);
when(stemcellsProvider.getModel(any())).thenThrow(new IOException("Couldn't connect to bosh"));
editor = harness.newEditor(
"stemcells:\n" +
"- alias: good\n" +
" name: centos-agent\n" +
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"latest<*>"
);
}
@Test public void contentAssistStemcellVersionFromDirector() throws Exception {
Editor editor;
stemcellsProvider = provideStemcellsFrom(
@@ -970,7 +986,7 @@ public class BoshEditorTest {
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"222.2<*>", "333.3<*>"
"222.2<*>", "333.3<*>", "latest<*>"
);
editor = harness.newEditor(
@@ -980,7 +996,7 @@ public class BoshEditorTest {
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"123.4<*>", "222.2<*>"
"123.4<*>", "222.2<*>", "latest<*>"
);
editor = harness.newEditor(
@@ -989,10 +1005,10 @@ public class BoshEditorTest {
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"123.4<*>", "222.2<*>", "333.3<*>"
"123.4<*>", "222.2<*>", "333.3<*>", "latest<*>"
);
//when os or name are 'bogus' at least suggest proposals based on other prop
//when os and name are 'bogus' at least suggest proposals based on other prop
editor = harness.newEditor(
"stemcells:\n" +
"- alias: good\n" +
@@ -1001,7 +1017,7 @@ public class BoshEditorTest {
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"222.2<*>", "333.3<*>"
"222.2<*>", "333.3<*>", "latest<*>"
);
editor = harness.newEditor(
@@ -1012,7 +1028,7 @@ public class BoshEditorTest {
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"222.2<*>", "333.3<*>"
"222.2<*>", "333.3<*>", "latest<*>"
);
//when the os and name disagree, merge the proposals for both:
@@ -1024,10 +1040,34 @@ public class BoshEditorTest {
" version: <*>\n"
);
editor.assertContextualCompletions("<*>",
"123.4<*>", "222.2<*>", "333.3<*>"
"123.4<*>", "222.2<*>", "333.3<*>", "latest<*>"
);
}
@SuppressWarnings("unchecked")
@Test public void reconcileStemcellVersionNoDirector() throws Exception {
Editor editor;
stemcellsProvider = mock(DynamicModelProvider.class);
when(stemcellsProvider.getModel(any())).thenThrow(new IOException("Couldn't connect to bosh"));
editor = harness.newEditor(
"stemcells:\n" +
"- alias: aaa\n" +
" name: centos-agent\n" +
" version: 222.2\n" +
"- alias: bbb\n" +
" name: centos-agent\n" +
" version: 123.4\n" +
"- alias: ddd\n" +
" os: ubuntu\n" +
" version: 333.3\n" +
"- alias: eee\n" +
" os: ubuntu\n" +
" version: latest\n"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
editor.assertProblems(/*NONE*/);
}
@Test public void reconcileStemcellVersionFromDirector() throws Exception {
Editor editor;
stemcellsProvider = provideStemcellsFrom(
@@ -1047,12 +1087,15 @@ public class BoshEditorTest {
" version: 123.4\n" +
"- alias: ddd\n" +
" os: ubuntu\n" +
" version: 333.3\n"
" version: 333.3\n" +
"- alias: eee\n" +
" os: ubuntu\n" +
" version: latest\n"
);
editor.ignoreProblem(YamlSchemaProblems.MISSING_PROPERTY);
editor.assertProblems(
"123.4|unknown 'StemcellVersion'. Valid values are: [222.2, 333.3]",
"333.3|unknown 'StemcellVersion'. Valid values are: [123.4, 222.2]"
"123.4|unknown 'StemcellVersion[name=centos-agent]'. Valid values are: [222.2, 333.3]",
"333.3|unknown 'StemcellVersion[os=ubuntu]'. Valid values are: [123.4, 222.2]"
);
}

View File

@@ -11,6 +11,7 @@
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.HashMap;
@@ -24,12 +25,12 @@ import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
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.EnumValueParser;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParser;
@@ -291,19 +292,30 @@ public class YTypeFactory {
return null;
}
public AbstractType addHintProvider(Callable<Collection<YValueHint>> hintProvider) {
addHintProvider((DynamicSchemaContext dc) -> hintProvider);
public AbstractType setHintProvider(Callable<Collection<YValueHint>> hintProvider) {
setHintProvider((DynamicSchemaContext dc) -> hintProvider);
return this;
}
public AbstractType addHintProvider(SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider) {
public AbstractType setHintProvider(SchemaContextAware<Callable<Collection<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=getProviderHints(dc);
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()]);
@@ -324,6 +336,9 @@ public class YTypeFactory {
}
}
/**
* Prevents adding additional hints.
*/
public void sealHints() {
hints = ImmutableList.copyOf(hints);
}
@@ -382,23 +397,16 @@ public class YTypeFactory {
addProperty(new YTypedPropertyImpl(name, type));
}
public AbstractType addHints(String... strings) {
if (strings != null) {
for (String value : strings) {
BasicYValueHint hint = new BasicYValueHint(value);
if (!hints.contains(hint)) {
hints.add(hint);
}
}
}
return this;
return addHints(Arrays.stream(strings).map(s -> new BasicYValueHint(s)).toArray(BasicYValueHint[]::new));
}
public void addHints(YValueHint... extraHints) {
public AbstractType addHints(YValueHint... extraHints) {
for (YValueHint h : extraHints) {
if (!hints.contains(h)) {
hints.add(h);
}
}
return this;
}
public void parseWith(SchemaContextAware<ValueParser> parser) {
@@ -413,6 +421,29 @@ public class YTypeFactory {
return parser;
}
/**
* Modifies currently installed parser so it is guaranteed to accept at least given values.
* @return
*/
public AbstractType alsoAccept(String... _values) {
if (parser!=null) {
ImmutableSet<String> values = ImmutableSet.copyOf(_values);
final SchemaContextAware<ValueParser> oldParserProvider = parser;
parser = (dc) -> (s) -> {
if (values.contains(s)) {
return s;
} else {
ValueParser oldParser = oldParserProvider.safeWithContext(dc).orElse(null);
if (oldParser!=null) {
return oldParser.parse(s);
}
return s;
}
};
}
return this;
}
public AbstractType require(Constraint dynamicConstraint) {
this.constraints.add(dynamicConstraint);
return this;
@@ -849,7 +880,7 @@ public class YTypeFactory {
public YAtomicType yenumFromHints(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<YValueHint>> values) {
YAtomicType t = yatomic(name);
t.addHintProvider((dc) -> () -> values.withContext(dc));
t.setHintProvider((dc) -> () -> values.withContext(dc));
t.parseWith((DynamicSchemaContext dc) -> {
Collection<String> strings = YTypeFactory.values(values.withContext(dc));
return new EnumValueParser(name, strings) {
@@ -867,9 +898,7 @@ public class YTypeFactory {
//Error message formatter:
(parseString, validValues) -> "'"+parseString+"' is an unknown '"+name+"'. Valid values are: "+validValues,
//Hints provider:
(dc) ->
hints(values.withContext(dc)
)
(dc) -> hints(values.withContext(dc))
);
}
@@ -879,7 +908,7 @@ public class YTypeFactory {
public YAtomicType yenum(String name, BiFunction<String, Collection<String>, String> errorMessageFormatter, SchemaContextAware<Collection<String>> values) {
YAtomicType t = yatomic(name);
t.addHintProvider((dc) -> {
t.setHintProvider((dc) -> {
Collection<String> strings = values.withContext(dc);
return strings==null
? null

View File

@@ -118,7 +118,7 @@ public class PipelineYmlSchema implements YamlSchema {
public final YType t_location = f.yatomic("Location")
//Note: we could have used f.yenum here too. But it saves memory if we don't keep the large set of ValueHints in memory.
// That's why we attach custom hint provider and parser here that do essentially the same thing.
.addHintProvider(() -> {
.setHintProvider(() -> {
return ZoneId.getAvailableZoneIds().stream()
.map(BasicYValueHint::new)
.collect(Collectors.toList());
@@ -187,7 +187,7 @@ public class PipelineYmlSchema implements YamlSchema {
}
);
t_maybe_resource_name = f.yatomic("ResourceName | TaskOutput");
t_maybe_resource_name.addHintProvider((DynamicSchemaContext dc) -> {
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));

View File

@@ -122,25 +122,25 @@ public final class ManifestYmlSchema implements YamlSchema {
YAtomicType t_buildpack = f.yatomic("Buildpack");
if (buildpackProvider != null) {
t_buildpack.addHintProvider(buildpackProvider);
t_buildpack.setHintProvider(buildpackProvider);
// t_buildpack.parseWith(ManifestYmlValueParsers.fromHints(t_buildpack.toString(), buildpackProvider));
}
YAtomicType t_stack = f.yatomic("Stack");
if (stacksProvider!=null) {
t_stack.addHintProvider(stacksProvider);
t_stack.setHintProvider(stacksProvider);
t_stack.parseWith(ManifestYmlValueParsers.fromValueHints(stacksProvider, t_stack, ManifestYamlSchemaProblemsTypes.UNKNOWN_STACK_PROBLEM));
}
YAtomicType t_domain = f.yatomic("Domain");
if (domainsProvider != null) {
t_domain.addHintProvider(domainsProvider);
t_domain.setHintProvider(domainsProvider);
t_domain.parseWith(ManifestYmlValueParsers.fromValueHints(domainsProvider, t_domain, ManifestYamlSchemaProblemsTypes.UNKNOWN_DOMAIN_PROBLEM));
}
YAtomicType t_service = f.yatomic("Service");
if (servicesProvider != null) {
t_service.addHintProvider(servicesProvider);
t_service.setHintProvider(servicesProvider);
t_service.parseWith(new CFServicesValueParser(t_service.toString(),
YTypeFactory.valuesFromHintProvider(servicesProvider)));
}