View Javadoc

    1   /**
    2    * Copyright (c) 2008-2011, http://www.snakeyaml.org
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16
    17  package org.yaml.snakeyaml.composer;
    18
    19  import java.util.ArrayList;
    20  import java.util.HashMap;
    21  import java.util.HashSet;
    22  import java.util.List;
    23  import java.util.Map;
    24  import java.util.Set;
    25
    26  import org.yaml.snakeyaml.events.AliasEvent;
    27  import org.yaml.snakeyaml.events.Event;
    28  import org.yaml.snakeyaml.events.MappingStartEvent;
    29  import org.yaml.snakeyaml.events.NodeEvent;
    30  import org.yaml.snakeyaml.events.ScalarEvent;
    31  import org.yaml.snakeyaml.events.SequenceStartEvent;
    32  import org.yaml.snakeyaml.nodes.MappingNode;
    33  import org.yaml.snakeyaml.nodes.Node;
    34  import org.yaml.snakeyaml.nodes.NodeId;
    35  import org.yaml.snakeyaml.nodes.NodeTuple;
    36  import org.yaml.snakeyaml.nodes.ScalarNode;
    37  import org.yaml.snakeyaml.nodes.SequenceNode;
    38  import org.yaml.snakeyaml.nodes.Tag;
    39  import org.yaml.snakeyaml.parser.Parser;
    40  import org.yaml.snakeyaml.resolver.Resolver;
    41
    42  /**
    43   * Creates a node graph from parser events.
    44   * <p>
    45   * Corresponds to the 'Compose' step as described in chapter 3.1 of the <a
    46   * href="http://yaml.org/spec/1.1/">YAML Specification</a>.
    47   * </p>
    48   */
    49  public class Composer {
    50      private final Parser parser;
    51      private final Resolver resolver;
    52      private final Map<String, Node> anchors;
    53      private final Set<Node> recursiveNodes;
    54
    55      public Composer(Parser parser, Resolver resolver) {
    56          this.parser = parser;
    57          this.resolver = resolver;
    58          this.anchors = new HashMap<String, Node>();
    59          this.recursiveNodes = new HashSet<Node>();
    60      }
    61
    62      /**
    63       * Checks if further documents are available.
    64       *
    65       * @return <code>true</code> if there is at least one more document.
    66       */
    67      public boolean checkNode() {
    68          // Drop the STREAM-START event.
    69          if (parser.checkEvent(Event.ID.StreamStart)) {
    70              parser.getEvent();
    71          }
    72          // If there are more documents available?
    73          return !parser.checkEvent(Event.ID.StreamEnd);
    74      }
    75
    76      /**
    77       * Reads and composes the next document.
    78       *
    79       * @return The root node of the document or <code>null</code> if no more
    80       *         documents are available.
    81       */
    82      public Node getNode() {
    83          // Get the root node of the next document.
    84          if (!parser.checkEvent(Event.ID.StreamEnd)) {
    85              return composeDocument();
    86          } else {
    87              return null;
    88          }
    89      }
    90
    91      /**
    92       * Reads a document from a source that contains only one document.
    93       * <p>
    94       * If the stream contains more than one document an exception is thrown.
    95       * </p>
    96       *
    97       * @return The root node of the document or <code>null</code> if no document
    98       *         is available.
    99       */
    100     public Node getSingleNode() {
    101         // Drop the STREAM-START event.
    102         parser.getEvent();
    103         // Compose a document if the stream is not empty.
    104         Node document = null;
    105         if (!parser.checkEvent(Event.ID.StreamEnd)) {
    106             document = composeDocument();
    107         }
    108         // Ensure that the stream contains no more documents.
    109         if (!parser.checkEvent(Event.ID.StreamEnd)) {
    110             Event event = parser.getEvent();
    111             throw new ComposerException("expected a single document in the stream",
    112                     document.getStartMark(), "but found another document", event.getStartMark());
    113         }
    114         // Drop the STREAM-END event.
    115         parser.getEvent();
    116         return document;
    117     }
    118
    119     private Node composeDocument() {
    120         // Drop the DOCUMENT-START event.
    121         parser.getEvent();
    122         // Compose the root node.
    123         Node node = composeNode(null, null);
    124         // Drop the DOCUMENT-END event.
    125         parser.getEvent();
    126         this.anchors.clear();
    127         recursiveNodes.clear();
    128         return node;
    129     }
    130
    131     private Node composeNode(Node parent, Object index) {
    132         recursiveNodes.add(parent);
    133         if (parser.checkEvent(Event.ID.Alias)) {
    134             AliasEvent event = (AliasEvent) parser.getEvent();
    135             String anchor = event.getAnchor();
    136             if (!anchors.containsKey(anchor)) {
    137                 throw new ComposerException(null, null, "found undefined alias " + anchor,
    138                         event.getStartMark());
    139             }
    140             Node result = anchors.get(anchor);
    141             if (recursiveNodes.remove(result)) {
    142                 result.setTwoStepsConstruction(true);
    143             }
    144             return result;
    145         }
    146         NodeEvent event = (NodeEvent) parser.peekEvent();
    147         String anchor = null;
    148         anchor = event.getAnchor();
    149         if (anchor != null && anchors.containsKey(anchor)) {
    150             throw new ComposerException("found duplicate anchor " + anchor + "; first occurence",
    151                     this.anchors.get(anchor).getStartMark(), "second occurence",
    152                     event.getStartMark());
    153         }
    154         Node node = null;
    155         if (parser.checkEvent(Event.ID.Scalar)) {
    156             node = composeScalarNode(anchor);
    157         } else if (parser.checkEvent(Event.ID.SequenceStart)) {
    158             node = composeSequenceNode(anchor);
    159         } else {
    160             node = composeMappingNode(anchor);
    161         }
    162         recursiveNodes.remove(parent);
    163         return node;
    164     }
    165
    166     private Node composeScalarNode(String anchor) {
    167         ScalarEvent ev = (ScalarEvent) parser.getEvent();
    168         String tag = ev.getTag();
    169         boolean resolved = false;
    170         Tag nodeTag;
    171         if (tag == null || tag.equals("!")) {
    172             nodeTag = resolver.resolve(NodeId.scalar, ev.getValue(), ev.getImplicit().canOmitTagInPlainScalar());
    173             resolved = true;
    174         } else {
    175             nodeTag = new Tag(tag);
    176         }
    177         Node node = new ScalarNode(nodeTag, resolved, ev.getValue(), ev.getStartMark(),
    178                 ev.getEndMark(), ev.getStyle());
    179         if (anchor != null) {
    180             anchors.put(anchor, node);
    181         }
    182         return node;
    183     }
    184
    185     private Node composeSequenceNode(String anchor) {
    186         SequenceStartEvent startEvent = (SequenceStartEvent) parser.getEvent();
    187         String tag = startEvent.getTag();
    188         Tag nodeTag;
    189         boolean resolved = false;
    190         if (tag == null || tag.equals("!")) {
    191             nodeTag = resolver.resolve(NodeId.sequence, null, startEvent.getImplicit());
    192             resolved = true;
    193         } else {
    194             nodeTag = new Tag(tag);
    195         }
    196         final ArrayList<Node> children = new ArrayList<Node>();
    197         SequenceNode node = new SequenceNode(nodeTag, resolved, children,
    198                 startEvent.getStartMark(), null, startEvent.getFlowStyle());
    199         if (anchor != null) {
    200             anchors.put(anchor, node);
    201         }
    202         int index = 0;
    203         while (!parser.checkEvent(Event.ID.SequenceEnd)) {
    204             children.add(composeNode(node, index));
    205             index++;
    206         }
    207         Event endEvent = parser.getEvent();
    208         node.setEndMark(endEvent.getEndMark());
    209         return node;
    210     }
    211
    212     private Node composeMappingNode(String anchor) {
    213         MappingStartEvent startEvent = (MappingStartEvent) parser.getEvent();
    214         String tag = startEvent.getTag();
    215         Tag nodeTag;
    216         boolean resolved = false;
    217         if (tag == null || tag.equals("!")) {
    218             nodeTag = resolver.resolve(NodeId.mapping, null, startEvent.getImplicit());
    219             resolved = true;
    220         } else {
    221             nodeTag = new Tag(tag);
    222         }
    223
    224         final List<NodeTuple> children = new ArrayList<NodeTuple>();
    225         MappingNode node = new MappingNode(nodeTag, resolved, children, startEvent.getStartMark(),
    226                 null, startEvent.getFlowStyle());
    227         if (anchor != null) {
    228             anchors.put(anchor, node);
    229         }
    230         while (!parser.checkEvent(Event.ID.MappingEnd)) {
    231             Node itemKey = composeNode(node, null);
    232             if (itemKey.getTag().equals(Tag.MERGE)) {
    233                 node.setMerged(true);
    234             } else if (itemKey.getTag().equals(Tag.VALUE)) {
    235                 itemKey.setTag(Tag.STR);
    236             }
    237             Node itemValue = composeNode(node, itemKey);
    238             children.add(new NodeTuple(itemKey, itemValue));
    239         }
    240         Event endEvent = parser.getEvent();
    241         node.setEndMark(endEvent.getEndMark());
    242         return node;
    243     }
    244 }