• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2008, SnakeYAML
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 package org.yaml.snakeyaml.constructor;
15 
16 import java.lang.reflect.Array;
17 import java.lang.reflect.Modifier;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.EnumMap;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.LinkedHashMap;
24 import java.util.LinkedHashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28 import java.util.Set;
29 import java.util.SortedMap;
30 import java.util.SortedSet;
31 import java.util.TreeMap;
32 import java.util.TreeSet;
33 import org.yaml.snakeyaml.LoaderOptions;
34 import org.yaml.snakeyaml.TypeDescription;
35 import org.yaml.snakeyaml.composer.Composer;
36 import org.yaml.snakeyaml.composer.ComposerException;
37 import org.yaml.snakeyaml.error.YAMLException;
38 import org.yaml.snakeyaml.introspector.PropertyUtils;
39 import org.yaml.snakeyaml.nodes.CollectionNode;
40 import org.yaml.snakeyaml.nodes.MappingNode;
41 import org.yaml.snakeyaml.nodes.Node;
42 import org.yaml.snakeyaml.nodes.NodeId;
43 import org.yaml.snakeyaml.nodes.NodeTuple;
44 import org.yaml.snakeyaml.nodes.ScalarNode;
45 import org.yaml.snakeyaml.nodes.SequenceNode;
46 import org.yaml.snakeyaml.nodes.Tag;
47 
48 public abstract class BaseConstructor {
49 
50   /**
51    * An instance returned by newInstance methods when instantiation has not been performed.
52    */
53   protected static final Object NOT_INSTANTIATED_OBJECT = new Object();
54 
55   /**
56    * It maps the node kind to the the Construct implementation. When the runtime class is known then
57    * the implicit tag is ignored.
58    */
59   protected final Map<NodeId, Construct> yamlClassConstructors =
60       new EnumMap<NodeId, Construct>(NodeId.class);
61   /**
62    * It maps the (explicit or implicit) tag to the Construct implementation. It is used: 1) explicit
63    * tag - if present. 2) implicit tag - when the runtime class of the instance is unknown (the node
64    * has the Object.class)
65    */
66   protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>();
67   /**
68    * It maps the (explicit or implicit) tag to the Construct implementation. It is used when no
69    * exact match found.
70    */
71   protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>();
72 
73   protected Composer composer;
74   final Map<Node, Object> constructedObjects;
75   private final Set<Node> recursiveObjects;
76   private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill;
77   private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill;
78 
79   protected Tag rootTag;
80   private PropertyUtils propertyUtils;
81   private boolean explicitPropertyUtils;
82   private boolean allowDuplicateKeys = true;
83   private boolean wrappedToRootException = false;
84 
85   private boolean enumCaseSensitive = false;
86 
87   protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions;
88   protected final Map<Tag, Class<? extends Object>> typeTags;
89 
90   protected LoaderOptions loadingConfig;
91 
BaseConstructor()92   public BaseConstructor() {
93     this(new LoaderOptions());
94   }
95 
BaseConstructor(LoaderOptions loadingConfig)96   public BaseConstructor(LoaderOptions loadingConfig) {
97     constructedObjects = new HashMap<Node, Object>();
98     recursiveObjects = new HashSet<Node>();
99     maps2fill =
100         new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>();
101     sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>();
102     typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
103     typeTags = new HashMap<Tag, Class<? extends Object>>();
104 
105     rootTag = null;
106     explicitPropertyUtils = false;
107 
108     typeDefinitions.put(SortedMap.class,
109         new TypeDescription(SortedMap.class, Tag.OMAP, TreeMap.class));
110     typeDefinitions.put(SortedSet.class,
111         new TypeDescription(SortedSet.class, Tag.SET, TreeSet.class));
112     this.loadingConfig = loadingConfig;
113   }
114 
setComposer(Composer composer)115   public void setComposer(Composer composer) {
116     this.composer = composer;
117   }
118 
119   /**
120    * Check if more documents available
121    *
122    * @return true when there are more YAML documents in the stream
123    */
checkData()124   public boolean checkData() {
125     // If there are more documents available?
126     return composer.checkNode();
127   }
128 
129   /**
130    * Construct and return the next document
131    *
132    * @return constructed instance
133    */
getData()134   public Object getData() throws NoSuchElementException {
135     // Construct and return the next document.
136     if (!composer.checkNode()) {
137       throw new NoSuchElementException("No document is available.");
138     }
139     Node node = composer.getNode();
140     if (rootTag != null) {
141       node.setTag(rootTag);
142     }
143     return constructDocument(node);
144   }
145 
146   /**
147    * Ensure that the stream contains a single document and construct it
148    *
149    * @param type the class of the instance being created
150    * @return constructed instance
151    * @throws ComposerException in case there are more documents in the stream
152    */
getSingleData(Class<?> type)153   public Object getSingleData(Class<?> type) {
154     // Ensure that the stream contains a single document and construct it
155     final Node node = composer.getSingleNode();
156     if (node != null && !Tag.NULL.equals(node.getTag())) {
157       if (Object.class != type) {
158         node.setTag(new Tag(type));
159       } else if (rootTag != null) {
160         node.setTag(rootTag);
161       }
162       return constructDocument(node);
163     } else {
164       Construct construct = yamlConstructors.get(Tag.NULL);
165       return construct.construct(node);
166     }
167   }
168 
169   /**
170    * Construct complete YAML document. Call the second step in case of recursive structures. At the
171    * end cleans all the state.
172    *
173    * @param node root Node
174    * @return Java instance
175    */
constructDocument(Node node)176   protected final Object constructDocument(Node node) {
177     try {
178       Object data = constructObject(node);
179       fillRecursive();
180       return data;
181     } catch (RuntimeException e) {
182       if (wrappedToRootException && !(e instanceof YAMLException)) {
183         throw new YAMLException(e);
184       } else {
185         throw e;
186       }
187     } finally {
188       // clean up resources
189       constructedObjects.clear();
190       recursiveObjects.clear();
191     }
192   }
193 
194   /**
195    * Fill the recursive structures and clean the internal collections
196    */
fillRecursive()197   private void fillRecursive() {
198     if (!maps2fill.isEmpty()) {
199       for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) {
200         RecursiveTuple<Object, Object> key_value = entry._2();
201         entry._1().put(key_value._1(), key_value._2());
202       }
203       maps2fill.clear();
204     }
205     if (!sets2fill.isEmpty()) {
206       for (RecursiveTuple<Set<Object>, Object> value : sets2fill) {
207         value._1().add(value._2());
208       }
209       sets2fill.clear();
210     }
211   }
212 
213   /**
214    * Construct object from the specified Node. Return existing instance if the node is already
215    * constructed.
216    *
217    * @param node Node to be constructed
218    * @return Java instance
219    */
constructObject(Node node)220   protected Object constructObject(Node node) {
221     if (constructedObjects.containsKey(node)) {
222       return constructedObjects.get(node);
223     }
224     return constructObjectNoCheck(node);
225   }
226 
constructObjectNoCheck(Node node)227   protected Object constructObjectNoCheck(Node node) {
228     if (recursiveObjects.contains(node)) {
229       throw new ConstructorException(null, null, "found unconstructable recursive node",
230           node.getStartMark());
231     }
232     recursiveObjects.add(node);
233     Construct constructor = getConstructor(node);
234     Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node)
235         : constructor.construct(node);
236 
237     finalizeConstruction(node, data);
238     constructedObjects.put(node, data);
239     recursiveObjects.remove(node);
240     if (node.isTwoStepsConstruction()) {
241       constructor.construct2ndStep(node, data);
242     }
243     return data;
244   }
245 
246   /**
247    * Get the constructor to construct the Node. For implicit tags if the runtime class is known a
248    * dedicated Construct implementation is used. Otherwise the constructor is chosen by the tag.
249    *
250    * @param node {@link Node} to construct an instance from
251    * @return {@link Construct} implementation for the specified node
252    */
getConstructor(Node node)253   protected Construct getConstructor(Node node) {
254     if (node.useClassConstructor()) {
255       return yamlClassConstructors.get(node.getNodeId());
256     } else {
257       Construct constructor = yamlConstructors.get(node.getTag());
258       if (constructor == null) {
259         for (String prefix : yamlMultiConstructors.keySet()) {
260           if (node.getTag().startsWith(prefix)) {
261             return yamlMultiConstructors.get(prefix);
262           }
263         }
264         return yamlConstructors.get(null);
265       }
266       return constructor;
267     }
268   }
269 
constructScalar(ScalarNode node)270   protected String constructScalar(ScalarNode node) {
271     return node.getValue();
272   }
273 
274   // >>>> DEFAULTS >>>>
createDefaultList(int initSize)275   protected List<Object> createDefaultList(int initSize) {
276     return new ArrayList<Object>(initSize);
277   }
278 
createDefaultSet(int initSize)279   protected Set<Object> createDefaultSet(int initSize) {
280     return new LinkedHashSet<Object>(initSize);
281   }
282 
createDefaultMap(int initSize)283   protected Map<Object, Object> createDefaultMap(int initSize) {
284     // respect order from YAML document
285     return new LinkedHashMap<Object, Object>(initSize);
286   }
287 
createArray(Class<?> type, int size)288   protected Object createArray(Class<?> type, int size) {
289     return Array.newInstance(type.getComponentType(), size);
290   }
291 
292   // <<<< DEFAULTS <<<<
293 
finalizeConstruction(Node node, Object data)294   protected Object finalizeConstruction(Node node, Object data) {
295     final Class<? extends Object> type = node.getType();
296     if (typeDefinitions.containsKey(type)) {
297       return typeDefinitions.get(type).finalizeConstruction(data);
298     }
299     return data;
300   }
301 
302   // >>>> NEW instance
newInstance(Node node)303   protected Object newInstance(Node node) {
304     return newInstance(Object.class, node);
305   }
306 
newInstance(Class<?> ancestor, Node node)307   protected final Object newInstance(Class<?> ancestor, Node node) {
308     return newInstance(ancestor, node, true);
309   }
310 
311   /**
312    * Tries to create a new object for the node.
313    *
314    * @param ancestor expected ancestor of the {@code node.getType()}
315    * @param node for which to create a corresponding java object
316    * @param tryDefault should default constructor to be tried when there is no corresponding
317    *        {@code TypeDescription} or {@code TypeDescription.newInstance(node)} returns
318    *        {@code null}.
319    *
320    * @return - a new object created for {@code node.getType()} by using corresponding
321    *         TypeDescription.newInstance or default constructor. - {@code NOT_INSTANTIATED_OBJECT}
322    *         in case no object has been created
323    */
newInstance(Class<?> ancestor, Node node, boolean tryDefault)324   protected Object newInstance(Class<?> ancestor, Node node, boolean tryDefault) {
325     try {
326       final Class<? extends Object> type = node.getType();
327       if (typeDefinitions.containsKey(type)) {
328         TypeDescription td = typeDefinitions.get(type);
329         final Object instance = td.newInstance(node);
330         if (instance != null) {
331           return instance;
332         }
333       }
334 
335       if (tryDefault) {
336         /*
337          * Removed <code> have InstantiationException in case of abstract type
338          */
339         if (ancestor.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) {
340           java.lang.reflect.Constructor<?> c = type.getDeclaredConstructor();
341           c.setAccessible(true);
342           return c.newInstance();
343         }
344       }
345     } catch (Exception e) {
346       throw new YAMLException(e);
347     }
348 
349     return NOT_INSTANTIATED_OBJECT;
350   }
351 
352   @SuppressWarnings("unchecked")
newSet(CollectionNode<?> node)353   protected Set<Object> newSet(CollectionNode<?> node) {
354     Object instance = newInstance(Set.class, node);
355     if (instance != NOT_INSTANTIATED_OBJECT) {
356       return (Set<Object>) instance;
357     } else {
358       return createDefaultSet(node.getValue().size());
359     }
360   }
361 
362   @SuppressWarnings("unchecked")
newList(SequenceNode node)363   protected List<Object> newList(SequenceNode node) {
364     Object instance = newInstance(List.class, node);
365     if (instance != NOT_INSTANTIATED_OBJECT) {
366       return (List<Object>) instance;
367     } else {
368       return createDefaultList(node.getValue().size());
369     }
370   }
371 
372   @SuppressWarnings("unchecked")
newMap(MappingNode node)373   protected Map<Object, Object> newMap(MappingNode node) {
374     Object instance = newInstance(Map.class, node);
375     if (instance != NOT_INSTANTIATED_OBJECT) {
376       return (Map<Object, Object>) instance;
377     } else {
378       return createDefaultMap(node.getValue().size());
379     }
380   }
381 
382   // <<<< NEW instance
383 
384   // >>>> Construct => NEW, 2ndStep(filling)
constructSequence(SequenceNode node)385   protected List<? extends Object> constructSequence(SequenceNode node) {
386     List<Object> result = newList(node);
387     constructSequenceStep2(node, result);
388     return result;
389   }
390 
constructSet(SequenceNode node)391   protected Set<? extends Object> constructSet(SequenceNode node) {
392     Set<Object> result = newSet(node);
393     constructSequenceStep2(node, result);
394     return result;
395   }
396 
constructArray(SequenceNode node)397   protected Object constructArray(SequenceNode node) {
398     return constructArrayStep2(node, createArray(node.getType(), node.getValue().size()));
399   }
400 
constructSequenceStep2(SequenceNode node, Collection<Object> collection)401   protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) {
402     for (Node child : node.getValue()) {
403       collection.add(constructObject(child));
404     }
405   }
406 
constructArrayStep2(SequenceNode node, Object array)407   protected Object constructArrayStep2(SequenceNode node, Object array) {
408     final Class<?> componentType = node.getType().getComponentType();
409 
410     int index = 0;
411     for (Node child : node.getValue()) {
412       // Handle multi-dimensional arrays...
413       if (child.getType() == Object.class) {
414         child.setType(componentType);
415       }
416 
417       final Object value = constructObject(child);
418 
419       if (componentType.isPrimitive()) {
420         // Null values are disallowed for primitives
421         if (value == null) {
422           throw new NullPointerException("Unable to construct element value for " + child);
423         }
424 
425         // Primitive arrays require quite a lot of work.
426         if (byte.class.equals(componentType)) {
427           Array.setByte(array, index, ((Number) value).byteValue());
428 
429         } else if (short.class.equals(componentType)) {
430           Array.setShort(array, index, ((Number) value).shortValue());
431 
432         } else if (int.class.equals(componentType)) {
433           Array.setInt(array, index, ((Number) value).intValue());
434 
435         } else if (long.class.equals(componentType)) {
436           Array.setLong(array, index, ((Number) value).longValue());
437 
438         } else if (float.class.equals(componentType)) {
439           Array.setFloat(array, index, ((Number) value).floatValue());
440 
441         } else if (double.class.equals(componentType)) {
442           Array.setDouble(array, index, ((Number) value).doubleValue());
443 
444         } else if (char.class.equals(componentType)) {
445           Array.setChar(array, index, ((Character) value).charValue());
446 
447         } else if (boolean.class.equals(componentType)) {
448           Array.setBoolean(array, index, ((Boolean) value).booleanValue());
449 
450         } else {
451           throw new YAMLException("unexpected primitive type");
452         }
453 
454       } else {
455         // Non-primitive arrays can simply be assigned:
456         Array.set(array, index, value);
457       }
458 
459       ++index;
460     }
461     return array;
462   }
463 
constructSet(MappingNode node)464   protected Set<Object> constructSet(MappingNode node) {
465     final Set<Object> set = newSet(node);
466     constructSet2ndStep(node, set);
467     return set;
468   }
469 
constructMapping(MappingNode node)470   protected Map<Object, Object> constructMapping(MappingNode node) {
471     final Map<Object, Object> mapping = newMap(node);
472     constructMapping2ndStep(node, mapping);
473     return mapping;
474   }
475 
constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping)476   protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
477     List<NodeTuple> nodeValue = node.getValue();
478     for (NodeTuple tuple : nodeValue) {
479       Node keyNode = tuple.getKeyNode();
480       Node valueNode = tuple.getValueNode();
481       Object key = constructObject(keyNode);
482       if (key != null) {
483         try {
484           key.hashCode();// check circular dependencies
485         } catch (Exception e) {
486           throw new ConstructorException("while constructing a mapping", node.getStartMark(),
487               "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
488         }
489       }
490       Object value = constructObject(valueNode);
491       if (keyNode.isTwoStepsConstruction()) {
492         if (loadingConfig.getAllowRecursiveKeys()) {
493           postponeMapFilling(mapping, key, value);
494         } else {
495           throw new YAMLException(
496               "Recursive key for mapping is detected but it is not configured to be allowed.");
497         }
498       } else {
499         mapping.put(key, value);
500       }
501     }
502   }
503 
504   /*
505    * if keyObject is created it 2 steps we should postpone putting it in map because it may have
506    * different hash after initialization compared to clean just created one. And map of course does
507    * not observe key hashCode changes.
508    */
postponeMapFilling(Map<Object, Object> mapping, Object key, Object value)509   protected void postponeMapFilling(Map<Object, Object> mapping, Object key, Object value) {
510     maps2fill.add(0, new RecursiveTuple(mapping, new RecursiveTuple(key, value)));
511   }
512 
constructSet2ndStep(MappingNode node, Set<Object> set)513   protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
514     List<NodeTuple> nodeValue = node.getValue();
515     for (NodeTuple tuple : nodeValue) {
516       Node keyNode = tuple.getKeyNode();
517       Object key = constructObject(keyNode);
518       if (key != null) {
519         try {
520           key.hashCode();// check circular dependencies
521         } catch (Exception e) {
522           throw new ConstructorException("while constructing a Set", node.getStartMark(),
523               "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e);
524         }
525       }
526       if (keyNode.isTwoStepsConstruction()) {
527         postponeSetFilling(set, key);
528       } else {
529         set.add(key);
530       }
531     }
532   }
533 
534   /*
535    * if keyObject is created it 2 steps we should postpone putting it into the set because it may
536    * have different hash after initialization compared to clean just created one. And set of course
537    * does not observe value hashCode changes.
538    */
postponeSetFilling(Set<Object> set, Object key)539   protected void postponeSetFilling(Set<Object> set, Object key) {
540     sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key));
541   }
542 
setPropertyUtils(PropertyUtils propertyUtils)543   public void setPropertyUtils(PropertyUtils propertyUtils) {
544     this.propertyUtils = propertyUtils;
545     explicitPropertyUtils = true;
546     Collection<TypeDescription> tds = typeDefinitions.values();
547     for (TypeDescription typeDescription : tds) {
548       typeDescription.setPropertyUtils(propertyUtils);
549     }
550   }
551 
getPropertyUtils()552   public final PropertyUtils getPropertyUtils() {
553     if (propertyUtils == null) {
554       propertyUtils = new PropertyUtils();
555     }
556     return propertyUtils;
557   }
558 
559   /**
560    * Make YAML aware how to parse a custom Class. If there is no root Class assigned in constructor
561    * then the 'root' property of this definition is respected.
562    *
563    * @param definition to be added to the Constructor
564    * @return the previous value associated with <code>definition</code>, or <code>null</code> if
565    *         there was no mapping for <code>definition</code>.
566    */
addTypeDescription(TypeDescription definition)567   public TypeDescription addTypeDescription(TypeDescription definition) {
568     if (definition == null) {
569       throw new NullPointerException("TypeDescription is required.");
570     }
571     Tag tag = definition.getTag();
572     typeTags.put(tag, definition.getType());
573     definition.setPropertyUtils(getPropertyUtils());
574     return typeDefinitions.put(definition.getType(), definition);
575   }
576 
577   private static class RecursiveTuple<T, K> {
578 
579     private final T _1;
580     private final K _2;
581 
RecursiveTuple(T _1, K _2)582     public RecursiveTuple(T _1, K _2) {
583       this._1 = _1;
584       this._2 = _2;
585     }
586 
_2()587     public K _2() {
588       return _2;
589     }
590 
_1()591     public T _1() {
592       return _1;
593     }
594   }
595 
isExplicitPropertyUtils()596   public final boolean isExplicitPropertyUtils() {
597     return explicitPropertyUtils;
598   }
599 
isAllowDuplicateKeys()600   public boolean isAllowDuplicateKeys() {
601     return allowDuplicateKeys;
602   }
603 
setAllowDuplicateKeys(boolean allowDuplicateKeys)604   public void setAllowDuplicateKeys(boolean allowDuplicateKeys) {
605     this.allowDuplicateKeys = allowDuplicateKeys;
606   }
607 
isWrappedToRootException()608   public boolean isWrappedToRootException() {
609     return wrappedToRootException;
610   }
611 
setWrappedToRootException(boolean wrappedToRootException)612   public void setWrappedToRootException(boolean wrappedToRootException) {
613     this.wrappedToRootException = wrappedToRootException;
614   }
615 
isEnumCaseSensitive()616   public boolean isEnumCaseSensitive() {
617     return enumCaseSensitive;
618   }
619 
setEnumCaseSensitive(boolean enumCaseSensitive)620   public void setEnumCaseSensitive(boolean enumCaseSensitive) {
621     this.enumCaseSensitive = enumCaseSensitive;
622   }
623 }
624