From 4ea3e8371f85e62b7658b40609d347af3392376b Mon Sep 17 00:00:00 2001 From: Kris De Volder Date: Fri, 23 Dec 2016 18:57:39 -0800 Subject: [PATCH] Concourse: reconciler checks for duplicate resource names --- .../ide/vscode/concourse/ConcourseModel.java | 12 ++-- .../vscode/concourse/PipelineYmlSchema.java | 5 +- .../ide/vscode/concourse/ValueParsers.java | 16 +++++ .../vscode/concourse/util/CollectorUtil.java | 67 +++++++++++++++++++ .../concourse/PipelineYamlEditorTest.java | 23 +++++++ 5 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/util/CollectorUtil.java diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java index f1b5b6e77..bc4882e76 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ConcourseModel.java @@ -27,11 +27,15 @@ import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider; import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST; import org.springframework.ide.vscode.commons.yaml.ast.YamlParser; import org.springframework.ide.vscode.commons.yaml.path.YamlPath; +import org.springframework.ide.vscode.concourse.util.CollectorUtil; import org.springframework.ide.vscode.concourse.util.StaleFallbackCache; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.error.YAMLException; import org.yaml.snakeyaml.nodes.Node; +import com.google.common.collect.Multiset; +import com.google.common.collect.Multisets; + /** * ConcourseModels is responsible for extracting various bits of information * out of .yml documents and caching them for use by various tools (reconcile engine @@ -76,7 +80,7 @@ public class ConcourseModel { * names (e.g. because there hasn't been a successful parse yet and current document contents * can not be parsed). */ - public Set getResourceNames(IDocument doc) { + public Multiset getResourceNames(IDocument doc) { return getStringsFromAst(doc, RESOURCE_NAMES_PATH); } @@ -90,18 +94,18 @@ public class ConcourseModel { * names (e.g. because there hasn't been a successful parse yet and current document contents * can not be parsed). */ - public Set getJobNames(IDocument doc) { + public Multiset getJobNames(IDocument doc) { return getStringsFromAst(doc, JOB_NAMES_PATH); } - private Set getStringsFromAst(IDocument doc, YamlPath path) { + private Multiset getStringsFromAst(IDocument doc, YamlPath path) { return getFromAst(doc, (ast) -> { Node root = ast.get(0); return path .traverseAmbiguously(root) .map(NodeUtil::asScalar) .filter((string) -> string!=null) - .collect(Collectors.toSet()); + .collect(CollectorUtil.toMultiset()); }); } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java index e32578aa2..2267b2f6a 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/PipelineYmlSchema.java @@ -106,6 +106,9 @@ public class PipelineYmlSchema implements YamlSchema { return models.getJobNames(dc.getDocument()); } ); + + YAtomicType resourceNameDef = f.yatomic("Resource Name"); + resourceNameDef.parseWith(ValueParsers.resourceNameDef(models)); YBeanType getStep = f.ybean("GetStep"); prop(getStep, "get", resourceName); @@ -157,7 +160,7 @@ public class PipelineYmlSchema implements YamlSchema { prop(step, "timeout", t_duration); YBeanType resource = f.ybean("Resource"); - prop(resource, "name", t_ne_string); + prop(resource, "name", resourceNameDef); prop(resource, "type", t_resource_type_name); prop(resource, "source", t_any); diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java index 7b1ea3743..5368ff505 100644 --- a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/ValueParsers.java @@ -14,6 +14,9 @@ import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.RegexpParser; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.ValueParser; +import org.springframework.ide.vscode.commons.yaml.schema.SchemaContextAware; + +import com.google.common.collect.Multiset; /** * Methods and constants to create/get parsers for some atomic types @@ -33,6 +36,19 @@ public class ValueParsers { public static final ValueParser POS_INTEGER = integerRange(0, null); + public static final SchemaContextAware resourceNameDef(ConcourseModel models) { + return (dc) -> { + Multiset resourceNames = models.getResourceNames(dc.getDocument()); + return (String input) -> { + if (resourceNames.count(input)<=1) { + //okay + return resourceNames; + } + throw new IllegalArgumentException("Duplicate resource name '"+input+"'"); + }; + }; + }; + public static ValueParser integerAtLeast(final Integer lowerBound) { return integerRange(lowerBound, null); } diff --git a/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/util/CollectorUtil.java b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/util/CollectorUtil.java new file mode 100644 index 000000000..2ce3160f7 --- /dev/null +++ b/vscode-extensions/vscode-concourse/src/main/java/org/springframework/ide/vscode/concourse/util/CollectorUtil.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.concourse.util; + +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; + +import com.google.common.collect.HashMultiset; +import com.google.common.collect.ImmutableMultiset; +import com.google.common.collect.ImmutableSet; + +/** + * Stuff missing from {@link Collectors} that we implement ourself. + */ +public class CollectorUtil { + + /** + * Collects elements into a ImmutableMutiset (the set is converted to an immutable one + * at the end. Accumulating / combining is done with a mutable Multiset because that involves + * less copying.) + */ + public static Collector, ImmutableMultiset> toMultiset() { + return new Collector, ImmutableMultiset>() { + + @Override + public Supplier> supplier() { + return HashMultiset::create; + } + + @Override + public BiConsumer, T> accumulator() { + return (a, e) -> a.add(e); + } + + @Override + public BinaryOperator> combiner() { + return (a1, a2) -> { + a1.addAll(a2); + return a1; + }; + } + @Override + public Function, ImmutableMultiset> finisher() { + return ImmutableMultiset::copyOf; + } + + @Override + public Set characteristics() { + return ImmutableSet.of(Collector.Characteristics.UNORDERED); + } + }; + } + +} diff --git a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java index 8be2700ee..7ed9b2e08 100644 --- a/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java +++ b/vscode-extensions/vscode-concourse/src/test/java/org/springframework/ide/vscode/concourse/PipelineYamlEditorTest.java @@ -479,6 +479,29 @@ public class PipelineYamlEditorTest { ); } + @Test + public void reconcileDuplicateResourceNames() throws Exception { + Editor editor = harness.newEditor( + "resources:\n" + + "- name: sts4\n" + + " type: git\n" + + " source:\n" + + " repository: https://github.com/kdvolder/somestuff\n" + + "- name: utils\n" + + " type: git\n" + + " source:\n" + + " repository: https://github.com/kdvolder/someutils\n" + + "- name: sts4\n" + + " type: git\n" + + " source:\n" + + " repository: https://github.com/kdvolder/extras\n" + ); + editor.assertProblems( + "sts4|Duplicate resource name", + "sts4|Duplicate resource name" + ); + } + @Test public void completionsResourceReferences() throws Exception { assertContextualCompletions(