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 */
16package org.yaml.snakeyaml.constructor;
17
18import java.beans.IntrospectionException;
19import java.math.BigDecimal;
20import java.math.BigInteger;
21import java.util.ArrayList;
22import java.util.Calendar;
23import java.util.Collection;
24import java.util.Date;
25import java.util.HashMap;
26import java.util.List;
27import java.util.Map;
28import java.util.Properties;
29import java.util.Set;
30import java.util.SortedMap;
31import java.util.SortedSet;
32import java.util.TreeMap;
33import java.util.TreeSet;
34import java.util.UUID;
35
36import org.yaml.snakeyaml.TypeDescription;
37import org.yaml.snakeyaml.error.YAMLException;
38import org.yaml.snakeyaml.introspector.Property;
39import org.yaml.snakeyaml.nodes.MappingNode;
40import org.yaml.snakeyaml.nodes.Node;
41import org.yaml.snakeyaml.nodes.NodeId;
42import org.yaml.snakeyaml.nodes.NodeTuple;
43import org.yaml.snakeyaml.nodes.ScalarNode;
44import org.yaml.snakeyaml.nodes.SequenceNode;
45import org.yaml.snakeyaml.nodes.Tag;
46
47/**
48 * Construct a custom Java instance.
49 */
50public class Constructor extends SafeConstructor {
51    private final Map<Tag, Class<? extends Object>> typeTags;
52    protected 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                        default: // scalar
268                        }
269                    }
270                    if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
271                        // only if there is no explicit TypeDescription
272                        Class<?>[] arguments = property.getActualTypeArguments();
273                        if (arguments != null && arguments.length > 0) {
274                            // type safe (generic) collection may contain the
275                            // proper class
276                            if (valueNode.getNodeId() == NodeId.sequence) {
277                                Class<?> t = arguments[0];
278                                SequenceNode snode = (SequenceNode) valueNode;
279                                snode.setListType(t);
280                            } else if (valueNode.getTag().equals(Tag.SET)) {
281                                Class<?> t = arguments[0];
282                                MappingNode mnode = (MappingNode) valueNode;
283                                mnode.setOnlyKeyType(t);
284                                mnode.setUseClassConstructor(true);
285                            } else if (property.getType().isAssignableFrom(Map.class)) {
286                                Class<?> ketType = arguments[0];
287                                Class<?> valueType = arguments[1];
288                                MappingNode mnode = (MappingNode) valueNode;
289                                mnode.setTypes(ketType, valueType);
290                                mnode.setUseClassConstructor(true);
291                            } else {
292                                // the type for collection entries cannot be
293                                // detected
294                            }
295                        }
296                    }
297
298                    Object value = constructObject(valueNode);
299                    // Correct when the property expects float but double was
300                    // constructed
301                    if (property.getType() == Float.TYPE || property.getType() == Float.class) {
302                        if (value instanceof Double) {
303                            value = ((Double) value).floatValue();
304                        }
305                    }
306                    // Correct when the property a String but the value is binary
307                    if (property.getType() == String.class && Tag.BINARY.equals(valueNode.getTag()) && value instanceof byte[]) {
308                        value = new String((byte[])value);
309                    }
310
311                    property.set(object, value);
312                } catch (Exception e) {
313                    throw new ConstructorException("Cannot create property=" + key
314                            + " for JavaBean=" + object, node.getStartMark(), e.getMessage(),
315                            valueNode.getStartMark(), e);
316                }
317            }
318            return object;
319        }
320
321        protected Property getProperty(Class<? extends Object> type, String name)
322                throws IntrospectionException {
323            return getPropertyUtils().getProperty(type, name);
324        }
325    }
326
327    /**
328     * Construct an instance when the runtime class is not known but a global
329     * tag with a class name is defined. It delegates the construction to the
330     * appropriate constructor based on the node kind (scalar, sequence,
331     * mapping)
332     */
333    protected class ConstructYamlObject implements Construct {
334
335        private Construct getConstructor(Node node) {
336            Class<?> cl = getClassForNode(node);
337            node.setType(cl);
338            // call the constructor as if the runtime class is defined
339            Construct constructor = yamlClassConstructors.get(node.getNodeId());
340            return constructor;
341        }
342
343        public Object construct(Node node) {
344            Object result = null;
345            try {
346                result = getConstructor(node).construct(node);
347            } catch (ConstructorException e) {
348                throw e;
349            } catch (Exception e) {
350                throw new ConstructorException(null, null, "Can't construct a java object for "
351                        + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
352            }
353            return result;
354        }
355
356        public void construct2ndStep(Node node, Object object) {
357            try {
358                getConstructor(node).construct2ndStep(node, object);
359            } catch (Exception e) {
360                throw new ConstructorException(null, null,
361                        "Can't construct a second step for a java object for " + node.getTag()
362                                + "; exception=" + e.getMessage(), node.getStartMark(), e);
363            }
364        }
365    }
366
367    /**
368     * Construct scalar instance when the runtime class is known. Recursive
369     * structures are not supported.
370     */
371    protected class ConstructScalar extends AbstractConstruct {
372        public Object construct(Node nnode) {
373            ScalarNode node = (ScalarNode) nnode;
374            Class<?> type = node.getType();
375            Object result;
376            if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type)
377                    || type == Boolean.class || Date.class.isAssignableFrom(type)
378                    || type == Character.class || type == BigInteger.class
379                    || type == BigDecimal.class || Enum.class.isAssignableFrom(type)
380                    || Tag.BINARY.equals(node.getTag()) || Calendar.class.isAssignableFrom(type) || type == UUID.class) {
381                // standard classes created directly
382                result = constructStandardJavaInstance(type, node);
383            } else {
384                // there must be only 1 constructor with 1 argument
385                java.lang.reflect.Constructor<?>[] javaConstructors = type
386                        .getDeclaredConstructors();
387                int oneArgCount = 0;
388                java.lang.reflect.Constructor<?> javaConstructor = null;
389                for (java.lang.reflect.Constructor<?> c : javaConstructors) {
390                    if (c.getParameterTypes().length == 1) {
391                        oneArgCount++;
392                        javaConstructor = c;
393                    }
394                }
395                Object argument;
396                if (javaConstructor == null) {
397                    throw new YAMLException("No single argument constructor found for " + type);
398                } else if (oneArgCount == 1) {
399                    argument = constructStandardJavaInstance(
400                            javaConstructor.getParameterTypes()[0], node);
401                } else {
402                    // TODO it should be possible to use implicit types instead
403                    // of forcing String. Resolver must be available here to
404                    // obtain the implicit tag. Then we can set the tag and call
405                    // callConstructor(node) to create the argument instance.
406                    // On the other hand it may be safer to require a custom
407                    // constructor to avoid guessing the argument class
408                    argument = constructScalar(node);
409                    try {
410                        javaConstructor = type.getDeclaredConstructor(String.class);
411                    } catch (Exception e) {
412                        throw new YAMLException("Can't construct a java object for scalar "
413                                + node.getTag() + "; No String constructor found. Exception="
414                                + e.getMessage(), e);
415                    }
416                }
417                try {
418                    javaConstructor.setAccessible(true);
419                    result = javaConstructor.newInstance(argument);
420                } catch (Exception e) {
421                    throw new ConstructorException(null, null,
422                            "Can't construct a java object for scalar " + node.getTag()
423                                    + "; exception=" + e.getMessage(), node.getStartMark(), e);
424                }
425            }
426            return result;
427        }
428
429        @SuppressWarnings("unchecked")
430        private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes")
431        Class type, ScalarNode node) {
432            Object result;
433            if (type == String.class) {
434                Construct stringConstructor = yamlConstructors.get(Tag.STR);
435                result = stringConstructor.construct(node);
436            } else if (type == Boolean.class || type == Boolean.TYPE) {
437                Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
438                result = boolConstructor.construct(node);
439            } else if (type == Character.class || type == Character.TYPE) {
440                Construct charConstructor = yamlConstructors.get(Tag.STR);
441                String ch = (String) charConstructor.construct(node);
442                if (ch.length() == 0) {
443                    result = null;
444                } else if (ch.length() != 1) {
445                    throw new YAMLException("Invalid node Character: '" + ch + "'; length: "
446                            + ch.length());
447                } else {
448                    result = Character.valueOf(ch.charAt(0));
449                }
450            } else if (Date.class.isAssignableFrom(type)) {
451                Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
452                Date date = (Date) dateConstructor.construct(node);
453                if (type == Date.class) {
454                    result = date;
455                } else {
456                    try {
457                        java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
458                        result = constr.newInstance(date.getTime());
459                    } catch (RuntimeException e) {
460                        throw e;
461                    } catch (Exception e) {
462                        throw new YAMLException("Cannot construct: '" + type + "'");
463                    }
464                }
465            } else if (type == Float.class || type == Double.class || type == Float.TYPE
466                    || type == Double.TYPE || type == BigDecimal.class) {
467                if (type == BigDecimal.class) {
468                    result = new BigDecimal(node.getValue());
469                } else {
470                    Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
471                    result = doubleConstructor.construct(node);
472                    if (type == Float.class || type == Float.TYPE) {
473                        result = new Float((Double) result);
474                    }
475                }
476            } else if (type == Byte.class || type == Short.class || type == Integer.class
477                    || type == Long.class || type == BigInteger.class || type == Byte.TYPE
478                    || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
479                Construct intConstructor = yamlConstructors.get(Tag.INT);
480                result = intConstructor.construct(node);
481                if (type == Byte.class || type == Byte.TYPE) {
482                    result = Byte.valueOf(result.toString());
483                } else if (type == Short.class || type == Short.TYPE) {
484                    result = Short.valueOf(result.toString());
485                } else if (type == Integer.class || type == Integer.TYPE) {
486                    result = Integer.parseInt(result.toString());
487                } else if (type == Long.class || type == Long.TYPE) {
488                    result = Long.valueOf(result.toString());
489                } else {
490                    // only BigInteger left
491                    result = new BigInteger(result.toString());
492                }
493            } else if (Enum.class.isAssignableFrom(type)) {
494                String enumValueName = node.getValue();
495                try {
496                    result = Enum.valueOf(type, enumValueName);
497                } catch (Exception ex) {
498                    throw new YAMLException("Unable to find enum value '" + enumValueName
499                            + "' for enum class: " + type.getName());
500                }
501            } else if (Calendar.class.isAssignableFrom(type)) {
502                ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
503                contr.construct(node);
504                result = contr.getCalendar();
505            } else if (Number.class.isAssignableFrom(type)) {
506                ConstructYamlNumber contr = new ConstructYamlNumber();
507                result = contr.construct(node);
508            }  else if (UUID.class == type) {
509                result = UUID.fromString(node.getValue());
510            } else {
511                if (yamlConstructors.containsKey(node.getTag())) {
512                    result = yamlConstructors.get(node.getTag()).construct(node);
513                } else {
514                    throw new YAMLException("Unsupported class: " + type);
515                }
516            }
517            return result;
518        }
519    }
520
521    /**
522     * Construct sequence (List, Array, or immutable object) when the runtime
523     * class is known.
524     */
525    protected class ConstructSequence implements Construct {
526        @SuppressWarnings("unchecked")
527        public Object construct(Node node) {
528            SequenceNode snode = (SequenceNode) node;
529            if (Set.class.isAssignableFrom(node.getType())) {
530                if (node.isTwoStepsConstruction()) {
531                    throw new YAMLException("Set cannot be recursive.");
532                } else {
533                    return constructSet(snode);
534                }
535            } else if (Collection.class.isAssignableFrom(node.getType())) {
536                if (node.isTwoStepsConstruction()) {
537                    return createDefaultList(snode.getValue().size());
538                } else {
539                    return constructSequence(snode);
540                }
541            } else if (node.getType().isArray()) {
542                if (node.isTwoStepsConstruction()) {
543                    return createArray(node.getType(), snode.getValue().size());
544                } else {
545                    return constructArray(snode);
546                }
547            } else {
548                // create immutable object
549                List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>(
550                        snode.getValue().size());
551                for (java.lang.reflect.Constructor<?> constructor : node
552                        .getType().getDeclaredConstructors()) {
553                    if (snode.getValue()
554                            .size() == constructor.getParameterTypes().length) {
555                        possibleConstructors.add(constructor);
556                    }
557                }
558                if (!possibleConstructors.isEmpty()) {
559                    if (possibleConstructors.size() == 1) {
560                        Object[] argumentList = new Object[snode.getValue().size()];
561                        java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
562                        int index = 0;
563                        for (Node argumentNode : snode.getValue()) {
564                            Class<?> type = c.getParameterTypes()[index];
565                            // set runtime classes for arguments
566                            argumentNode.setType(type);
567                            argumentList[index++] = constructObject(argumentNode);
568                        }
569
570                        try {
571                            c.setAccessible(true);
572                            return c.newInstance(argumentList);
573                        } catch (Exception e) {
574                            throw new YAMLException(e);
575                        }
576                    }
577
578                    // use BaseConstructor
579                    List<Object> argumentList = (List<Object>) constructSequence(snode);
580                    Class<?>[] parameterTypes = new Class[argumentList.size()];
581                    int index = 0;
582                    for (Object parameter : argumentList) {
583                        parameterTypes[index] = parameter.getClass();
584                        index++;
585                    }
586
587                    for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
588                        Class<?>[] argTypes = c.getParameterTypes();
589                        boolean foundConstructor = true;
590                        for (int i = 0; i < argTypes.length; i++) {
591                            if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
592                                foundConstructor = false;
593                                break;
594                            }
595                        }
596                        if (foundConstructor) {
597                            try {
598                                c.setAccessible(true);
599                                return c.newInstance(argumentList.toArray());
600                            } catch (Exception e) {
601                                throw new YAMLException(e);
602                            }
603                        }
604                    }
605                }
606                throw new YAMLException("No suitable constructor with "
607                        + String.valueOf(snode.getValue().size()) + " arguments found for "
608                        + node.getType());
609
610            }
611        }
612
613        private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
614            if (!clazz.isPrimitive()) {
615                return clazz;
616            }
617            if (clazz == Integer.TYPE) {
618                return Integer.class;
619            }
620            if (clazz == Float.TYPE) {
621                return Float.class;
622            }
623            if (clazz == Double.TYPE) {
624                return Double.class;
625            }
626            if (clazz == Boolean.TYPE) {
627                return Boolean.class;
628            }
629            if (clazz == Long.TYPE) {
630                return Long.class;
631            }
632            if (clazz == Character.TYPE) {
633                return Character.class;
634            }
635            if (clazz == Short.TYPE) {
636                return Short.class;
637            }
638            if (clazz == Byte.TYPE) {
639                return Byte.class;
640            }
641            throw new YAMLException("Unexpected primitive " + clazz);
642        }
643
644        @SuppressWarnings("unchecked")
645        public void construct2ndStep(Node node, Object object) {
646            SequenceNode snode = (SequenceNode) node;
647            if (List.class.isAssignableFrom(node.getType())) {
648                List<Object> list = (List<Object>) object;
649                constructSequenceStep2(snode, list);
650            } else if (node.getType().isArray()) {
651                constructArrayStep2(snode, object);
652            } else {
653                throw new YAMLException("Immutable objects cannot be recursive.");
654            }
655        }
656    }
657
658    protected Class<?> getClassForNode(Node node) {
659        Class<? extends Object> classForTag = typeTags.get(node.getTag());
660        if (classForTag == null) {
661            String name = node.getTag().getClassName();
662            Class<?> cl;
663            try {
664                cl = getClassForName(name);
665            } catch (ClassNotFoundException e) {
666                throw new YAMLException("Class not found: " + name);
667            }
668            typeTags.put(node.getTag(), cl);
669            return cl;
670        } else {
671            return classForTag;
672        }
673    }
674
675    protected Class<?> getClassForName(String name) throws ClassNotFoundException {
676        try {
677            return Class.forName(name, true, Thread.currentThread().getContextClassLoader());
678        } catch (ClassNotFoundException e) {
679            return Class.forName(name);
680        }
681    }
682}
683