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