/* * Copyright (c) MuleSoft, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.raml.parser.visitor; import static org.raml.parser.rule.ValidationMessage.NON_SCALAR_KEY_MESSAGE; import static org.raml.parser.rule.ValidationResult.createErrorResult; import static org.raml.parser.tagresolver.IncludeResolver.INCLUDE_APPLIED_TAG; import static org.raml.parser.tagresolver.IncludeResolver.INCLUDE_TAG; import static org.raml.parser.visitor.TupleType.KEY; import static org.raml.parser.visitor.TupleType.VALUE; import static org.yaml.snakeyaml.nodes.NodeId.scalar; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Deque; import java.util.List; import java.util.Stack; import org.raml.parser.rule.DefaultTupleRule; import org.raml.parser.rule.NodeRule; import org.raml.parser.rule.NodeRuleFactory; import org.raml.parser.rule.SequenceRule; import org.raml.parser.rule.TupleRule; import org.raml.parser.rule.ValidationResult; 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; import org.yaml.snakeyaml.nodes.Tag; public class YamlDocumentValidator implements YamlValidator { private Class<?> documentClass; private Stack<NodeRule<?>> ruleContext = new Stack<NodeRule<?>>(); private Deque<IncludeInfo> includeContext = new ArrayDeque<IncludeInfo>(); private List<ValidationResult> messages = new ArrayList<ValidationResult>(); private NodeRuleFactory nodeRuleFactory; protected YamlDocumentValidator(Class<?> documentClass) { this(documentClass, new NodeRuleFactory()); } protected YamlDocumentValidator(Class<?> documentClass, NodeRuleFactory nodeRuleFactory) { this.documentClass = documentClass; this.nodeRuleFactory = nodeRuleFactory; } protected Stack<NodeRule<?>> getRuleContext() { return ruleContext; } @Override public void onMappingNodeStart(MappingNode node, TupleType tupleType) { if (tupleType == KEY) { addMessage(node, createErrorResult(NON_SCALAR_KEY_MESSAGE, node)); } } @Override public void onMappingNodeEnd(MappingNode node, TupleType tupleType) { } @Override @SuppressWarnings("unchecked") public void onSequenceStart(SequenceNode node, TupleType tupleType) { if (tupleType == KEY) { addMessage(node, createErrorResult(NON_SCALAR_KEY_MESSAGE, node)); } else { NodeRule<SequenceNode> peek = (NodeRule<SequenceNode>) ruleContext.peek(); addMessages(node, peek.validateValue(node)); } } @Override public void onSequenceEnd(SequenceNode node, TupleType tupleType) { } @Override @SuppressWarnings("unchecked") public void onScalar(ScalarNode node, TupleType tupleType) { List<ValidationResult> result; NodeRule<?> peek = ruleContext.peek(); if (tupleType == VALUE) { result = ((NodeRule<ScalarNode>) peek).validateValue(node); } else { result = ((TupleRule<ScalarNode, ?>) peek).validateKey(node); } addMessages(node, result); } private void addMessages(Node node, List<ValidationResult> result) { for (ValidationResult validationResult : result) { validationResult.setIncludeContext(includeContext); messages.add(validationResult); } } private void addMessage(Node node, ValidationResult errorResult) { addMessages(node, Collections.<ValidationResult>singletonList(errorResult)); } @Override public void onDocumentStart(MappingNode node) { ruleContext.push(buildDocumentRule()); } @Override public void onDocumentEnd(MappingNode node) { NodeRule<?> pop = ruleContext.pop(); List<ValidationResult> onRuleEnd = pop.onRuleEnd(); addMessages(node, onRuleEnd); } @Override public void onTupleEnd(NodeTuple nodeTuple) { NodeRule<?> rule = ruleContext.pop(); if (rule != null) { List<ValidationResult> onRuleEnd = rule.onRuleEnd(); addMessages(nodeTuple.getKeyNode(), onRuleEnd); } else { throw new IllegalStateException("Unexpected ruleContext state"); } } @Override public void onTupleStart(NodeTuple nodeTuple) { TupleRule<?, ?> tupleRule = (TupleRule<?, ?>) ruleContext.peek(); if (tupleRule != null) { TupleRule<?, ?> rule = tupleRule.getRuleForTuple(nodeTuple); ruleContext.push(rule); } else { throw new IllegalStateException("Unexpected ruleContext state"); } } @Override public void onSequenceElementStart(Node sequenceNode) { NodeRule peek = ruleContext.peek(); if (!(peek instanceof SequenceRule)) { ruleContext.push(peek); } else { ruleContext.push(((SequenceRule) peek).getItemRule()); } } @Override public void onSequenceElementEnd(Node sequenceNode) { NodeRule<?> rule = ruleContext.pop(); List<ValidationResult> validationResults = rule.onRuleEnd(); addMessages(sequenceNode, validationResults); } @Override public void onCustomTagStart(Tag tag, Node originalValueNode, NodeTuple nodeTuple) { if (INCLUDE_TAG.equals(tag) && originalValueNode.getNodeId() == scalar) { includeContext.push(new IncludeInfo((ScalarNode) originalValueNode)); } else if (tag.startsWith(INCLUDE_APPLIED_TAG)) { includeContext.push(new IncludeInfo(tag)); } } @Override public void onCustomTagEnd(Tag tag, Node originalValueNode, NodeTuple nodeTuple) { if ((INCLUDE_TAG.equals(tag) && originalValueNode.getNodeId() == scalar) || tag.startsWith(INCLUDE_APPLIED_TAG)) { includeContext.pop(); } } @Override public void onCustomTagError(Tag tag, Node node, String message) { addMessages(node, Arrays.asList(createErrorResult(message, node.getStartMark(), node.getEndMark()))); } private DefaultTupleRule<Node, MappingNode> buildDocumentRule() { return nodeRuleFactory.createDocumentRule(documentClass); } @Override public List<ValidationResult> getMessages() { return messages; } }