Reconciler check that jobs used in 'passed' attribute...
... must interact with the resource.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/*******************************************************************************
|
||||
* 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.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.util.Streams;
|
||||
|
||||
/**
|
||||
* Abstract superclass for implementing concrete {@link YamlTraversal}s.
|
||||
* <p>
|
||||
* Note that allthoigh this class provides a default implementation for both
|
||||
* `traverse` and `traverseAmbiguously`, at least one of these methods *must*
|
||||
* be overridden by the subclass (otherwise the methods will just call eachother
|
||||
* in a infinite recursion loop).
|
||||
* <p>
|
||||
* To implement a non-ambiguous traversal, override the `traverse` method.
|
||||
* <p>
|
||||
* To implement an ambguous traversal, override the `traverseAmbiguously` method
|
||||
* instead.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class AbstractYamlTraversal implements YamlTraversal {
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
|
||||
return Streams.fromNullable(traverse(start));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> T traverse(T startNode) {
|
||||
return traverseAmbiguously(startNode).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*******************************************************************************
|
||||
* 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.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.Streams;
|
||||
|
||||
public class AlternativeYamlTraversal extends AbstractYamlTraversal {
|
||||
|
||||
private YamlTraversal first;
|
||||
private YamlTraversal second;
|
||||
|
||||
public AlternativeYamlTraversal(YamlTraversal first, YamlTraversal second) {
|
||||
Assert.isLegal(!first.isEmpty());
|
||||
Assert.isLegal(!second.isEmpty());
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
|
||||
return Stream.concat(
|
||||
first.traverseAmbiguously(start),
|
||||
second.traverseAmbiguously(start)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEmpty() {
|
||||
return first.canEmpty() || second.canEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Or("+first+", "+second+")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*******************************************************************************
|
||||
* 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.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class FilteringTraversal extends AbstractYamlTraversal {
|
||||
|
||||
private YamlTraversal yamlTraversal;
|
||||
private YamlTraversal check;
|
||||
|
||||
public FilteringTraversal(YamlTraversal yamlTraversal, YamlTraversal check) {
|
||||
this.yamlTraversal = yamlTraversal;
|
||||
this.check = check;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
|
||||
Stream<T> x = yamlTraversal.traverseAmbiguously(start);
|
||||
return x.filter(target ->
|
||||
check.traverseAmbiguously(target)
|
||||
.findAny()
|
||||
.isPresent()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Filter("+yamlTraversal + "has: " + check + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEmpty() {
|
||||
return yamlTraversal.canEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*******************************************************************************
|
||||
* 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.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.Streams;
|
||||
|
||||
public class RepeatingYamlTraversal extends AbstractYamlTraversal {
|
||||
|
||||
private YamlTraversal step;
|
||||
|
||||
public RepeatingYamlTraversal(YamlTraversal step) {
|
||||
Assert.isLegal(!step.canEmpty()); //This implementation is still too simplistic to handle that properly!
|
||||
// If you hit this assert, then it may be time to make it more sophisticated.
|
||||
this.step = step;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
|
||||
if (start==null) {
|
||||
return Stream.empty();
|
||||
} else {
|
||||
return Stream.concat(
|
||||
Streams.fromNullable(start),
|
||||
step.traverseAmbiguously(start).flatMap(next -> {
|
||||
return this.traverseAmbiguously(next);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Repeat("+step+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlTraversal repeat() {
|
||||
//don't make 'Repeat(Repeat(...))'
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEmpty() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* 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.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class SequencingYamlTraversal extends AbstractYamlTraversal {
|
||||
|
||||
private YamlTraversal first;
|
||||
private YamlTraversal second;
|
||||
|
||||
public SequencingYamlTraversal(YamlTraversal first, YamlTraversal second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
|
||||
return first.traverseAmbiguously(start)
|
||||
.flatMap(second::traverseAmbiguously);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEmpty() {
|
||||
return first.canEmpty() && second.canEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ package org.springframework.ide.vscode.commons.yaml.path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeRef;
|
||||
@@ -29,7 +30,7 @@ import reactor.core.publisher.Flux;
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class YamlPath {
|
||||
public class YamlPath extends AbstractYamlTraversal {
|
||||
|
||||
public static final YamlPath EMPTY = new YamlPath();
|
||||
private final YamlPathSegment[] segments;
|
||||
@@ -132,34 +133,7 @@ public class YamlPath {
|
||||
return new YamlPath(newPath);
|
||||
}
|
||||
|
||||
public Node traverseToNode(YamlFileAST root) {
|
||||
ASTCursor cursor = traverse(new ASTRootCursor(root));
|
||||
if (cursor instanceof NodeCursor) {
|
||||
return ((NodeCursor)cursor).getNode();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public <T extends YamlNavigable<T>> T traverse(T startNode) {
|
||||
return traverseAmbiguously(startNode).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Stream<Node> traverseAmbiguously(YamlFileAST ast) {
|
||||
if (ast!=null) {
|
||||
return traverseAmbiguously(new ASTRootCursor(ast))
|
||||
.map((ASTCursor cursor) -> (Node)cursor.getNode());
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
public Stream<Node> traverseAmbiguously(Node startNode) {
|
||||
if (startNode!=null) {
|
||||
return traverseAmbiguously(new NodeCursor(startNode))
|
||||
.map((ASTCursor cursor) -> (Node)cursor.getNode());
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T startNode) {
|
||||
if (startNode!=null) {
|
||||
Stream<T> result = Stream.of(startNode);
|
||||
@@ -206,6 +180,7 @@ public class YamlPath {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return segments.length==0;
|
||||
}
|
||||
@@ -311,4 +286,31 @@ public class YamlPath {
|
||||
.block();
|
||||
}
|
||||
|
||||
@Override
|
||||
public YamlTraversal then(YamlTraversal _other) {
|
||||
if (isEmpty()) {
|
||||
return _other;
|
||||
} else if (_other.isEmpty()) {
|
||||
return this;
|
||||
} else if (_other instanceof YamlPathSegment) {
|
||||
return this.append((YamlPathSegment) _other);
|
||||
} else if (_other instanceof YamlPath) {
|
||||
YamlPath other = (YamlPath) _other;
|
||||
return new YamlPath(
|
||||
Stream.concat(
|
||||
Arrays.stream(this.segments),
|
||||
Arrays.stream(other.segments)
|
||||
).toArray(sz -> new YamlPathSegment[sz])
|
||||
);
|
||||
} else {
|
||||
return new SequencingYamlTraversal(this, _other);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEmpty() {
|
||||
//The empty path is the only one that 'canEmpty' since any step in path moves the cursor.
|
||||
return isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.commons.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A YamlPathSegment is a 'primitive' NodeNavigator operation.
|
||||
* More complex operations (i.e {@link YamlPath}) are composed as seqences
|
||||
@@ -17,7 +19,7 @@ package org.springframework.ide.vscode.commons.yaml.path;
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public abstract class YamlPathSegment {
|
||||
public abstract class YamlPathSegment extends AbstractYamlTraversal {
|
||||
|
||||
public static YamlPathSegment decode(String code) {
|
||||
switch (code.charAt(0)) {
|
||||
@@ -34,6 +36,11 @@ public abstract class YamlPathSegment {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start) {
|
||||
return start.traverseAmbiguously(this);
|
||||
}
|
||||
|
||||
public static enum YamlPathSegmentType {
|
||||
VAL_AT_KEY, //Go to value associate with given key in a map.
|
||||
KEY_AT_KEY, //Go to the key node associated with a given key in a map.
|
||||
@@ -43,7 +50,7 @@ public abstract class YamlPathSegment {
|
||||
|
||||
public static class AnyChild extends YamlPathSegment {
|
||||
|
||||
private static AnyChild INSTANCE = new AnyChild();
|
||||
static AnyChild INSTANCE = new AnyChild();
|
||||
|
||||
private AnyChild() {}
|
||||
|
||||
@@ -76,6 +83,7 @@ public abstract class YamlPathSegment {
|
||||
protected String getValueCode() {
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class AtIndex extends YamlPathSegment {
|
||||
@@ -226,6 +234,12 @@ public abstract class YamlPathSegment {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canEmpty() {
|
||||
//All path segments implement a real 'one step' movement,
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toNavString();
|
||||
@@ -255,7 +269,23 @@ public abstract class YamlPathSegment {
|
||||
return getTypeCode() + getValueCode();
|
||||
}
|
||||
|
||||
|
||||
protected abstract String getValueCode();
|
||||
|
||||
protected abstract char getTypeCode();
|
||||
|
||||
@Override
|
||||
public YamlTraversal then(YamlTraversal other) {
|
||||
//Overriding the `then` method to try to compress sequences of segments into YamlPath instead of deeply nested
|
||||
if (other.isEmpty()) {
|
||||
return this;
|
||||
} else if (other instanceof YamlPathSegment) {
|
||||
return new YamlPath(this, (YamlPathSegment)other);
|
||||
} else if (other instanceof YamlPath) {
|
||||
return ((YamlPath) other).prepend(this);
|
||||
} else {
|
||||
return new SequencingYamlTraversal(this, other);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
/*******************************************************************************
|
||||
* 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.yaml.path;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
public interface YamlTraversal {
|
||||
|
||||
////////////////////////////////////////////////
|
||||
/// Methods for performing traversals :
|
||||
|
||||
/**
|
||||
* This is the essence of a traversal. Any concrete traversal must provide
|
||||
* some way to be applied to a starting point and return a stream of
|
||||
* endpoints. If a traversal is non-ambiguous the stream contains at
|
||||
* most one element.
|
||||
* <p>
|
||||
* If traversal is deterministic, the stream will contain at most one element.
|
||||
* <p>
|
||||
* If traversal has potential ambiguity then the stream may contain more than
|
||||
* one element. Each of the elements in the stream is an alternate
|
||||
* place that this traversal could end-up in.
|
||||
*/
|
||||
<T extends YamlNavigable<T>> Stream<T> traverseAmbiguously(T start);
|
||||
|
||||
/**
|
||||
* Performs a traversal and silently drops all but one of the endpoints.
|
||||
* <p>
|
||||
* This is a convenience method for unambiguous traversals, or for when the caller
|
||||
* doesn't care about precisely which one of the possible alternate end-points
|
||||
* they might get.
|
||||
*/
|
||||
<T extends YamlNavigable<T>> T traverse(T startNode);
|
||||
|
||||
default Node traverseToNode(YamlFileAST root) {
|
||||
ASTCursor cursor = traverse(new ASTRootCursor(root));
|
||||
if (cursor instanceof NodeCursor) {
|
||||
return ((NodeCursor)cursor).getNode();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
default Stream<Node> traverseAmbiguously(YamlFileAST ast) {
|
||||
if (ast!=null) {
|
||||
return traverseAmbiguously(new ASTRootCursor(ast))
|
||||
.filter(cursor -> cursor.getNode() instanceof Node)
|
||||
.map((ASTCursor cursor) -> (Node)cursor.getNode());
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
default Stream<Node> traverseAmbiguously(Node startNode) {
|
||||
if (startNode!=null) {
|
||||
return traverseAmbiguously(new NodeCursor(startNode))
|
||||
.map((ASTCursor cursor) -> (Node)cursor.getNode());
|
||||
}
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Creating/composing traversals
|
||||
|
||||
YamlTraversal EMPTY = YamlPath.EMPTY;
|
||||
|
||||
default YamlTraversal then(YamlTraversal other) {
|
||||
if (this.isEmpty()) {
|
||||
return other;
|
||||
} else if (other.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
return new SequencingYamlTraversal(this, other);
|
||||
}
|
||||
|
||||
default YamlTraversal thenValAt(int index) {
|
||||
return then(YamlPathSegment.valueAt(index));
|
||||
}
|
||||
default YamlTraversal thenValAt(String key) {
|
||||
return then(YamlPathSegment.valueAt(key));
|
||||
}
|
||||
default YamlTraversal thenKeyAt(String key) {
|
||||
return then(YamlPathSegment.keyAt(key));
|
||||
}
|
||||
default YamlTraversal thenAnyChild() {
|
||||
return then(YamlPathSegment.anyChild());
|
||||
}
|
||||
default YamlTraversal or(YamlTraversal other) {
|
||||
if (this.isEmpty()) {
|
||||
return other;
|
||||
} else if (other.isEmpty()) {
|
||||
return this;
|
||||
} else {
|
||||
return new AlternativeYamlTraversal(this, other);
|
||||
}
|
||||
}
|
||||
|
||||
default YamlTraversal repeatAtLeast(int howMany) {
|
||||
if (isEmpty()) {
|
||||
return this;
|
||||
} else if (howMany>0) {
|
||||
return this.then(this.repeatAtLeast(howMany-1));
|
||||
} else {
|
||||
return this.repeat();
|
||||
}
|
||||
}
|
||||
|
||||
default YamlTraversal repeat() {
|
||||
return new RepeatingYamlTraversal(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the end-points of a traversal, retaining only those
|
||||
* for which the `check` traversal starting at the end-point
|
||||
* leads somewhere.
|
||||
*/
|
||||
default YamlTraversal has(YamlTraversal check) {
|
||||
if (this.isEmpty()) {
|
||||
return this; // don't bother filtering empty!
|
||||
}
|
||||
return new FilteringTraversal(this, check);
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// Computing information about the traversal's nature
|
||||
|
||||
/**
|
||||
* Returns true if the traversal has only one possible end-point, equal
|
||||
* to its starting point. In other words the traversal does nothing.
|
||||
*/
|
||||
default boolean isEmpty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the traversal might include the starting point in its end points.
|
||||
* <p>
|
||||
* Note this is not the same as 'isEmpty', though 'isEmpty' implies 'canEmpty'.
|
||||
* <p>
|
||||
* If this returns null, it should be taken to mean 'unknown'. This is used for
|
||||
* cases where analyzing the traversal can not predict for certain if the traversal
|
||||
* allows a 'no movement' step.
|
||||
*/
|
||||
boolean canEmpty();
|
||||
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import org.springframework.ide.vscode.commons.yaml.schema.YType;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
/**
|
||||
* A type collector can optionally be added to a {@link YamlASTReconciler}.
|
||||
* A type collector can optionally be added to a {@link SchemaBasedYamlASTReconciler}.
|
||||
* It is notified of the types the reconciler infers for
|
||||
* any AST nodes it visits during reconciling.
|
||||
*
|
||||
|
||||
@@ -124,6 +124,7 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
if (typeCollector!=null) {
|
||||
typeCollector.accept(node, type);
|
||||
}
|
||||
checkConstraints(parent, node, type, schemaContext);
|
||||
switch (getNodeId(node)) {
|
||||
case mapping:
|
||||
MappingNode map = (MappingNode) node;
|
||||
@@ -250,12 +251,14 @@ public class SchemaBasedYamlASTReconciler implements YamlASTReconciler {
|
||||
}
|
||||
problems.accept(YamlSchemaProblems.missingProperties(message, dc, missingProps, parent, map, quickfixes.MISSING_PROP_FIX));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Check for other constraints attached to the type
|
||||
for (Constraint constraint : typeUtil.getConstraints(type)) {
|
||||
if (constraint!=null) {
|
||||
constraint.verify(dc, parent, map, type, problems);
|
||||
}
|
||||
protected void checkConstraints(Node parent, Node node, YType type, DynamicSchemaContext dc) {
|
||||
//Check for other constraints attached to the type
|
||||
for (Constraint constraint : typeUtil.getConstraints(type)) {
|
||||
if (constraint!=null) {
|
||||
constraint.verify(dc, parent, node, type, problems);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ 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.yaml.reconcile.YamlSchemaProblems;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.AbstractType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraints;
|
||||
|
||||
@@ -214,10 +215,9 @@ public class YTypeFactory {
|
||||
|
||||
private SchemaContextAware<ValueParser> parser;
|
||||
private List<YTypedProperty> propertyList = new ArrayList<>();
|
||||
private final List<YValueHint> hints = new ArrayList<>();
|
||||
private List<YValueHint> hints = new ArrayList<>();
|
||||
private Map<String, YTypedProperty> cachedPropertyMap;
|
||||
private SchemaContextAware<Callable<Collection<YValueHint>>> hintProvider;
|
||||
|
||||
private List<Constraint> constraints = new ArrayList<>(2);
|
||||
|
||||
public boolean isSequenceable() {
|
||||
@@ -357,8 +357,9 @@ public class YTypeFactory {
|
||||
return parser == null ? null : parser.withContext(dc);
|
||||
}
|
||||
|
||||
public void require(Constraint dynamicConstraint) {
|
||||
public AbstractType require(Constraint dynamicConstraint) {
|
||||
this.constraints.add(dynamicConstraint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void requireOneOf(String... properties) {
|
||||
@@ -645,7 +646,7 @@ public class YTypeFactory {
|
||||
return getPrimaryProps();
|
||||
}
|
||||
|
||||
private synchronized Map<String, AbstractType> typesByPrimary() {
|
||||
public synchronized Map<String, AbstractType> typesByPrimary() {
|
||||
if (typesByPrimary==null) {
|
||||
//To ensure that the map of 'typesByPrimary' is never stale, make the list of
|
||||
// types immutable at this point. The assumption here is that union can be
|
||||
|
||||
@@ -395,7 +395,7 @@ public class YamlStructureParser {
|
||||
if (index!=null) {
|
||||
List<SNode> cs = getChildren();
|
||||
if (index>=0 && index<cs.size()) {
|
||||
return Streams.of(cs.get(index));
|
||||
return Streams.fromNullable(cs.get(index));
|
||||
}
|
||||
}
|
||||
return Stream.empty();
|
||||
@@ -502,7 +502,7 @@ public class YamlStructureParser {
|
||||
case VAL_AT_KEY:
|
||||
return this.getChildrenWithKey(s.toPropString());
|
||||
case VAL_AT_INDEX:
|
||||
return Streams.of(this.getSeqChildWithIndex(s.toIndex()));
|
||||
return Streams.fromNullable(this.getSeqChildWithIndex(s.toIndex()));
|
||||
default:
|
||||
return Stream.empty();
|
||||
}
|
||||
|
||||
@@ -16,9 +16,9 @@ import java.util.stream.Stream;
|
||||
public class Streams {
|
||||
|
||||
/**
|
||||
* Like java.util.Stream.of but returns Stream.empty of the element is null
|
||||
* Like java.util.Stream.of but returns Stream.empty if the element is null
|
||||
*/
|
||||
public static <T> Stream<T> of(T e) {
|
||||
public static <T> Stream<T> fromNullable(T e) {
|
||||
return e==null ? Stream.empty() : Stream.of(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
package org.springframework.ide.vscode.concourse;
|
||||
|
||||
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.anyChild;
|
||||
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.keyAt;
|
||||
import static org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment.valueAt;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -23,6 +25,7 @@ import java.util.stream.Collectors;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
|
||||
@@ -33,14 +36,17 @@ import org.springframework.ide.vscode.commons.yaml.path.ASTRootCursor;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.NodeCursor;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlPathSegment;
|
||||
import org.springframework.ide.vscode.commons.yaml.path.YamlTraversal;
|
||||
import org.springframework.ide.vscode.commons.yaml.reconcile.YamlSchemaProblems;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.BasicYValueHint;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.DynamicSchemaContext;
|
||||
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;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YBeanUnionType;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YTypedProperty;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.YValueHint;
|
||||
import org.springframework.ide.vscode.commons.yaml.schema.constraints.Constraint;
|
||||
import org.springframework.ide.vscode.commons.yaml.util.Streams;
|
||||
import org.springframework.ide.vscode.concourse.util.CollectorUtil;
|
||||
import org.springframework.ide.vscode.concourse.util.StaleFallbackCache;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
@@ -60,13 +66,92 @@ import com.google.common.collect.Multiset;
|
||||
*/
|
||||
public class ConcourseModel {
|
||||
|
||||
// /**
|
||||
// * Verification of contraint: a job used in the 'passed' attribute of a step
|
||||
// * must interact with the resource in question.
|
||||
// */
|
||||
// public Constraint jobHasInteractionWithResource() {
|
||||
// return (IDocument doc, Node parent, Node node, YType type, Set<String> foundProps, IProblemCollector problems) -> {
|
||||
// }
|
||||
/**
|
||||
* Verification of contraint: a job used in the 'passed' attribute of a step
|
||||
* must interact with the resource in question.
|
||||
*/
|
||||
public final void passedJobHasInteractionWithResource(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) {
|
||||
YamlPath path = dc.getPath();
|
||||
// Expecting a path like this: YamlPath([0], .jobs, [1], .plan, [0], .passed, [0])
|
||||
path = path.dropLast();
|
||||
if (YamlPathSegment.valueAt("passed").equals(path.getLastSegment())) {
|
||||
String jobName = NodeUtil.asScalar(node);
|
||||
JobModel job = getJob(dc.getDocument(), jobName);
|
||||
if (job!=null) {
|
||||
//Only check if the job exists. Otherwise the extra checks will show 'redundant' errors (e.g.
|
||||
// complaining that 'some-job' doesn't ineract with a resource (because the resource doesn't exist).
|
||||
YamlFileAST root = this.getSafeAst(dc.getDocument());
|
||||
if (root!=null) {
|
||||
Node stepNode = path.dropLast().traverseToNode(root);
|
||||
if (stepNode!=null) {
|
||||
StepModel step = newStep(stepNode);
|
||||
String resourceName = step.getResourceName();
|
||||
if (resourceName!=null) {
|
||||
Set<String> interactions = job.getInteractedResources();
|
||||
if (interactions!=null && !interactions.contains(resourceName)) {
|
||||
problems.accept(YamlSchemaProblems.schemaProblem("Job '"+jobName+"' does not interact with resource '"+resourceName+"'", node));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the job with given name. If there is no such job, or if there is more than one, this will return null.
|
||||
*/
|
||||
private JobModel getJob(IDocument doc, String jobName) {
|
||||
List<JobModel> jobs = getFromAst(doc, ast ->
|
||||
JOBS_PATH.traverseAmbiguously(ast)
|
||||
.filter(node -> jobName.equals(NodeUtil.getScalarProperty(node, "name")))
|
||||
.map(JobModel::new)
|
||||
)
|
||||
.limit(2) //We only need 2 elements at most to determine if there is more than one
|
||||
.collect(Collectors.toList());
|
||||
return jobs.size()==1
|
||||
? jobs.get(0)
|
||||
: null;
|
||||
}
|
||||
|
||||
public StepModel newStep(Node _node) {
|
||||
MappingNode node = (MappingNode) _node;
|
||||
Set<String> keys = NodeUtil.getScalarKeys(node);
|
||||
for (Entry<String, AbstractType> primary : stepType.typesByPrimary().entrySet()) {
|
||||
String stepType = primary.getKey();
|
||||
if (keys.contains(primary.getKey())) {
|
||||
return new StepModel(stepType, node);
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Node does not look like step node: "+node);
|
||||
}
|
||||
|
||||
private static final YamlTraversal JobModel_GET_PUT_STEP_PATH = new YamlPath()
|
||||
.then(valueAt("plan"))
|
||||
.then(anyChild().repeatAtLeast(1))
|
||||
.has(keyAt("get").or(keyAt("put")));
|
||||
|
||||
/**
|
||||
* Wraps around a Node in the AST that represents a 'job' and
|
||||
* provides methods for accessing information from the node.
|
||||
*/
|
||||
public class JobModel {
|
||||
|
||||
private Node node;
|
||||
|
||||
JobModel(Node node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
public Set<String> getInteractedResources() {
|
||||
return JobModel_GET_PUT_STEP_PATH
|
||||
.traverseAmbiguously(node)
|
||||
.map(node -> newStep(node))
|
||||
.flatMap(step -> Streams.fromNullable(step.getResourceName()))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps around a Node in the AST that represents a 'step' and
|
||||
@@ -83,6 +168,7 @@ public class ConcourseModel {
|
||||
}
|
||||
|
||||
public Node getResourceNameNode() {
|
||||
Assert.isLegal("put".equals(stepType) || "get".equals(stepType));
|
||||
Node node = NodeUtil.getProperty(step, "resource");
|
||||
return node!=null ? node : NodeUtil.getProperty(step, stepType);
|
||||
}
|
||||
@@ -111,6 +197,12 @@ public class ConcourseModel {
|
||||
|
||||
}
|
||||
|
||||
public static final YamlPath JOBS_PATH = new YamlPath(
|
||||
anyChild(),
|
||||
valueAt("jobs"),
|
||||
anyChild()
|
||||
);
|
||||
|
||||
public static final YamlPath JOB_NAMES_PATH = new YamlPath(
|
||||
anyChild(),
|
||||
valueAt("jobs"),
|
||||
@@ -144,6 +236,8 @@ public class ConcourseModel {
|
||||
|
||||
private final Supplier<SnippetBuilder> snippetBuilderFactory;
|
||||
|
||||
private YBeanUnionType stepType;
|
||||
|
||||
public ConcourseModel(SimpleLanguageServer languageServer) {
|
||||
Yaml yaml = new Yaml();
|
||||
this.parser = new YamlParser(yaml);
|
||||
@@ -326,12 +420,13 @@ public class ConcourseModel {
|
||||
return astTypes;
|
||||
}
|
||||
|
||||
public StepModel newStep(String stepType, MappingNode stepNode) {
|
||||
return new StepModel(stepType, stepNode);
|
||||
}
|
||||
|
||||
public void setResourceTypeRegistry(ResourceTypeRegistry resourceTypes) {
|
||||
this.resourceTypes = resourceTypes;
|
||||
}
|
||||
|
||||
public void setStepType(YBeanUnionType step) {
|
||||
Assert.isNull("stepType already set", this.stepType);
|
||||
this.stepType = step;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
.parseWith(ValueParsers.integerAtLeast(1));
|
||||
|
||||
public final YAtomicType t_resource_name;
|
||||
public final YAtomicType t_job_name;
|
||||
public final AbstractType t_job_name;
|
||||
public final YAtomicType t_resource_type_name;
|
||||
public final YType t_mime_type = f.yatomic("MimeType")
|
||||
.parseWith(ValueParsers.NE_STRING)
|
||||
@@ -189,7 +189,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
(DynamicSchemaContext dc) -> {
|
||||
return models.getJobNames(dc);
|
||||
}
|
||||
);
|
||||
).require(models::passedJobHasInteractionWithResource);
|
||||
|
||||
YAtomicType resourceNameDef = f.yatomic("Resource Name");
|
||||
resourceNameDef.parseWith(ConcourseValueParsers.resourceNameDef(models));
|
||||
@@ -290,7 +290,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
putStep.require((DynamicSchemaContext dc, Node parent, Node _map, YType type, IProblemCollector problems) -> {
|
||||
if (_map instanceof MappingNode) {
|
||||
MappingNode map = (MappingNode) _map;
|
||||
StepModel step = models.newStep("put", map);
|
||||
StepModel step = models.newStep(map);
|
||||
String resourceName = step.getResourceName();
|
||||
if (resourceName!=null) {
|
||||
ResourceModel resource = models.getResource(dc.getDocument(), resourceName);
|
||||
@@ -342,6 +342,7 @@ public class PipelineYmlSchema implements YamlSchema {
|
||||
addProp(step, subStep, "tags", t_strings);
|
||||
addProp(step, subStep, "timeout", t_duration);
|
||||
}
|
||||
models.setStepType(step);
|
||||
|
||||
AbstractType job = f.ybean("Job");
|
||||
addProp(job, "name", jobNameDef).isRequired(true);
|
||||
|
||||
@@ -651,8 +651,8 @@ public class ConcourseEditorTest {
|
||||
{
|
||||
List<Diagnostic> problems = editor.assertProblems(
|
||||
"config|Only one of [config, file]",
|
||||
"config|[platform, run] are required",
|
||||
"config|One of [image_resource, image]",
|
||||
"config|[platform, run] are required",
|
||||
"file|Only one of [config, file]"
|
||||
);
|
||||
//All of the problems in this example are property contraint violations! So all should be warnings.
|
||||
@@ -1615,8 +1615,8 @@ public class ConcourseEditorTest {
|
||||
" access_key_id: the-key"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"source|'bucket' is required",
|
||||
"source|One of [regexp, versioned_file] is required"
|
||||
"source|One of [regexp, versioned_file] is required",
|
||||
"source|'bucket' is required"
|
||||
);
|
||||
|
||||
editor = harness.newEditor(
|
||||
@@ -3116,7 +3116,7 @@ public class ConcourseEditorTest {
|
||||
);
|
||||
}
|
||||
|
||||
@Ignore @Test public void reconcilerJobFromPassedAttributeMustInteractWithResource() throws Exception {
|
||||
@Test public void reconcilerJobFromPassedAttributeMustInteractWithResource() throws Exception {
|
||||
Editor editor;
|
||||
|
||||
editor = harness.newEditor(
|
||||
@@ -3141,7 +3141,7 @@ public class ConcourseEditorTest {
|
||||
" - build-it # <- bad\n"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"build-it^ # <- bad|Job 'build-it' doesn't interact with the resource 'version'"
|
||||
"build-it^ # <- bad|Job 'build-it' does not interact with resource 'version'"
|
||||
);
|
||||
|
||||
editor = harness.newEditor(
|
||||
@@ -3166,7 +3166,7 @@ public class ConcourseEditorTest {
|
||||
" - build-it # <- good\n"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"build-it^ # <- bad|Job 'build-it' doesn't interact with the resource 'source-repo'"
|
||||
"build-it^ # <- bad|Job 'build-it' does not interact with resource 'source-repo'"
|
||||
);
|
||||
|
||||
//Check that we find interactions in steps that are at the top-level of the plan:
|
||||
|
||||
Reference in New Issue
Block a user