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 }