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