Concourse Reconciler: check for unused resource

This commit is contained in:
Kris De Volder
2017-05-03 17:19:31 -07:00
parent d7eda61c99
commit 642acea0d9
7 changed files with 202 additions and 34 deletions

View File

@@ -10,19 +10,22 @@
*******************************************************************************/
package org.springframework.ide.vscode.concourse;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
import org.springframework.ide.vscode.commons.yaml.reconcile.ITypeCollector;
import org.springframework.ide.vscode.commons.yaml.schema.YType;
import org.springframework.ide.vscode.commons.yaml.schema.YTypeFactory.YAtomicType;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
/**
* An implementation of {@link ITypeCollector} which keeps track of the
@@ -32,6 +35,42 @@ import com.google.common.collect.ImmutableMap;
*/
public class ASTTypeCache implements ITypeCollector {
public interface NodeTypes {
Collection<Node> getNodes(YType type);
Map<Node, YType> getTypes();
}
/**
* Wraps around a {@link ImmutableMap}<Node, Type> and lazy builds the inverse
* map as needed.
*/
private static class NodeTypesImpl implements NodeTypes {
private ImmutableMap<Node, YType> node2type;
private Multimap<YType, Node> type2node = null; //lazy initialized when used.
public NodeTypesImpl(ImmutableMap<Node, YType> node2type) {
this.node2type = node2type;
}
@Override
public synchronized Collection<Node> getNodes(YType type) {
if (type2node==null) {
ImmutableMultimap.Builder<YType, Node> builder = ImmutableMultimap.builder();
for (Entry<Node, YType> e : node2type.entrySet()) {
builder.put(e.getValue(), e.getKey());
}
type2node = builder.build();
}
return type2node.get(type);
}
@Override
public Map<Node, YType> getTypes() {
return node2type;
}
}
/**
* Set upon commencing a reconciler session.
*/
@@ -43,7 +82,7 @@ public class ASTTypeCache implements ITypeCollector {
private ImmutableMap.Builder<Node, YType> currentTypes = null;
private final Set<YType> interestingTypes = new HashSet<>();
private final Map<String, ImmutableMap<Node, YType>> typeIndex = new HashMap<>();
private final Map<String, NodeTypes> typeIndex = new HashMap<>();
@Override
public void beginCollecting(YamlFileAST ast) {
@@ -56,7 +95,7 @@ public class ASTTypeCache implements ITypeCollector {
public synchronized void endCollecting(YamlFileAST ast) {
Assert.isLegal(currentAst==ast);
String uri = ast.getDocument().getUri();
typeIndex.put(uri, currentTypes.build());
typeIndex.put(uri, new NodeTypesImpl(currentTypes.build()));
this.currentAst = null;
this.currentTypes = null;
}
@@ -69,9 +108,9 @@ public class ASTTypeCache implements ITypeCollector {
}
public synchronized YType getType(YamlFileAST ast, Node node) {
ImmutableMap<Node, YType> types = typeIndex.get(ast.getDocument().getUri());
NodeTypes types = typeIndex.get(ast.getDocument().getUri());
if (types!=null) {
return types.get(node);
return types.getTypes().get(node);
}
return null;
}
@@ -84,7 +123,7 @@ public class ASTTypeCache implements ITypeCollector {
this.interestingTypes.add(type);
}
public synchronized ImmutableMap<Node, YType> getNodes(String uri) {
public synchronized NodeTypes getNodeTypes(String uri) {
return typeIndex.get(uri);
}

View File

@@ -61,7 +61,7 @@ public class ConcourseDocumentSymbolHandler implements DocumentSymbolHandler {
public List<? extends SymbolInformation> handle(DocumentSymbolParams params) {
Builder<SymbolInformation> builder = ImmutableList.builder();
TextDocument doc = documents.getDocument(params.getTextDocument().getUri());
for (Entry<Node, YType> entry : astTypeCache.getNodes(params.getTextDocument().getUri()).entrySet()) {
for (Entry<Node, YType> entry : astTypeCache.getNodeTypes(params.getTextDocument().getUri()).getTypes().entrySet()) {
if (definitionTypes.contains(entry.getValue())) {
try {
builder.add(createSymbol(doc, entry.getKey(), entry.getValue()));

View File

@@ -18,6 +18,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -27,6 +28,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage
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.StringUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlASTProvider;
@@ -44,9 +46,11 @@ 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.constraints.Constraint;
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.util.Streams;
import org.springframework.ide.vscode.concourse.ASTTypeCache.NodeTypes;
import org.springframework.ide.vscode.concourse.util.CollectorUtil;
import org.springframework.ide.vscode.concourse.util.StaleFallbackCache;
import org.yaml.snakeyaml.Yaml;
@@ -66,6 +70,33 @@ import com.google.common.collect.Multiset;
*/
public class ConcourseModel {
/**
* Verification of a 'isUsed' contraint. Basically this consults the ast-type cache, (which should be
* fully populated at the end reconciling) to see if the nodes of any nodes of a given type (representing
* a 'use' of something, contain the value of the current node (which is supposed to be a definition of
* that same type of something).
*/
public Constraint isUsed(YType refType, String entityTypeName) {
getAstTypeCache().addInterestingType(refType); //ensure the type is tracked in the type-cache
return new Constraint() {
@Override
public void verify(DynamicSchemaContext dc, Node parent, Node node, YType type, IProblemCollector problems) {
String defName = NodeUtil.asScalar(node);
if (StringUtil.hasText(defName)) { //Avoid silly 'not used' errors for empty names (will have an other error already).
NodeTypes nodeTypes = getAstTypeCache().getNodeTypes(dc.getDocument().getUri());
if (nodeTypes!=null) {
Optional<Node> reference = nodeTypes.getNodes(refType).stream()
.filter(refNode -> defName.equals(NodeUtil.asScalar(refNode)))
.findAny();
if (!reference.isPresent()) {
problems.accept(YamlSchemaProblems.schemaProblem("Unused '"+entityTypeName+"'", node));
}
}
}
}
};
}
/**
* Verification of contraint: a job used in the 'passed' attribute of a step
* must interact with the resource in question.

View File

@@ -74,7 +74,7 @@ public class ConcourseValueParsers {
//okay
return input;
}
throw new IllegalArgumentException("Duplicate "+typeName+" '"+input+"'");
throw new ValueParseException("Duplicate "+typeName+" '"+input+"'");
};
};
};

View File

@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.concourse;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
@@ -23,7 +22,6 @@ import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.ValueParseException;
import org.springframework.ide.vscode.commons.util.ValueParser;
import org.springframework.ide.vscode.commons.util.ValueParsers;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.ast.YamlFileAST;
@@ -101,7 +99,7 @@ public class PipelineYmlSchema implements YamlSchema {
public final YType t_strictly_pos_integer = f.yatomic("Strictly Positive Integer")
.parseWith(ValueParsers.integerAtLeast(1));
public final YAtomicType t_resource_name;
public final AbstractType t_resource_name;
public final AbstractType t_job_name;
public final YAtomicType t_resource_type_name;
public final YType t_mime_type = f.yatomic("MimeType")
@@ -191,8 +189,9 @@ public class PipelineYmlSchema implements YamlSchema {
}
).require(models::passedJobHasInteractionWithResource);
YAtomicType resourceNameDef = f.yatomic("Resource Name");
resourceNameDef.parseWith(ConcourseValueParsers.resourceNameDef(models));
YAtomicType t_resource_name_def = f.yatomic("Resource Name");
t_resource_name_def.parseWith(ConcourseValueParsers.resourceNameDef(models));
t_resource_name_def.require(models.isUsed(t_resource_name, "Resource"));
YAtomicType jobNameDef = f.yatomic("Job Name");
jobNameDef.parseWith(ConcourseValueParsers.jobNameDef(models));
YAtomicType resourceTypeNameDef = f.yatomic("ResourceType Name");
@@ -203,7 +202,7 @@ public class PipelineYmlSchema implements YamlSchema {
);
AbstractType t_resource = f.ybean("Resource");
addProp(t_resource, "name", resourceNameDef).isRequired(true);
addProp(t_resource, "name", t_resource_name_def).isRequired(true);
addProp(t_resource, "type", t_resource_type_name).isRequired(true);
addProp(t_resource, "source", resourceSource);
addProp(t_resource, "check_every", t_duration);
@@ -372,7 +371,7 @@ public class PipelineYmlSchema implements YamlSchema {
definitionTypes = ImmutableList.of(
jobNameDef,
resourceTypeNameDef,
resourceNameDef
t_resource_name_def
);
initializeDefaultResourceTypes();