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.constructor;
18  
19  import java.lang.reflect.Array;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.EnumMap;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.LinkedHashMap;
26  import java.util.LinkedHashSet;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Set;
30  
31  import org.yaml.snakeyaml.composer.Composer;
32  import org.yaml.snakeyaml.composer.ComposerException;
33  import org.yaml.snakeyaml.error.YAMLException;
34  import org.yaml.snakeyaml.introspector.PropertyUtils;
35  import org.yaml.snakeyaml.nodes.MappingNode;
36  import org.yaml.snakeyaml.nodes.Node;
37  import org.yaml.snakeyaml.nodes.NodeId;
38  import org.yaml.snakeyaml.nodes.NodeTuple;
39  import org.yaml.snakeyaml.nodes.ScalarNode;
40  import org.yaml.snakeyaml.nodes.SequenceNode;
41  import org.yaml.snakeyaml.nodes.Tag;
42  
43  public abstract class BaseConstructor {
44      /**
45       * It maps the node kind to the the Construct implementation. When the
46       * runtime class is known then the implicit tag is ignored.
47       */
48      protected final Map<NodeId, Construct> yamlClassConstructors = new EnumMap<NodeId, Construct>(
49              NodeId.class);
50      /**
51       * It maps the (explicit or implicit) tag to the Construct implementation.
52       * It is used: <br/>
53       * 1) explicit tag - if present. <br/>
54       * 2) implicit tag - when the runtime class of the instance is unknown (the
55       * node has the Object.class)
56       */
57      protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>();
58      /**
59       * It maps the (explicit or implicit) tag to the Construct implementation.
60       * It is used when no exact match found.
61       */
62      protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>();
63  
64      private Composer composer;
65      private final Map<Node, Object> constructedObjects;
66      private final Set<Node> recursiveObjects;
67      private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill;
68      private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill;
69  
70      protected Tag rootTag;
71      private PropertyUtils propertyUtils;
72      private boolean explicitPropertyUtils;
73  
74      public BaseConstructor() {
75          constructedObjects = new HashMap<Node, Object>();
76          recursiveObjects = new HashSet<Node>();
77          maps2fill = new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>();
78          sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>();
79          rootTag = null;
80          explicitPropertyUtils = false;
81      }
82  
83      public void setComposer(Composer composer) {
84          this.composer = composer;
85      }
86  
87      /**
88       * Check if more documents available
89       * 
90       * @return true when there are more YAML documents in the stream
91       */
92      public boolean checkData() {
93          // If there are more documents available?
94          return composer.checkNode();
95      }
96  
97      /**
98       * Construct and return the next document
99       * 
100      * @return constructed instance
101      */
102     public Object getData() {
103         // Construct and return the next document.
104         composer.checkNode();
105         Node node = composer.getNode();
106         if (rootTag != null) {
107             node.setTag(rootTag);
108         }
109         return constructDocument(node);
110     }
111 
112     /**
113      * Ensure that the stream contains a single document and construct it
114      * 
115      * @return constructed instance
116      * @throws ComposerException
117      *             in case there are more documents in the stream
118      */
119     public Object getSingleData(Class<?> type) {
120         // Ensure that the stream contains a single document and construct it
121         Node node = composer.getSingleNode();
122         if (node != null) {
123             if (Object.class != type) {
124                 node.setTag(new Tag(type));
125             } else if (rootTag != null) {
126                 node.setTag(rootTag);
127             }
128             return constructDocument(node);
129         }
130         return null;
131     }
132 
133     /**
134      * Construct complete YAML document. Call the second step in case of
135      * recursive structures. At the end cleans all the state.
136      * 
137      * @param node
138      *            root Node
139      * @return Java instance
140      */
141     private Object constructDocument(Node node) {
142         Object data = constructObject(node);
143         fillRecursive();
144         constructedObjects.clear();
145         recursiveObjects.clear();
146         return data;
147     }
148 
149     private void fillRecursive() {
150         if (!maps2fill.isEmpty()) {
151             for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) {
152                 RecursiveTuple<Object, Object> key_value = entry._2();
153                 entry._1().put(key_value._1(), key_value._2());
154             }
155             maps2fill.clear();
156         }
157         if (!sets2fill.isEmpty()) {
158             for (RecursiveTuple<Set<Object>, Object> value : sets2fill) {
159                 value._1().add(value._2());
160             }
161             sets2fill.clear();
162         }
163     }
164 
165     /**
166      * Construct object from the specified Node. Return existing instance if the
167      * node is already constructed.
168      * 
169      * @param node
170      *            Node to be constructed
171      * @return Java instance
172      */
173     protected Object constructObject(Node node) {
174         if (constructedObjects.containsKey(node)) {
175             return constructedObjects.get(node);
176         }
177         if (recursiveObjects.contains(node)) {
178             throw new ConstructorException(null, null, "found unconstructable recursive node",
179                     node.getStartMark());
180         }
181         recursiveObjects.add(node);
182         Construct constructor = getConstructor(node);
183         Object data = constructor.construct(node);
184         constructedObjects.put(node, data);
185         recursiveObjects.remove(node);
186         if (node.isTwoStepsConstruction()) {
187             constructor.construct2ndStep(node, data);
188         }
189         return data;
190     }
191 
192     /**
193      * Get the constructor to construct the Node. For implicit tags if the
194      * runtime class is known a dedicated Construct implementation is used.
195      * Otherwise the constructor is chosen by the tag.
196      * 
197      * @param node
198      *            Node to be constructed
199      * @return Construct implementation for the specified node
200      */
201     protected Construct getConstructor(Node node) {
202         if (node.useClassConstructor()) {
203             return yamlClassConstructors.get(node.getNodeId());
204         } else {
205             Construct constructor = yamlConstructors.get(node.getTag());
206             if (constructor == null) {
207                 for (String prefix : yamlMultiConstructors.keySet()) {
208                     if (node.getTag().startsWith(prefix)) {
209                         return yamlMultiConstructors.get(prefix);
210                     }
211                 }
212                 return yamlConstructors.get(null);
213             }
214             return constructor;
215         }
216     }
217 
218     protected Object constructScalar(ScalarNode node) {
219         return node.getValue();
220     }
221 
222     protected List<Object> createDefaultList(int initSize) {
223         return new ArrayList<Object>(initSize);
224     }
225 
226     protected Set<Object> createDefaultSet(int initSize) {
227         return new LinkedHashSet<Object>(initSize);
228     }
229 
230     @SuppressWarnings("unchecked")
231     protected <T> T[] createArray(Class<T> type, int size) {
232         return (T[]) Array.newInstance(type.getComponentType(), size);
233     }
234 
235     @SuppressWarnings("unchecked")
236     protected List<? extends Object> constructSequence(SequenceNode node) {
237         List<Object> result;
238         if (List.class.isAssignableFrom(node.getType()) && !node.getType().isInterface()) {
239             // the root class may be defined (Vector for instance)
240             try {
241                 result = (List<Object>) node.getType().newInstance();
242             } catch (Exception e) {
243                 throw new YAMLException(e);
244             }
245         } else {
246             result = createDefaultList(node.getValue().size());
247         }
248         constructSequenceStep2(node, result);
249         return result;
250 
251     }
252 
253     @SuppressWarnings("unchecked")
254     protected Set<? extends Object> constructSet(SequenceNode node) {
255         Set<Object> result;
256         if (!node.getType().isInterface()) {
257             // the root class may be defined
258             try {
259                 result = (Set<Object>) node.getType().newInstance();
260             } catch (Exception e) {
261                 throw new YAMLException(e);
262             }
263         } else {
264             result = createDefaultSet(node.getValue().size());
265         }
266         constructSequenceStep2(node, result);
267         return result;
268 
269     }
270 
271     protected Object constructArray(SequenceNode node) {
272         return constructArrayStep2(node, createArray(node.getType(), node.getValue().size()));
273     }
274 
275     protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) {
276         for (Node child : node.getValue()) {
277             collection.add(constructObject(child));
278         }
279     }
280 
281     protected Object constructArrayStep2(SequenceNode node, Object array) {
282         int index = 0;
283         for (Node child : node.getValue()) {
284             Array.set(array, index++, constructObject(child));
285         }
286         return array;
287     }
288 
289     protected Map<Object, Object> createDefaultMap() {
290         // respect order from YAML document
291         return new LinkedHashMap<Object, Object>();
292     }
293 
294     protected Set<Object> createDefaultSet() {
295         // respect order from YAML document
296         return new LinkedHashSet<Object>();
297     }
298 
299     protected Set<Object> constructSet(MappingNode node) {
300         Set<Object> set = createDefaultSet();
301         constructSet2ndStep(node, set);
302         return set;
303     }
304 
305     protected Map<Object, Object> constructMapping(MappingNode node) {
306         Map<Object, Object> mapping = createDefaultMap();
307         constructMapping2ndStep(node, mapping);
308         return mapping;
309     }
310 
311     protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
312         List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue();
313         for (NodeTuple tuple : nodeValue) {
314             Node keyNode = tuple.getKeyNode();
315             Node valueNode = tuple.getValueNode();
316             Object key = constructObject(keyNode);
317             if (key != null) {
318                 try {
319                     key.hashCode();// check circular dependencies
320                 } catch (Exception e) {
321                     throw new ConstructorException("while constructing a mapping",
322                             node.getStartMark(), "found unacceptable key " + key, tuple
323                                     .getKeyNode().getStartMark(), e);
324                 }
325             }
326             Object value = constructObject(valueNode);
327             if (keyNode.isTwoStepsConstruction()) {
328                 /*
329                  * if keyObject is created it 2 steps we should postpone putting
330                  * it in map because it may have different hash after
331                  * initialization compared to clean just created one. And map of
332                  * course does not observe key hashCode changes.
333                  */
334                 maps2fill.add(0,
335                         new RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>(
336                                 mapping, new RecursiveTuple<Object, Object>(key, value)));
337             } else {
338                 mapping.put(key, value);
339             }
340         }
341     }
342 
343     protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
344         List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue();
345         for (NodeTuple tuple : nodeValue) {
346             Node keyNode = tuple.getKeyNode();
347             Object key = constructObject(keyNode);
348             if (key != null) {
349                 try {
350                     key.hashCode();// check circular dependencies
351                 } catch (Exception e) {
352                     throw new ConstructorException("while constructing a Set", node.getStartMark(),
353                             "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
354                 }
355             }
356             if (keyNode.isTwoStepsConstruction()) {
357                 /*
358                  * if keyObject is created it 2 steps we should postpone putting
359                  * it into the set because it may have different hash after
360                  * initialization compared to clean just created one. And set of
361                  * course does not observe value hashCode changes.
362                  */
363                 sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key));
364             } else {
365                 set.add(key);
366             }
367         }
368     }
369 
370     // TODO protected List<Object[]> constructPairs(MappingNode node) {
371     // List<Object[]> pairs = new LinkedList<Object[]>();
372     // List<Node[]> nodeValue = (List<Node[]>) node.getValue();
373     // for (Iterator<Node[]> iter = nodeValue.iterator(); iter.hasNext();) {
374     // Node[] tuple = iter.next();
375     // Object key = constructObject(Object.class, tuple[0]);
376     // Object value = constructObject(Object.class, tuple[1]);
377     // pairs.add(new Object[] { key, value });
378     // }
379     // return pairs;
380     // }
381 
382     public void setPropertyUtils(PropertyUtils propertyUtils) {
383         this.propertyUtils = propertyUtils;
384         explicitPropertyUtils = true;
385     }
386 
387     public final PropertyUtils getPropertyUtils() {
388         if (propertyUtils == null) {
389             propertyUtils = new PropertyUtils();
390         }
391         return propertyUtils;
392     }
393 
394     private static class RecursiveTuple<T, K> {
395         private final T _1;
396         private final K _2;
397 
398         public RecursiveTuple(T _1, K _2) {
399             this._1 = _1;
400             this._2 = _2;
401         }
402 
403         public K _2() {
404             return _2;
405         }
406 
407         public T _1() {
408             return _1;
409         }
410     }
411 
412     public final boolean isExplicitPropertyUtils() {
413         return explicitPropertyUtils;
414     }
415 }