/******************************************************************************* * 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.eclipse.cloudfoundry.manifest.editor; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import org.eclipse.core.runtime.IProgressMonitor; import org.springframework.ide.eclipse.editor.support.reconcile.IProblemCollector; import org.springframework.ide.eclipse.editor.support.reconcile.ReconcileProblem; import org.springframework.ide.eclipse.editor.support.yaml.ast.YamlFileAST; import org.springframework.ide.eclipse.editor.support.yaml.reconcile.YamlASTReconciler; import org.springframework.util.Assert; import org.yaml.snakeyaml.nodes.MappingNode; import org.yaml.snakeyaml.nodes.Node; import org.yaml.snakeyaml.nodes.NodeTuple; import org.yaml.snakeyaml.nodes.ScalarNode; import org.yaml.snakeyaml.nodes.SequenceNode; /** * Detects mutually exclusive properties in Deployment Manifest YAML and reports errors. * * @author Alex Boyko * */ public class ManifestYamlExclusivePropertiesReconciler implements YamlASTReconciler { @FunctionalInterface public interface ProblemFactory { ReconcileProblem problem(String key, int start, int end); } private static final String APPLICATIONS = "applications"; final private IProblemCollector problems; final private Set<String> keys1; final private Set<String> keys2; final private ProblemFactory key1MessageFactory; final private ProblemFactory key2MessageFactory; public ManifestYamlExclusivePropertiesReconciler(IProblemCollector problems, Set<String> keys1, ProblemFactory key1MessageFactory, Set<String> keys2, ProblemFactory key2MessageFactory) { Assert.notNull(problems); Assert.notNull(keys1); Assert.notNull(keys2); this.problems = problems; this.keys1 = keys1; this.keys2 = keys2; this.key1MessageFactory = key1MessageFactory; this.key2MessageFactory = key2MessageFactory; } @Override public void reconcile(YamlFileAST ast, IProgressMonitor mon) { List<ScalarNode> key1Nodes = new ArrayList<>(); List<ScalarNode> key2Nodes = new ArrayList<>(); ast.getNodes().stream() .filter(n -> n instanceof MappingNode) .map(n -> (MappingNode) n) .forEach(n -> reconcileManifestNode(n, key1Nodes, key2Nodes, mon)); if (!key1Nodes.isEmpty() && !key2Nodes.isEmpty()) { if (key1MessageFactory != null) { key1Nodes.forEach(keyNode -> problems.accept(key1MessageFactory.problem(keyNode.getValue(), keyNode.getStartMark().getIndex(), keyNode.getEndMark().getIndex()))); } if (key2MessageFactory != null) { key2Nodes.forEach(keyNode -> problems.accept(key2MessageFactory.problem(keyNode.getValue(), keyNode.getStartMark().getIndex(), keyNode.getEndMark().getIndex()))); } } } private void reconcileManifestNode(MappingNode n, Collection<ScalarNode> key1Nodes, Collection<ScalarNode> key2Nodes, IProgressMonitor mon) { NodeTuple applicationsTuple = findValueNode(n, APPLICATIONS); if (applicationsTuple.getValueNode() instanceof SequenceNode) { List<Node> appNodes = ((SequenceNode)applicationsTuple.getValueNode()).getValue(); appNodes.stream() .filter(a -> a instanceof MappingNode) .map(a -> (MappingNode) a) .forEach(a -> reconcileNode(a, key1Nodes, key2Nodes)); } reconcileNode(n, key1Nodes, key2Nodes); } private void reconcileNode(MappingNode n, Collection<ScalarNode> key1Nodes, Collection<ScalarNode> key2Nodes) { key1Nodes.addAll(findKeyNodes(n, keys1).collect(Collectors.toList())); key2Nodes.addAll(findKeyNodes(n, keys2).collect(Collectors.toList())); } private Stream<ScalarNode> findKeyNodes(MappingNode n, Set<String> keys) { return n.getValue().stream() .filter(tuple -> tuple.getKeyNode() instanceof ScalarNode) .map(tuple -> (ScalarNode) tuple.getKeyNode()) .filter(keyNode -> keys.contains(keyNode.getValue())); } private static NodeTuple findValueNode(MappingNode node, String key) { for (NodeTuple tuple : node.getValue()) { if (tuple.getKeyNode() instanceof ScalarNode) { ScalarNode scalar = (ScalarNode) tuple.getKeyNode(); if (key.equals(scalar.getValue())) { return tuple; } } } return null; } }