All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yaml.snakeyaml.composer.Composer Maven / Gradle / Ivy

There is a newer version: 1.0.366
Show newest version
/**
 * Copyright (c) 2008, SnakeYAML
 *
 * 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.yaml.snakeyaml.composer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.comments.CommentEventsCollector;
import org.yaml.snakeyaml.comments.CommentLine;
import org.yaml.snakeyaml.comments.CommentType;
import org.yaml.snakeyaml.error.Mark;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.events.AliasEvent;
import org.yaml.snakeyaml.events.Event;
import org.yaml.snakeyaml.events.MappingStartEvent;
import org.yaml.snakeyaml.events.NodeEvent;
import org.yaml.snakeyaml.events.ScalarEvent;
import org.yaml.snakeyaml.events.SequenceStartEvent;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeId;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import org.yaml.snakeyaml.nodes.SequenceNode;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.parser.Parser;
import org.yaml.snakeyaml.resolver.Resolver;

/**
 * Creates a node graph from parser events.
 * 

* Corresponds to the 'Compose' step as described in chapter 3.1 of the * YAML Specification. *

*/ public class Composer { /** * its parser */ protected final Parser parser; private final Resolver resolver; private final Map anchors; private final Set recursiveNodes; private int nonScalarAliasesCount = 0; private final LoaderOptions loadingConfig; private final CommentEventsCollector blockCommentsCollector; private final CommentEventsCollector inlineCommentsCollector; // keep the nesting of collections inside other collections private int nestingDepth = 0; private final int nestingDepthLimit; /** * Create * * @param parser - the parser * @param resolver - the resolver * @param loadingConfig - options */ public Composer(Parser parser, Resolver resolver, LoaderOptions loadingConfig) { if (parser == null) { throw new NullPointerException("Parser must be provided"); } if (resolver == null) { throw new NullPointerException("Resolver must be provided"); } if (loadingConfig == null) { throw new NullPointerException("LoaderOptions must be provided"); } this.parser = parser; this.resolver = resolver; this.anchors = new HashMap(); this.recursiveNodes = new HashSet(); this.loadingConfig = loadingConfig; this.blockCommentsCollector = new CommentEventsCollector(parser, CommentType.BLANK_LINE, CommentType.BLOCK); this.inlineCommentsCollector = new CommentEventsCollector(parser, CommentType.IN_LINE); nestingDepthLimit = loadingConfig.getNestingDepthLimit(); } /** * Checks if further documents are available. * * @return true if there is at least one more document. */ public boolean checkNode() { // Drop the STREAM-START event. if (parser.checkEvent(Event.ID.StreamStart)) { parser.getEvent(); } // If there are more documents available? return !parser.checkEvent(Event.ID.StreamEnd); } /** * Reads and composes the next document. * * @return The root node of the document or null if no more documents are available. */ public Node getNode() { // Collect inter-document start comments blockCommentsCollector.collectEvents(); if (parser.checkEvent(Event.ID.StreamEnd)) { List commentLines = blockCommentsCollector.consume(); Mark startMark = commentLines.get(0).getStartMark(); List children = Collections.emptyList(); Node node = new MappingNode(Tag.COMMENT, false, children, startMark, null, FlowStyle.BLOCK); node.setBlockComments(commentLines); return node; } // Drop the DOCUMENT-START event. parser.getEvent(); // Compose the root node. Node node = composeNode(null); // Drop the DOCUMENT-END event. blockCommentsCollector.collectEvents(); if (!blockCommentsCollector.isEmpty()) { node.setEndComments(blockCommentsCollector.consume()); } parser.getEvent(); this.anchors.clear(); this.recursiveNodes.clear(); return node; } /** * Reads a document from a source that contains only one document. *

* If the stream contains more than one document an exception is thrown. *

* * @return The root node of the document or null if no document is available. */ public Node getSingleNode() { // Drop the STREAM-START event. parser.getEvent(); // Compose a document if the stream is not empty. Node document = null; if (!parser.checkEvent(Event.ID.StreamEnd)) { document = getNode(); } // Ensure that the stream contains no more documents. if (!parser.checkEvent(Event.ID.StreamEnd)) { Event event = parser.getEvent(); Mark contextMark = document != null ? document.getStartMark() : null; throw new ComposerException("expected a single document in the stream", contextMark, "but found another document", event.getStartMark()); } // Drop the STREAM-END event. parser.getEvent(); return document; } private Node composeNode(Node parent) { blockCommentsCollector.collectEvents(); if (parent != null) { recursiveNodes.add(parent); } final Node node; if (parser.checkEvent(Event.ID.Alias)) { AliasEvent event = (AliasEvent) parser.getEvent(); String anchor = event.getAnchor(); if (!anchors.containsKey(anchor)) { throw new ComposerException(null, null, "found undefined alias " + anchor, event.getStartMark()); } node = anchors.get(anchor); if (!(node instanceof ScalarNode)) { this.nonScalarAliasesCount++; if (this.nonScalarAliasesCount > loadingConfig.getMaxAliasesForCollections()) { throw new YAMLException( "Number of aliases for non-scalar nodes exceeds the specified max=" + loadingConfig.getMaxAliasesForCollections()); } } if (recursiveNodes.remove(node)) { node.setTwoStepsConstruction(true); } // drop comments, they can not be supported here blockCommentsCollector.consume(); inlineCommentsCollector.collectEvents().consume(); } else { NodeEvent event = (NodeEvent) parser.peekEvent(); String anchor = event.getAnchor(); increaseNestingDepth(); // the check for duplicate anchors has been removed (issue 174) if (parser.checkEvent(Event.ID.Scalar)) { node = composeScalarNode(anchor, blockCommentsCollector.consume()); } else if (parser.checkEvent(Event.ID.SequenceStart)) { node = composeSequenceNode(anchor); } else { node = composeMappingNode(anchor); } decreaseNestingDepth(); } recursiveNodes.remove(parent); return node; } protected Node composeScalarNode(String anchor, List blockComments) { ScalarEvent ev = (ScalarEvent) parser.getEvent(); String tag = ev.getTag(); boolean resolved = false; Tag nodeTag; if (tag == null || tag.equals("!")) { nodeTag = resolver.resolve(NodeId.scalar, ev.getValue(), ev.getImplicit().canOmitTagInPlainScalar()); resolved = true; } else { nodeTag = new Tag(tag); if (nodeTag.isCustomGlobal() && !loadingConfig.getTagInspector().isGlobalTagAllowed(nodeTag)) { throw new ComposerException(null, null, "Global tag is not allowed: " + tag, ev.getStartMark()); } } Node node = new ScalarNode(nodeTag, resolved, ev.getValue(), ev.getStartMark(), ev.getEndMark(), ev.getScalarStyle()); if (anchor != null) { node.setAnchor(anchor); anchors.put(anchor, node); } node.setBlockComments(blockComments); node.setInLineComments(inlineCommentsCollector.collectEvents().consume()); return node; } protected Node composeSequenceNode(String anchor) { SequenceStartEvent startEvent = (SequenceStartEvent) parser.getEvent(); String tag = startEvent.getTag(); Tag nodeTag; boolean resolved = false; if (tag == null || tag.equals("!")) { nodeTag = resolver.resolve(NodeId.sequence, null, startEvent.getImplicit()); resolved = true; } else { nodeTag = new Tag(tag); if (nodeTag.isCustomGlobal() && !loadingConfig.getTagInspector().isGlobalTagAllowed(nodeTag)) { throw new ComposerException(null, null, "Global tag is not allowed: " + tag, startEvent.getStartMark()); } } final ArrayList children = new ArrayList(); SequenceNode node = new SequenceNode(nodeTag, resolved, children, startEvent.getStartMark(), null, startEvent.getFlowStyle()); if (startEvent.isFlow()) { node.setBlockComments(blockCommentsCollector.consume()); } if (anchor != null) { node.setAnchor(anchor); anchors.put(anchor, node); } while (!parser.checkEvent(Event.ID.SequenceEnd)) { blockCommentsCollector.collectEvents(); if (parser.checkEvent(Event.ID.SequenceEnd)) { break; } children.add(composeNode(node)); } if (startEvent.isFlow()) { node.setInLineComments(inlineCommentsCollector.collectEvents().consume()); } Event endEvent = parser.getEvent(); node.setEndMark(endEvent.getEndMark()); inlineCommentsCollector.collectEvents(); if (!inlineCommentsCollector.isEmpty()) { node.setInLineComments(inlineCommentsCollector.consume()); } return node; } protected Node composeMappingNode(String anchor) { MappingStartEvent startEvent = (MappingStartEvent) parser.getEvent(); String tag = startEvent.getTag(); Tag nodeTag; boolean resolved = false; if (tag == null || tag.equals("!")) { nodeTag = resolver.resolve(NodeId.mapping, null, startEvent.getImplicit()); resolved = true; } else { nodeTag = new Tag(tag); if (nodeTag.isCustomGlobal() && !loadingConfig.getTagInspector().isGlobalTagAllowed(nodeTag)) { throw new ComposerException(null, null, "Global tag is not allowed: " + tag, startEvent.getStartMark()); } } final List children = new ArrayList(); MappingNode node = new MappingNode(nodeTag, resolved, children, startEvent.getStartMark(), null, startEvent.getFlowStyle()); if (startEvent.isFlow()) { node.setBlockComments(blockCommentsCollector.consume()); } if (anchor != null) { node.setAnchor(anchor); anchors.put(anchor, node); } while (!parser.checkEvent(Event.ID.MappingEnd)) { blockCommentsCollector.collectEvents(); if (parser.checkEvent(Event.ID.MappingEnd)) { break; } composeMappingChildren(children, node); } if (startEvent.isFlow()) { node.setInLineComments(inlineCommentsCollector.collectEvents().consume()); } Event endEvent = parser.getEvent(); node.setEndMark(endEvent.getEndMark()); inlineCommentsCollector.collectEvents(); if (!inlineCommentsCollector.isEmpty()) { node.setInLineComments(inlineCommentsCollector.consume()); } return node; } /** * Compose the members of mapping * * @param children - the data to fill * @param node - the source */ protected void composeMappingChildren(List children, MappingNode node) { Node itemKey = composeKeyNode(node); if (itemKey.getTag().equals(Tag.MERGE)) { node.setMerged(true); } Node itemValue = composeValueNode(node); children.add(new NodeTuple(itemKey, itemValue)); } /** * To be able to override composeNode(node) which is a key * * @param node - the source * @return node */ protected Node composeKeyNode(MappingNode node) { return composeNode(node); } /** * To be able to override composeNode(node) which is a value * * @param node - the source * @return node */ protected Node composeValueNode(MappingNode node) { return composeNode(node); } /** * Increase nesting depth and fail when it exceeds the denied limit */ private void increaseNestingDepth() { if (nestingDepth > nestingDepthLimit) { throw new YAMLException("Nesting Depth exceeded max " + nestingDepthLimit); } nestingDepth++; } /** * Indicate that the collection is finished and the nesting is decreased */ private void decreaseNestingDepth() { if (nestingDepth > 0) { nestingDepth--; } else { throw new YAMLException("Nesting Depth cannot be negative"); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy