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.beans.IntrospectionException;
    20  import java.math.BigDecimal;
    21  import java.math.BigInteger;
    22  import java.util.ArrayList;
    23  import java.util.Calendar;
    24  import java.util.Collection;
    25  import java.util.Date;
    26  import java.util.HashMap;
    27  import java.util.List;
    28  import java.util.Map;
    29  import java.util.Properties;
    30  import java.util.Set;
    31  import java.util.SortedMap;
    32  import java.util.SortedSet;
    33  import java.util.TreeMap;
    34  import java.util.TreeSet;
    35
    36  import org.yaml.snakeyaml.TypeDescription;
    37  import org.yaml.snakeyaml.error.YAMLException;
    38  import org.yaml.snakeyaml.introspector.Property;
    39  import org.yaml.snakeyaml.nodes.MappingNode;
    40  import org.yaml.snakeyaml.nodes.Node;
    41  import org.yaml.snakeyaml.nodes.NodeId;
    42  import org.yaml.snakeyaml.nodes.NodeTuple;
    43  import org.yaml.snakeyaml.nodes.ScalarNode;
    44  import org.yaml.snakeyaml.nodes.SequenceNode;
    45  import org.yaml.snakeyaml.nodes.Tag;
    46
    47  /**
    48   * Construct a custom Java instance.
    49   */
    50  public class Constructor extends SafeConstructor {
    51      private final Map<Tag, Class<? extends Object>> typeTags;
    52      private final Map<Class<? extends Object>, TypeDescription> typeDefinitions;
    53
    54      public Constructor() {
    55          this(Object.class);
    56      }
    57
    58      /**
    59       * Create Constructor for the specified class as the root.
    60       *
    61       * @param theRoot
    62       *            - the class (usually JavaBean) to be constructed
    63       */
    64      public Constructor(Class<? extends Object> theRoot) {
    65          this(new TypeDescription(checkRoot(theRoot)));
    66      }
    67
    68      /**
    69       * Ugly Java way to check the argument in the constructor
    70       */
    71      private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) {
    72          if (theRoot == null) {
    73              throw new NullPointerException("Root class must be provided.");
    74          } else
    75              return theRoot;
    76      }
    77
    78      public Constructor(TypeDescription theRoot) {
    79          if (theRoot == null) {
    80              throw new NullPointerException("Root type must be provided.");
    81          }
    82          this.yamlConstructors.put(null, new ConstructYamlObject());
    83          if (!Object.class.equals(theRoot.getType())) {
    84              rootTag = new Tag(theRoot.getType());
    85          }
    86          typeTags = new HashMap<Tag, Class<? extends Object>>();
    87          typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
    88          yamlClassConstructors.put(NodeId.scalar, new ConstructScalar());
    89          yamlClassConstructors.put(NodeId.mapping, new ConstructMapping());
    90          yamlClassConstructors.put(NodeId.sequence, new ConstructSequence());
    91          addTypeDescription(theRoot);
    92      }
    93
    94      /**
    95       * Create Constructor for a class which does not have to be in the classpath
    96       * or for a definition from a Spring ApplicationContext.
    97       *
    98       * @param theRoot
    99       *            fully qualified class name of the root class (usually
    100      *            JavaBean)
    101      * @throws ClassNotFoundException
    102      */
    103     public Constructor(String theRoot) throws ClassNotFoundException {
    104         this(Class.forName(check(theRoot)));
    105     }
    106
    107     private static final String check(String s) {
    108         if (s == null) {
    109             throw new NullPointerException("Root type must be provided.");
    110         }
    111         if (s.trim().length() == 0) {
    112             throw new YAMLException("Root type must be provided.");
    113         }
    114         return s;
    115     }
    116
    117     /**
    118      * Make YAML aware how to parse a custom Class. If there is no root Class
    119      * assigned in constructor then the 'root' property of this definition is
    120      * respected.
    121      *
    122      * @param definition
    123      *            to be added to the Constructor
    124      * @return the previous value associated with <tt>definition</tt>, or
    125      *         <tt>null</tt> if there was no mapping for <tt>definition</tt>.
    126      */
    127     public TypeDescription addTypeDescription(TypeDescription definition) {
    128         if (definition == null) {
    129             throw new NullPointerException("TypeDescription is required.");
    130         }
    131         Tag tag = definition.getTag();
    132         typeTags.put(tag, definition.getType());
    133         return typeDefinitions.put(definition.getType(), definition);
    134     }
    135
    136     /**
    137      * Construct mapping instance (Map, JavaBean) when the runtime class is
    138      * known.
    139      */
    140     protected class ConstructMapping implements Construct {
    141
    142         /**
    143          * Construct JavaBean. If type safe collections are used please look at
    144          * <code>TypeDescription</code>.
    145          *
    146          * @param node
    147          *            node where the keys are property names (they can only be
    148          *            <code>String</code>s) and values are objects to be created
    149          * @return constructed JavaBean
    150          */
    151         public Object construct(Node node) {
    152             MappingNode mnode = (MappingNode) node;
    153             if (Properties.class.isAssignableFrom(node.getType())) {
    154                 Properties properties = new Properties();
    155                 if (!node.isTwoStepsConstruction()) {
    156                     constructMapping2ndStep(mnode, properties);
    157                 } else {
    158                     throw new YAMLException("Properties must not be recursive.");
    159                 }
    160                 return properties;
    161             } else if (SortedMap.class.isAssignableFrom(node.getType())) {
    162                 SortedMap<Object, Object> map = new TreeMap<Object, Object>();
    163                 if (!node.isTwoStepsConstruction()) {
    164                     constructMapping2ndStep(mnode, map);
    165                 }
    166                 return map;
    167             } else if (Map.class.isAssignableFrom(node.getType())) {
    168                 if (node.isTwoStepsConstruction()) {
    169                     return createDefaultMap();
    170                 } else {
    171                     return constructMapping(mnode);
    172                 }
    173             } else if (SortedSet.class.isAssignableFrom(node.getType())) {
    174                 SortedSet<Object> set = new TreeSet<Object>();
    175                 // XXX why this is not used ?
    176                 // if (!node.isTwoStepsConstruction()) {
    177                 constructSet2ndStep(mnode, set);
    178                 // }
    179                 return set;
    180             } else if (Collection.class.isAssignableFrom(node.getType())) {
    181                 if (node.isTwoStepsConstruction()) {
    182                     return createDefaultSet();
    183                 } else {
    184                     return constructSet(mnode);
    185                 }
    186             } else {
    187                 if (node.isTwoStepsConstruction()) {
    188                     return createEmptyJavaBean(mnode);
    189                 } else {
    190                     return constructJavaBean2ndStep(mnode, createEmptyJavaBean(mnode));
    191                 }
    192             }
    193         }
    194
    195         @SuppressWarnings("unchecked")
    196         public void construct2ndStep(Node node, Object object) {
    197             if (Map.class.isAssignableFrom(node.getType())) {
    198                 constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
    199             } else if (Set.class.isAssignableFrom(node.getType())) {
    200                 constructSet2ndStep((MappingNode) node, (Set<Object>) object);
    201             } else {
    202                 constructJavaBean2ndStep((MappingNode) node, object);
    203             }
    204         }
    205
    206         protected Object createEmptyJavaBean(MappingNode node) {
    207             try {
    208                 /**
    209                  * Using only default constructor. Everything else will be
    210                  * initialized on 2nd step. If we do here some partial
    211                  * initialization, how do we then track what need to be done on
    212                  * 2nd step? I think it is better to get only object here (to
    213                  * have it as reference for recursion) and do all other thing on
    214                  * 2nd step.
    215                  */
    216                 java.lang.reflect.Constructor<?> c = node.getType().getDeclaredConstructor();
    217                 c.setAccessible(true);
    218                 return c.newInstance();
    219             } catch (Exception e) {
    220                 throw new YAMLException(e);
    221             }
    222         }
    223
    224         protected Object constructJavaBean2ndStep(MappingNode node, Object object) {
    225             flattenMapping(node);
    226             Class<? extends Object> beanType = node.getType();
    227             List<NodeTuple> nodeValue = node.getValue();
    228             for (NodeTuple tuple : nodeValue) {
    229                 ScalarNode keyNode;
    230                 if (tuple.getKeyNode() instanceof ScalarNode) {
    231                     // key must be scalar
    232                     keyNode = (ScalarNode) tuple.getKeyNode();
    233                 } else {
    234                     throw new YAMLException("Keys must be scalars but found: " + tuple.getKeyNode());
    235                 }
    236                 Node valueNode = tuple.getValueNode();
    237                 // keys can only be Strings
    238                 keyNode.setType(String.class);
    239                 String key = (String) constructObject(keyNode);
    240                 try {
    241                     Property property = getProperty(beanType, key);
    242                     valueNode.setType(property.getType());
    243                     TypeDescription memberDescription = typeDefinitions.get(beanType);
    244                     boolean typeDetected = false;
    245                     if (memberDescription != null) {
    246                         switch (valueNode.getNodeId()) {
    247                         case sequence:
    248                             SequenceNode snode = (SequenceNode) valueNode;
    249                             Class<? extends Object> memberType = memberDescription
    250                                     .getListPropertyType(key);
    251                             if (memberType != null) {
    252                                 snode.setListType(memberType);
    253                                 typeDetected = true;
    254                             } else if (property.getType().isArray()) {
    255                                 snode.setListType(property.getType().getComponentType());
    256                                 typeDetected = true;
    257                             }
    258                             break;
    259                         case mapping:
    260                             MappingNode mnode = (MappingNode) valueNode;
    261                             Class<? extends Object> keyType = memberDescription.getMapKeyType(key);
    262                             if (keyType != null) {
    263                                 mnode.setTypes(keyType, memberDescription.getMapValueType(key));
    264                                 typeDetected = true;
    265                             }
    266                             break;
    267                         }
    268                     }
    269                     if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
    270                         // only if there is no explicit TypeDescription
    271                         Class<?>[] arguments = property.getActualTypeArguments();
    272                         if (arguments != null) {
    273                             // type safe (generic) collection may contain the
    274                             // proper class
    275                             if (valueNode.getNodeId() == NodeId.sequence) {
    276                                 Class<?> t = arguments[0];
    277                                 SequenceNode snode = (SequenceNode) valueNode;
    278                                 snode.setListType(t);
    279                             } else if (valueNode.getTag().equals(Tag.SET)) {
    280                                 Class<?> t = arguments[0];
    281                                 MappingNode mnode = (MappingNode) valueNode;
    282                                 mnode.setOnlyKeyType(t);
    283                                 mnode.setUseClassConstructor(true);
    284                             } else if (property.getType().isAssignableFrom(Map.class)) {
    285                                 Class<?> ketType = arguments[0];
    286                                 Class<?> valueType = arguments[1];
    287                                 MappingNode mnode = (MappingNode) valueNode;
    288                                 mnode.setTypes(ketType, valueType);
    289                                 mnode.setUseClassConstructor(true);
    290                             } else {
    291                                 // the type for collection entries cannot be
    292                                 // detected
    293                             }
    294                         }
    295                     }
    296                     Object value = constructObject(valueNode);
    297                     property.set(object, value);
    298                 } catch (Exception e) {
    299                     throw new YAMLException("Cannot create property=" + key + " for JavaBean="
    300                             + object + "; " + e.getMessage(), e);
    301                 }
    302             }
    303             return object;
    304         }
    305
    306         protected Property getProperty(Class<? extends Object> type, String name)
    307                 throws IntrospectionException {
    308             return getPropertyUtils().getProperty(type, name);
    309         }
    310     }
    311
    312     /**
    313      * Construct an instance when the runtime class is not known but a global
    314      * tag with a class name is defined. It delegates the construction to the
    315      * appropriate constructor based on the node kind (scalar, sequence,
    316      * mapping)
    317      */
    318     protected class ConstructYamlObject implements Construct {
    319
    320         private Construct getConstructor(Node node) {
    321             Class<?> cl = getClassForNode(node);
    322             node.setType(cl);
    323             // call the constructor as if the runtime class is defined
    324             Construct constructor = yamlClassConstructors.get(node.getNodeId());
    325             return constructor;
    326         }
    327
    328         public Object construct(Node node) {
    329             Object result = null;
    330             try {
    331                 result = getConstructor(node).construct(node);
    332             } catch (Exception e) {
    333                 throw new ConstructorException(null, null, "Can't construct a java object for "
    334                         + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
    335             }
    336             return result;
    337         }
    338
    339         public void construct2ndStep(Node node, Object object) {
    340             try {
    341                 getConstructor(node).construct2ndStep(node, object);
    342             } catch (Exception e) {
    343                 throw new ConstructorException(null, null,
    344                         "Can't construct a second step for a java object for " + node.getTag()
    345                                 + "; exception=" + e.getMessage(), node.getStartMark(), e);
    346             }
    347         }
    348     }
    349
    350     /**
    351      * Construct scalar instance when the runtime class is known. Recursive
    352      * structures are not supported.
    353      */
    354     protected class ConstructScalar extends AbstractConstruct {
    355         public Object construct(Node nnode) {
    356             ScalarNode node = (ScalarNode) nnode;
    357             Class<?> type = node.getType();
    358             Object result;
    359             if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type)
    360                     || type == Boolean.class || Date.class.isAssignableFrom(type)
    361                     || type == Character.class || type == BigInteger.class
    362                     || type == BigDecimal.class || Enum.class.isAssignableFrom(type)
    363                     || Tag.BINARY.equals(node.getTag()) || Calendar.class.isAssignableFrom(type)) {
    364                 // standard classes created directly
    365                 result = constructStandardJavaInstance(type, node);
    366             } else {
    367                 // there must be only 1 constructor with 1 argument
    368                 java.lang.reflect.Constructor<?>[] javaConstructors = type.getConstructors();
    369                 int oneArgCount = 0;
    370                 java.lang.reflect.Constructor<?> javaConstructor = null;
    371                 for (java.lang.reflect.Constructor<?> c : javaConstructors) {
    372                     if (c.getParameterTypes().length == 1) {
    373                         oneArgCount++;
    374                         javaConstructor = c;
    375                     }
    376                 }
    377                 Object argument;
    378                 if (javaConstructor == null) {
    379                     throw new YAMLException("No single argument constructor found for " + type);
    380                 } else if (oneArgCount == 1) {
    381                     argument = constructStandardJavaInstance(
    382                             javaConstructor.getParameterTypes()[0], node);
    383                 } else {
    384                     // TODO it should be possible to use implicit types instead
    385                     // of forcing String. Resolver must be available here to
    386                     // obtain the implicit tag. Then we can set the tag and call
    387                     // callConstructor(node) to create the argument instance.
    388                     // On the other hand it may be safer to require a custom
    389                     // constructor to avoid guessing the argument class
    390                     argument = constructScalar(node);
    391                     try {
    392                         javaConstructor = type.getConstructor(String.class);
    393                     } catch (Exception e) {
    394                         throw new ConstructorException(null, null,
    395                                 "Can't construct a java object for scalar " + node.getTag()
    396                                         + "; No String constructor found. Exception="
    397                                         + e.getMessage(), node.getStartMark(), e);
    398                     }
    399                 }
    400                 try {
    401                     result = javaConstructor.newInstance(argument);
    402                 } catch (Exception e) {
    403                     throw new ConstructorException(null, null,
    404                             "Can't construct a java object for scalar " + node.getTag()
    405                                     + "; exception=" + e.getMessage(), node.getStartMark(), e);
    406                 }
    407             }
    408             return result;
    409         }
    410
    411         @SuppressWarnings("unchecked")
    412         private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type,
    413                 ScalarNode node) {
    414             Object result;
    415             if (type == String.class) {
    416                 Construct stringConstructor = yamlConstructors.get(Tag.STR);
    417                 result = stringConstructor.construct(node);
    418             } else if (type == Boolean.class || type == Boolean.TYPE) {
    419                 Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
    420                 result = boolConstructor.construct(node);
    421             } else if (type == Character.class || type == Character.TYPE) {
    422                 Construct charConstructor = yamlConstructors.get(Tag.STR);
    423                 String ch = (String) charConstructor.construct(node);
    424                 if (ch.length() == 0) {
    425                     result = null;
    426                 } else if (ch.length() != 1) {
    427                     throw new YAMLException("Invalid node Character: '" + ch + "'; length: "
    428                             + ch.length());
    429                 } else {
    430                     result = new Character(ch.charAt(0));
    431                 }
    432             } else if (Date.class.isAssignableFrom(type)) {
    433                 Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
    434                 Date date = (Date) dateConstructor.construct(node);
    435                 if (type == Date.class) {
    436                     result = date;
    437                 } else {
    438                     try {
    439                         java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
    440                         result = constr.newInstance(date.getTime());
    441                     } catch (Exception e) {
    442                         throw new YAMLException("Cannot construct: '" + type + "'");
    443                     }
    444                 }
    445             } else if (type == Float.class || type == Double.class || type == Float.TYPE
    446                     || type == Double.TYPE || type == BigDecimal.class) {
    447                 if (type == BigDecimal.class) {
    448                     result = new BigDecimal(node.getValue());
    449                 } else {
    450                     Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
    451                     result = doubleConstructor.construct(node);
    452                     if (type == Float.class || type == Float.TYPE) {
    453                         result = new Float((Double) result);
    454                     }
    455                 }
    456             } else if (type == Byte.class || type == Short.class || type == Integer.class
    457                     || type == Long.class || type == BigInteger.class || type == Byte.TYPE
    458                     || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
    459                 Construct intConstructor = yamlConstructors.get(Tag.INT);
    460                 result = intConstructor.construct(node);
    461                 if (type == Byte.class || type == Byte.TYPE) {
    462                     result = new Byte(result.toString());
    463                 } else if (type == Short.class || type == Short.TYPE) {
    464                     result = new Short(result.toString());
    465                 } else if (type == Integer.class || type == Integer.TYPE) {
    466                     result = new Integer(result.toString());
    467                 } else if (type == Long.class || type == Long.TYPE) {
    468                     result = new Long(result.toString());
    469                 } else {
    470                     // only BigInteger left
    471                     result = new BigInteger(result.toString());
    472                 }
    473             } else if (Enum.class.isAssignableFrom(type)) {
    474                 String enumValueName = node.getValue();
    475                 try {
    476                     result = Enum.valueOf(type, enumValueName);
    477                 } catch (Exception ex) {
    478                     throw new YAMLException("Unable to find enum value '" + enumValueName
    479                             + "' for enum class: " + type.getName());
    480                 }
    481             } else if (Calendar.class.isAssignableFrom(type)) {
    482                 ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
    483                 contr.construct(node);
    484                 result = contr.getCalendar();
    485             } else {
    486                 throw new YAMLException("Unsupported class: " + type);
    487             }
    488             return result;
    489         }
    490     }
    491
    492     /**
    493      * Construct sequence (List, Array, or immutable object) when the runtime
    494      * class is known.
    495      */
    496     protected class ConstructSequence implements Construct {
    497         @SuppressWarnings("unchecked")
    498         public Object construct(Node node) {
    499             SequenceNode snode = (SequenceNode) node;
    500             if (Set.class.isAssignableFrom(node.getType())) {
    501                 if (node.isTwoStepsConstruction()) {
    502                     throw new YAMLException("Set cannot be recursive.");
    503                 } else {
    504                     return constructSet(snode);
    505                 }
    506             } else if (Collection.class.isAssignableFrom(node.getType())) {
    507                 if (node.isTwoStepsConstruction()) {
    508                     return createDefaultList(snode.getValue().size());
    509                 } else {
    510                     return constructSequence(snode);
    511                 }
    512             } else if (node.getType().isArray()) {
    513                 if (node.isTwoStepsConstruction()) {
    514                     return createArray(node.getType(), snode.getValue().size());
    515                 } else {
    516                     return constructArray(snode);
    517                 }
    518             } else {
    519                 // create immutable object
    520                 List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>(
    521                         snode.getValue().size());
    522                 for (java.lang.reflect.Constructor<?> constructor : node.getType()
    523                         .getConstructors()) {
    524                     if (snode.getValue().size() == constructor.getParameterTypes().length) {
    525                         possibleConstructors.add(constructor);
    526                     }
    527                 }
    528                 if (!possibleConstructors.isEmpty()) {
    529                     if (possibleConstructors.size() == 1) {
    530                         Object[] argumentList = new Object[snode.getValue().size()];
    531                         java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
    532                         int index = 0;
    533                         for (Node argumentNode : snode.getValue()) {
    534                             Class<?> type = c.getParameterTypes()[index];
    535                             // set runtime classes for arguments
    536                             argumentNode.setType(type);
    537                             argumentList[index++] = constructObject(argumentNode);
    538                         }
    539
    540                         try {
    541                             return c.newInstance(argumentList);
    542                         } catch (Exception e) {
    543                             throw new YAMLException(e);
    544                         }
    545                     }
    546
    547                     // use BaseConstructor
    548                     List<Object> argumentList = (List<Object>) constructSequence(snode);
    549                     Class<?>[] parameterTypes = new Class[argumentList.size()];
    550                     int index = 0;
    551                     for (Object parameter : argumentList) {
    552                         parameterTypes[index] = parameter.getClass();
    553                         index++;
    554                     }
    555
    556                     for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
    557                         Class<?>[] argTypes = c.getParameterTypes();
    558                         boolean foundConstructor = true;
    559                         for (int i = 0; i < argTypes.length; i++) {
    560                             if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
    561                                 foundConstructor = false;
    562                                 break;
    563                             }
    564                         }
    565                         if (foundConstructor) {
    566                             try {
    567                                 return c.newInstance(argumentList.toArray());
    568                             } catch (Exception e) {
    569                                 throw new YAMLException(e);
    570                             }
    571                         }
    572                     }
    573                 }
    574                 throw new YAMLException("No suitable constructor with "
    575                         + String.valueOf(snode.getValue().size()) + " arguments found for "
    576                         + node.getType());
    577
    578             }
    579         }
    580
    581         private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
    582             if (!clazz.isPrimitive()) {
    583                 return clazz;
    584             }
    585             if (clazz == Integer.TYPE) {
    586                 return Integer.class;
    587             }
    588             if (clazz == Float.TYPE) {
    589                 return Float.class;
    590             }
    591             if (clazz == Double.TYPE) {
    592                 return Double.class;
    593             }
    594             if (clazz == Boolean.TYPE) {
    595                 return Boolean.class;
    596             }
    597             if (clazz == Long.TYPE) {
    598                 return Long.class;
    599             }
    600             if (clazz == Character.TYPE) {
    601                 return Character.class;
    602             }
    603             if (clazz == Short.TYPE) {
    604                 return Short.class;
    605             }
    606             if (clazz == Byte.TYPE) {
    607                 return Byte.class;
    608             }
    609             throw new YAMLException("Unexpected primitive " + clazz);
    610         }
    611
    612         @SuppressWarnings("unchecked")
    613         public void construct2ndStep(Node node, Object object) {
    614             SequenceNode snode = (SequenceNode) node;
    615             if (List.class.isAssignableFrom(node.getType())) {
    616                 List<Object> list = (List<Object>) object;
    617                 constructSequenceStep2(snode, list);
    618             } else if (node.getType().isArray()) {
    619                 constructArrayStep2(snode, object);
    620             } else {
    621                 throw new YAMLException("Immutable objects cannot be recursive.");
    622             }
    623         }
    624     }
    625
    626     protected Class<?> getClassForNode(Node node) {
    627         Class<? extends Object> classForTag = typeTags.get(node.getTag());
    628         if (classForTag == null) {
    629             String name = node.getTag().getClassName();
    630             Class<?> cl;
    631             try {
    632                 cl = getClassForName(name);
    633             } catch (ClassNotFoundException e) {
    634                 throw new YAMLException("Class not found: " + name);
    635             }
    636             typeTags.put(node.getTag(), cl);
    637             return cl;
    638         } else {
    639             return classForTag;
    640         }
    641     }
    642
    643     protected Class<?> getClassForName(String name) throws ClassNotFoundException {
    644         return Class.forName(name);
    645     }
    646 }