Home | History | Annotate | Download | only in constructor
      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 
     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     protected final 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     protected Object createArray(Class<?> type, int size) {
    230         return Array.newInstance(type.getComponentType(), size);
    231     }
    232 
    233     @SuppressWarnings("unchecked")
    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")
    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 
    269     protected Object constructArray(SequenceNode node) {
    270         return constructArrayStep2(node, createArray(node.getType(), node.getValue().size()));
    271     }
    272 
    273     protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) {
    274         for (Node child : node.getValue()) {
    275             collection.add(constructObject(child));
    276         }
    277     }
    278 
    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 
    336     protected Map<Object, Object> createDefaultMap() {
    337         // respect order from YAML document
    338         return new LinkedHashMap<Object, Object>();
    339     }
    340 
    341     protected Set<Object> createDefaultSet() {
    342         // respect order from YAML document
    343         return new LinkedHashSet<Object>();
    344     }
    345 
    346     protected Set<Object> constructSet(MappingNode node) {
    347         Set<Object> set = createDefaultSet();
    348         constructSet2ndStep(node, set);
    349         return set;
    350     }
    351 
    352     protected Map<Object, Object> constructMapping(MappingNode node) {
    353         Map<Object, Object> mapping = createDefaultMap();
    354         constructMapping2ndStep(node, mapping);
    355         return mapping;
    356     }
    357 
    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 
    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 
    417     public void setPropertyUtils(PropertyUtils propertyUtils) {
    418         this.propertyUtils = propertyUtils;
    419         explicitPropertyUtils = true;
    420     }
    421 
    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 
    433         public RecursiveTuple(T _1, K _2) {
    434             this._1 = _1;
    435             this._2 = _2;
    436         }
    437 
    438         public K _2() {
    439             return _2;
    440         }
    441 
    442         public T _1() {
    443             return _1;
    444         }
    445     }
    446 
    447     public final boolean isExplicitPropertyUtils() {
    448         return explicitPropertyUtils;
    449     }
    450 }
    451