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 }