| /** |
| * Copyright (c) 2008-2013, http://www.snakeyaml.org |
| * |
| * 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.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| 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. |
| * <p> |
| * Corresponds to the 'Compose' step as described in chapter 3.1 of the <a |
| * href="http://yaml.org/spec/1.1/">YAML Specification</a>. |
| * </p> |
| */ |
| public class Composer { |
| private final Parser parser; |
| private final Resolver resolver; |
| private final Map<String, Node> anchors; |
| private final Set<Node> recursiveNodes; |
| |
| public Composer(Parser parser, Resolver resolver) { |
| this.parser = parser; |
| this.resolver = resolver; |
| this.anchors = new HashMap<String, Node>(); |
| this.recursiveNodes = new HashSet<Node>(); |
| } |
| |
| /** |
| * Checks if further documents are available. |
| * |
| * @return <code>true</code> 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 <code>null</code> if no more |
| * documents are available. |
| */ |
| public Node getNode() { |
| // Get the root node of the next document. |
| if (!parser.checkEvent(Event.ID.StreamEnd)) { |
| return composeDocument(); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Reads a document from a source that contains only one document. |
| * <p> |
| * If the stream contains more than one document an exception is thrown. |
| * </p> |
| * |
| * @return The root node of the document or <code>null</code> 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 = composeDocument(); |
| } |
| // Ensure that the stream contains no more documents. |
| if (!parser.checkEvent(Event.ID.StreamEnd)) { |
| Event event = parser.getEvent(); |
| throw new ComposerException("expected a single document in the stream", |
| document.getStartMark(), "but found another document", event.getStartMark()); |
| } |
| // Drop the STREAM-END event. |
| parser.getEvent(); |
| return document; |
| } |
| |
| private Node composeDocument() { |
| // Drop the DOCUMENT-START event. |
| parser.getEvent(); |
| // Compose the root node. |
| Node node = composeNode(null); |
| // Drop the DOCUMENT-END event. |
| parser.getEvent(); |
| this.anchors.clear(); |
| recursiveNodes.clear(); |
| return node; |
| } |
| |
| private Node composeNode(Node parent) { |
| recursiveNodes.add(parent); |
| 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 result = anchors.get(anchor); |
| if (recursiveNodes.remove(result)) { |
| result.setTwoStepsConstruction(true); |
| } |
| return result; |
| } |
| NodeEvent event = (NodeEvent) parser.peekEvent(); |
| String anchor = null; |
| anchor = event.getAnchor(); |
| if (anchor != null && anchors.containsKey(anchor)) { |
| throw new ComposerException("found duplicate anchor " + anchor + "; first occurence", |
| this.anchors.get(anchor).getStartMark(), "second occurence", |
| event.getStartMark()); |
| } |
| Node node = null; |
| if (parser.checkEvent(Event.ID.Scalar)) { |
| node = composeScalarNode(anchor); |
| } else if (parser.checkEvent(Event.ID.SequenceStart)) { |
| node = composeSequenceNode(anchor); |
| } else { |
| node = composeMappingNode(anchor); |
| } |
| recursiveNodes.remove(parent); |
| return node; |
| } |
| |
| private Node composeScalarNode(String anchor) { |
| 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); |
| } |
| Node node = new ScalarNode(nodeTag, resolved, ev.getValue(), ev.getStartMark(), |
| ev.getEndMark(), ev.getStyle()); |
| if (anchor != null) { |
| anchors.put(anchor, node); |
| } |
| return node; |
| } |
| |
| private 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); |
| } |
| final ArrayList<Node> children = new ArrayList<Node>(); |
| SequenceNode node = new SequenceNode(nodeTag, resolved, children, |
| startEvent.getStartMark(), null, startEvent.getFlowStyle()); |
| if (anchor != null) { |
| anchors.put(anchor, node); |
| } |
| int index = 0; |
| while (!parser.checkEvent(Event.ID.SequenceEnd)) { |
| children.add(composeNode(node)); |
| index++; |
| } |
| Event endEvent = parser.getEvent(); |
| node.setEndMark(endEvent.getEndMark()); |
| return node; |
| } |
| |
| private 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); |
| } |
| |
| final List<NodeTuple> children = new ArrayList<NodeTuple>(); |
| MappingNode node = new MappingNode(nodeTag, resolved, children, startEvent.getStartMark(), |
| null, startEvent.getFlowStyle()); |
| if (anchor != null) { |
| anchors.put(anchor, node); |
| } |
| while (!parser.checkEvent(Event.ID.MappingEnd)) { |
| Node itemKey = composeNode(node); |
| if (itemKey.getTag().equals(Tag.MERGE)) { |
| node.setMerged(true); |
| } else if (itemKey.getTag().equals(Tag.VALUE)) { |
| itemKey.setTag(Tag.STR); |
| } |
| Node itemValue = composeNode(node); |
| children.add(new NodeTuple(itemKey, itemValue)); |
| } |
| Event endEvent = parser.getEvent(); |
| node.setEndMark(endEvent.getEndMark()); |
| return node; |
| } |
| } |