View Javadoc

1   /**
2    * Copyright (c) 2008-2012, 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  package org.yaml.snakeyaml.composer;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.HashSet;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Set;
24  
25  import org.yaml.snakeyaml.events.AliasEvent;
26  import org.yaml.snakeyaml.events.Event;
27  import org.yaml.snakeyaml.events.MappingStartEvent;
28  import org.yaml.snakeyaml.events.NodeEvent;
29  import org.yaml.snakeyaml.events.ScalarEvent;
30  import org.yaml.snakeyaml.events.SequenceStartEvent;
31  import org.yaml.snakeyaml.nodes.MappingNode;
32  import org.yaml.snakeyaml.nodes.Node;
33  import org.yaml.snakeyaml.nodes.NodeId;
34  import org.yaml.snakeyaml.nodes.NodeTuple;
35  import org.yaml.snakeyaml.nodes.ScalarNode;
36  import org.yaml.snakeyaml.nodes.SequenceNode;
37  import org.yaml.snakeyaml.nodes.Tag;
38  import org.yaml.snakeyaml.parser.Parser;
39  import org.yaml.snakeyaml.resolver.Resolver;
40  
41  /**
42   * Creates a node graph from parser events.
43   * <p>
44   * Corresponds to the 'Compose' step as described in chapter 3.1 of the <a
45   * href="http://yaml.org/spec/1.1/">YAML Specification</a>.
46   * </p>
47   */
48  public class Composer {
49      private final Parser parser;
50      private final Resolver resolver;
51      private final Map<String, Node> anchors;
52      private final Set<Node> recursiveNodes;
53  
54      public Composer(Parser parser, Resolver resolver) {
55          this.parser = parser;
56          this.resolver = resolver;
57          this.anchors = new HashMap<String, Node>();
58          this.recursiveNodes = new HashSet<Node>();
59      }
60  
61      /**
62       * Checks if further documents are available.
63       * 
64       * @return <code>true</code> if there is at least one more document.
65       */
66      public boolean checkNode() {
67          // Drop the STREAM-START event.
68          if (parser.checkEvent(Event.ID.StreamStart)) {
69              parser.getEvent();
70          }
71          // If there are more documents available?
72          return !parser.checkEvent(Event.ID.StreamEnd);
73      }
74  
75      /**
76       * Reads and composes the next document.
77       * 
78       * @return The root node of the document or <code>null</code> if no more
79       *         documents are available.
80       */
81      public Node getNode() {
82          // Get the root node of the next document.
83          if (!parser.checkEvent(Event.ID.StreamEnd)) {
84              return composeDocument();
85          } else {
86              return null;
87          }
88      }
89  
90      /**
91       * Reads a document from a source that contains only one document.
92       * <p>
93       * If the stream contains more than one document an exception is thrown.
94       * </p>
95       * 
96       * @return The root node of the document or <code>null</code> if no document
97       *         is available.
98       */
99      public Node getSingleNode() {
100         // Drop the STREAM-START event.
101         parser.getEvent();
102         // Compose a document if the stream is not empty.
103         Node document = null;
104         if (!parser.checkEvent(Event.ID.StreamEnd)) {
105             document = composeDocument();
106         }
107         // Ensure that the stream contains no more documents.
108         if (!parser.checkEvent(Event.ID.StreamEnd)) {
109             Event event = parser.getEvent();
110             throw new ComposerException("expected a single document in the stream",
111                     document.getStartMark(), "but found another document", event.getStartMark());
112         }
113         // Drop the STREAM-END event.
114         parser.getEvent();
115         return document;
116     }
117 
118     private Node composeDocument() {
119         // Drop the DOCUMENT-START event.
120         parser.getEvent();
121         // Compose the root node.
122         Node node = composeNode(null);
123         // Drop the DOCUMENT-END event.
124         parser.getEvent();
125         this.anchors.clear();
126         recursiveNodes.clear();
127         return node;
128     }
129 
130     private Node composeNode(Node parent) {
131         recursiveNodes.add(parent);
132         if (parser.checkEvent(Event.ID.Alias)) {
133             AliasEvent event = (AliasEvent) parser.getEvent();
134             String anchor = event.getAnchor();
135             if (!anchors.containsKey(anchor)) {
136                 throw new ComposerException(null, null, "found undefined alias " + anchor,
137                         event.getStartMark());
138             }
139             Node result = anchors.get(anchor);
140             if (recursiveNodes.remove(result)) {
141                 result.setTwoStepsConstruction(true);
142             }
143             return result;
144         }
145         NodeEvent event = (NodeEvent) parser.peekEvent();
146         String anchor = null;
147         anchor = event.getAnchor();
148         if (anchor != null && anchors.containsKey(anchor)) {
149             throw new ComposerException("found duplicate anchor " + anchor + "; first occurence",
150                     this.anchors.get(anchor).getStartMark(), "second occurence",
151                     event.getStartMark());
152         }
153         Node node = null;
154         if (parser.checkEvent(Event.ID.Scalar)) {
155             node = composeScalarNode(anchor);
156         } else if (parser.checkEvent(Event.ID.SequenceStart)) {
157             node = composeSequenceNode(anchor);
158         } else {
159             node = composeMappingNode(anchor);
160         }
161         recursiveNodes.remove(parent);
162         return node;
163     }
164 
165     private Node composeScalarNode(String anchor) {
166         ScalarEvent ev = (ScalarEvent) parser.getEvent();
167         String tag = ev.getTag();
168         boolean resolved = false;
169         Tag nodeTag;
170         if (tag == null || tag.equals("!")) {
171             nodeTag = resolver.resolve(NodeId.scalar, ev.getValue(), ev.getImplicit()
172                     .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));
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);
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);
238             children.add(new NodeTuple(itemKey, itemValue));
239         }
240         Event endEvent = parser.getEvent();
241         node.setEndMark(endEvent.getEndMark());
242         return node;
243     }
244 }