1 /**
2  * Copyright (c) 2008, 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     protected 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 
BaseConstructor()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 
setComposer(Composer composer)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      */
checkData()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      */
getData()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      */
getSingleData(Class<?> type)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      */
constructDocument(Node node)140     protected final Object constructDocument(Node node) {
141         Object data = constructObject(node);
142         fillRecursive();
143         constructedObjects.clear();
144         recursiveObjects.clear();
145         return data;
146     }
147 
fillRecursive()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      */
constructObject(Node node)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      */
getConstructor(Node node)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 
constructScalar(ScalarNode node)217     protected Object constructScalar(ScalarNode node) {
218         return node.getValue();
219     }
220 
createDefaultList(int initSize)221     protected List<Object> createDefaultList(int initSize) {
222         return new ArrayList<Object>(initSize);
223     }
224 
createDefaultSet(int initSize)225     protected Set<Object> createDefaultSet(int initSize) {
226         return new LinkedHashSet<Object>(initSize);
227     }
228 
createArray(Class<?> type, int size)229     protected Object createArray(Class<?> type, int size) {
230         return Array.newInstance(type.getComponentType(), size);
231     }
232 
233     @SuppressWarnings("unchecked")
constructSequence(SequenceNode node)234     protected List<? extends Object> constructSequence(SequenceNode node) {
235         List<Object> result;
236         if (List.class.isAssignableFrom(node.getType()) && !node.getType().isInterface()) {
237             // the root class may be defined (Vector for instance)
238             try {
239                 result = (List<Object>) node.getType().newInstance();
240             } catch (Exception e) {
241                 throw new YAMLException(e);
242             }
243         } else {
244             result = createDefaultList(node.getValue().size());
245         }
246         constructSequenceStep2(node, result);
247         return result;
248 
249     }
250 
251     @SuppressWarnings("unchecked")
constructSet(SequenceNode node)252     protected Set<? extends Object> constructSet(SequenceNode node) {
253         Set<Object> result;
254         if (!node.getType().isInterface()) {
255             // the root class may be defined
256             try {
257                 result = (Set<Object>) node.getType().newInstance();
258             } catch (Exception e) {
259                 throw new YAMLException(e);
260             }
261         } else {
262             result = createDefaultSet(node.getValue().size());
263         }
264         constructSequenceStep2(node, result);
265         return result;
266 
267     }
268 
constructArray(SequenceNode node)269     protected Object constructArray(SequenceNode node) {
270         return constructArrayStep2(node, createArray(node.getType(), node.getValue().size()));
271     }
272 
constructSequenceStep2(SequenceNode node, Collection<Object> collection)273     protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) {
274         for (Node child : node.getValue()) {
275             collection.add(constructObject(child));
276         }
277     }
278 
constructArrayStep2(SequenceNode node, Object array)279     protected Object constructArrayStep2(SequenceNode node, Object array) {
280         final Class<?> componentType = node.getType().getComponentType();
281 
282         int index = 0;
283         for (Node child : node.getValue()) {
284             // Handle multi-dimensional arrays...
285             if (child.getType() == Object.class) {
286                 child.setType(componentType);
287             }
288 
289             final Object value = constructObject(child);
290 
291             if (componentType.isPrimitive()) {
292                 // Null values are disallowed for primitives
293                 if (value == null) {
294                     throw new NullPointerException("Unable to construct element value for " + child);
295                 }
296 
297                 // Primitive arrays require quite a lot of work.
298                 if (byte.class.equals(componentType)) {
299                     Array.setByte(array, index, ((Number) value).byteValue());
300 
301                 } else if (short.class.equals(componentType)) {
302                     Array.setShort(array, index, ((Number) value).shortValue());
303 
304                 } else if (int.class.equals(componentType)) {
305                     Array.setInt(array, index, ((Number) value).intValue());
306 
307                 } else if (long.class.equals(componentType)) {
308                     Array.setLong(array, index, ((Number) value).longValue());
309 
310                 } else if (float.class.equals(componentType)) {
311                     Array.setFloat(array, index, ((Number) value).floatValue());
312 
313                 } else if (double.class.equals(componentType)) {
314                     Array.setDouble(array, index, ((Number) value).doubleValue());
315 
316                 } else if (char.class.equals(componentType)) {
317                     Array.setChar(array, index, ((Character) value).charValue());
318 
319                 } else if (boolean.class.equals(componentType)) {
320                     Array.setBoolean(array, index, ((Boolean) value).booleanValue());
321 
322                 } else {
323                     throw new YAMLException("unexpected primitive type");
324                 }
325 
326             } else {
327                 // Non-primitive arrays can simply be assigned:
328                 Array.set(array, index, value);
329             }
330 
331             ++index;
332         }
333         return array;
334     }
335 
createDefaultMap()336     protected Map<Object, Object> createDefaultMap() {
337         // respect order from YAML document
338         return new LinkedHashMap<Object, Object>();
339     }
340 
createDefaultSet()341     protected Set<Object> createDefaultSet() {
342         // respect order from YAML document
343         return new LinkedHashSet<Object>();
344     }
345 
constructSet(MappingNode node)346     protected Set<Object> constructSet(MappingNode node) {
347         Set<Object> set = createDefaultSet();
348         constructSet2ndStep(node, set);
349         return set;
350     }
351 
constructMapping(MappingNode node)352     protected Map<Object, Object> constructMapping(MappingNode node) {
353         Map<Object, Object> mapping = createDefaultMap();
354         constructMapping2ndStep(node, mapping);
355         return mapping;
356     }
357 
constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping)358     protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
359         List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue();
360         for (NodeTuple tuple : nodeValue) {
361             Node keyNode = tuple.getKeyNode();
362             Node valueNode = tuple.getValueNode();
363             Object key = constructObject(keyNode);
364             if (key != null) {
365                 try {
366                     key.hashCode();// check circular dependencies
367                 } catch (Exception e) {
368                     throw new ConstructorException("while constructing a mapping",
369                             node.getStartMark(), "found unacceptable key " + key, tuple
370                                     .getKeyNode().getStartMark(), e);
371                 }
372             }
373             Object value = constructObject(valueNode);
374             if (keyNode.isTwoStepsConstruction()) {
375                 /*
376                  * if keyObject is created it 2 steps we should postpone putting
377                  * it in map because it may have different hash after
378                  * initialization compared to clean just created one. And map of
379                  * course does not observe key hashCode changes.
380                  */
381                 maps2fill.add(0,
382                         new RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>(
383                                 mapping, new RecursiveTuple<Object, Object>(key, value)));
384             } else {
385                 mapping.put(key, value);
386             }
387         }
388     }
389 
constructSet2ndStep(MappingNode node, Set<Object> set)390     protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
391         List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue();
392         for (NodeTuple tuple : nodeValue) {
393             Node keyNode = tuple.getKeyNode();
394             Object key = constructObject(keyNode);
395             if (key != null) {
396                 try {
397                     key.hashCode();// check circular dependencies
398                 } catch (Exception e) {
399                     throw new ConstructorException("while constructing a Set", node.getStartMark(),
400                             "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
401                 }
402             }
403             if (keyNode.isTwoStepsConstruction()) {
404                 /*
405                  * if keyObject is created it 2 steps we should postpone putting
406                  * it into the set because it may have different hash after
407                  * initialization compared to clean just created one. And set of
408                  * course does not observe value hashCode changes.
409                  */
410                 sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key));
411             } else {
412                 set.add(key);
413             }
414         }
415     }
416 
setPropertyUtils(PropertyUtils propertyUtils)417     public void setPropertyUtils(PropertyUtils propertyUtils) {
418         this.propertyUtils = propertyUtils;
419         explicitPropertyUtils = true;
420     }
421 
getPropertyUtils()422     public final PropertyUtils getPropertyUtils() {
423         if (propertyUtils == null) {
424             propertyUtils = new PropertyUtils();
425         }
426         return propertyUtils;
427     }
428 
429     private static class RecursiveTuple<T, K> {
430         private final T _1;
431         private final K _2;
432 
RecursiveTuple(T _1, K _2)433         public RecursiveTuple(T _1, K _2) {
434             this._1 = _1;
435             this._2 = _2;
436         }
437 
_2()438         public K _2() {
439             return _2;
440         }
441 
_1()442         public T _1() {
443             return _1;
444         }
445     }
446 
isExplicitPropertyUtils()447     public final boolean isExplicitPropertyUtils() {
448         return explicitPropertyUtils;
449     }
450 }
451