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.beans.IntrospectionException; 19 import java.math.BigDecimal; 20 import java.math.BigInteger; 21 import java.util.ArrayList; 22 import java.util.Calendar; 23 import java.util.Collection; 24 import java.util.Date; 25 import java.util.HashMap; 26 import java.util.List; 27 import java.util.Map; 28 import java.util.Properties; 29 import java.util.Set; 30 import java.util.SortedMap; 31 import java.util.SortedSet; 32 import java.util.TreeMap; 33 import java.util.TreeSet; 34 import java.util.UUID; 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 protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions; 53 Constructor()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 */ Constructor(Class<? extends Object> theRoot)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 */ checkRoot(Class<? extends Object> theRoot)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 Constructor(TypeDescription theRoot)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 */ Constructor(String theRoot)103 public Constructor(String theRoot) throws ClassNotFoundException { 104 this(Class.forName(check(theRoot))); 105 } 106 check(String s)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 */ addTypeDescription(TypeDescription definition)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 */ construct(Node node)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") construct2ndStep(Node node, Object object)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 createEmptyJavaBean(MappingNode node)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 constructJavaBean2ndStep(MappingNode node, Object object)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 getProperty(Class<? extends Object> type, String name)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 getConstructor(Node node)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 construct(Node node)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 construct2ndStep(Node node, Object object)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 { construct(Node nnode)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") constructStandardJavaInstance(@uppressWarnings"rawtypes") Class type, ScalarNode node)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") construct(Node node)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 wrapIfPrimitive(Class<?> clazz)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") construct2ndStep(Node node, Object object)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 getClassForNode(Node node)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 getClassForName(String name)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