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.lang.reflect.Array; 17 import java.lang.reflect.Modifier; 18 import java.util.ArrayList; 19 import java.util.Collection; 20 import java.util.EnumMap; 21 import java.util.HashMap; 22 import java.util.HashSet; 23 import java.util.LinkedHashMap; 24 import java.util.LinkedHashSet; 25 import java.util.List; 26 import java.util.Map; 27 import java.util.NoSuchElementException; 28 import java.util.Set; 29 import java.util.SortedMap; 30 import java.util.SortedSet; 31 import java.util.TreeMap; 32 import java.util.TreeSet; 33 import org.yaml.snakeyaml.LoaderOptions; 34 import org.yaml.snakeyaml.TypeDescription; 35 import org.yaml.snakeyaml.composer.Composer; 36 import org.yaml.snakeyaml.composer.ComposerException; 37 import org.yaml.snakeyaml.error.YAMLException; 38 import org.yaml.snakeyaml.introspector.PropertyUtils; 39 import org.yaml.snakeyaml.nodes.CollectionNode; 40 import org.yaml.snakeyaml.nodes.MappingNode; 41 import org.yaml.snakeyaml.nodes.Node; 42 import org.yaml.snakeyaml.nodes.NodeId; 43 import org.yaml.snakeyaml.nodes.NodeTuple; 44 import org.yaml.snakeyaml.nodes.ScalarNode; 45 import org.yaml.snakeyaml.nodes.SequenceNode; 46 import org.yaml.snakeyaml.nodes.Tag; 47 48 public abstract class BaseConstructor { 49 50 /** 51 * An instance returned by newInstance methods when instantiation has not been performed. 52 */ 53 protected static final Object NOT_INSTANTIATED_OBJECT = new Object(); 54 55 /** 56 * It maps the node kind to the the Construct implementation. When the runtime class is known then 57 * the implicit tag is ignored. 58 */ 59 protected final Map<NodeId, Construct> yamlClassConstructors = 60 new EnumMap<NodeId, Construct>(NodeId.class); 61 /** 62 * It maps the (explicit or implicit) tag to the Construct implementation. It is used: 1) explicit 63 * tag - if present. 2) implicit tag - when the runtime class of the instance is unknown (the node 64 * has the Object.class) 65 */ 66 protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>(); 67 /** 68 * It maps the (explicit or implicit) tag to the Construct implementation. It is used when no 69 * exact match found. 70 */ 71 protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>(); 72 73 protected Composer composer; 74 final Map<Node, Object> constructedObjects; 75 private final Set<Node> recursiveObjects; 76 private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill; 77 private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill; 78 79 protected Tag rootTag; 80 private PropertyUtils propertyUtils; 81 private boolean explicitPropertyUtils; 82 private boolean allowDuplicateKeys = true; 83 private boolean wrappedToRootException = false; 84 85 private boolean enumCaseSensitive = false; 86 87 protected final Map<Class<? extends Object>, TypeDescription> typeDefinitions; 88 protected final Map<Tag, Class<? extends Object>> typeTags; 89 90 protected LoaderOptions loadingConfig; 91 BaseConstructor()92 public BaseConstructor() { 93 this(new LoaderOptions()); 94 } 95 BaseConstructor(LoaderOptions loadingConfig)96 public BaseConstructor(LoaderOptions loadingConfig) { 97 constructedObjects = new HashMap<Node, Object>(); 98 recursiveObjects = new HashSet<Node>(); 99 maps2fill = 100 new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>(); 101 sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>(); 102 typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>(); 103 typeTags = new HashMap<Tag, Class<? extends Object>>(); 104 105 rootTag = null; 106 explicitPropertyUtils = false; 107 108 typeDefinitions.put(SortedMap.class, 109 new TypeDescription(SortedMap.class, Tag.OMAP, TreeMap.class)); 110 typeDefinitions.put(SortedSet.class, 111 new TypeDescription(SortedSet.class, Tag.SET, TreeSet.class)); 112 this.loadingConfig = loadingConfig; 113 } 114 setComposer(Composer composer)115 public void setComposer(Composer composer) { 116 this.composer = composer; 117 } 118 119 /** 120 * Check if more documents available 121 * 122 * @return true when there are more YAML documents in the stream 123 */ checkData()124 public boolean checkData() { 125 // If there are more documents available? 126 return composer.checkNode(); 127 } 128 129 /** 130 * Construct and return the next document 131 * 132 * @return constructed instance 133 */ getData()134 public Object getData() throws NoSuchElementException { 135 // Construct and return the next document. 136 if (!composer.checkNode()) { 137 throw new NoSuchElementException("No document is available."); 138 } 139 Node node = composer.getNode(); 140 if (rootTag != null) { 141 node.setTag(rootTag); 142 } 143 return constructDocument(node); 144 } 145 146 /** 147 * Ensure that the stream contains a single document and construct it 148 * 149 * @param type the class of the instance being created 150 * @return constructed instance 151 * @throws ComposerException in case there are more documents in the stream 152 */ getSingleData(Class<?> type)153 public Object getSingleData(Class<?> type) { 154 // Ensure that the stream contains a single document and construct it 155 final Node node = composer.getSingleNode(); 156 if (node != null && !Tag.NULL.equals(node.getTag())) { 157 if (Object.class != type) { 158 node.setTag(new Tag(type)); 159 } else if (rootTag != null) { 160 node.setTag(rootTag); 161 } 162 return constructDocument(node); 163 } else { 164 Construct construct = yamlConstructors.get(Tag.NULL); 165 return construct.construct(node); 166 } 167 } 168 169 /** 170 * Construct complete YAML document. Call the second step in case of recursive structures. At the 171 * end cleans all the state. 172 * 173 * @param node root Node 174 * @return Java instance 175 */ constructDocument(Node node)176 protected final Object constructDocument(Node node) { 177 try { 178 Object data = constructObject(node); 179 fillRecursive(); 180 return data; 181 } catch (RuntimeException e) { 182 if (wrappedToRootException && !(e instanceof YAMLException)) { 183 throw new YAMLException(e); 184 } else { 185 throw e; 186 } 187 } finally { 188 // clean up resources 189 constructedObjects.clear(); 190 recursiveObjects.clear(); 191 } 192 } 193 194 /** 195 * Fill the recursive structures and clean the internal collections 196 */ fillRecursive()197 private void fillRecursive() { 198 if (!maps2fill.isEmpty()) { 199 for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) { 200 RecursiveTuple<Object, Object> key_value = entry._2(); 201 entry._1().put(key_value._1(), key_value._2()); 202 } 203 maps2fill.clear(); 204 } 205 if (!sets2fill.isEmpty()) { 206 for (RecursiveTuple<Set<Object>, Object> value : sets2fill) { 207 value._1().add(value._2()); 208 } 209 sets2fill.clear(); 210 } 211 } 212 213 /** 214 * Construct object from the specified Node. Return existing instance if the node is already 215 * constructed. 216 * 217 * @param node Node to be constructed 218 * @return Java instance 219 */ constructObject(Node node)220 protected Object constructObject(Node node) { 221 if (constructedObjects.containsKey(node)) { 222 return constructedObjects.get(node); 223 } 224 return constructObjectNoCheck(node); 225 } 226 constructObjectNoCheck(Node node)227 protected Object constructObjectNoCheck(Node node) { 228 if (recursiveObjects.contains(node)) { 229 throw new ConstructorException(null, null, "found unconstructable recursive node", 230 node.getStartMark()); 231 } 232 recursiveObjects.add(node); 233 Construct constructor = getConstructor(node); 234 Object data = (constructedObjects.containsKey(node)) ? constructedObjects.get(node) 235 : constructor.construct(node); 236 237 finalizeConstruction(node, data); 238 constructedObjects.put(node, data); 239 recursiveObjects.remove(node); 240 if (node.isTwoStepsConstruction()) { 241 constructor.construct2ndStep(node, data); 242 } 243 return data; 244 } 245 246 /** 247 * Get the constructor to construct the Node. For implicit tags if the runtime class is known a 248 * dedicated Construct implementation is used. Otherwise the constructor is chosen by the tag. 249 * 250 * @param node {@link Node} to construct an instance from 251 * @return {@link Construct} implementation for the specified node 252 */ getConstructor(Node node)253 protected Construct getConstructor(Node node) { 254 if (node.useClassConstructor()) { 255 return yamlClassConstructors.get(node.getNodeId()); 256 } else { 257 Construct constructor = yamlConstructors.get(node.getTag()); 258 if (constructor == null) { 259 for (String prefix : yamlMultiConstructors.keySet()) { 260 if (node.getTag().startsWith(prefix)) { 261 return yamlMultiConstructors.get(prefix); 262 } 263 } 264 return yamlConstructors.get(null); 265 } 266 return constructor; 267 } 268 } 269 constructScalar(ScalarNode node)270 protected String constructScalar(ScalarNode node) { 271 return node.getValue(); 272 } 273 274 // >>>> DEFAULTS >>>> createDefaultList(int initSize)275 protected List<Object> createDefaultList(int initSize) { 276 return new ArrayList<Object>(initSize); 277 } 278 createDefaultSet(int initSize)279 protected Set<Object> createDefaultSet(int initSize) { 280 return new LinkedHashSet<Object>(initSize); 281 } 282 createDefaultMap(int initSize)283 protected Map<Object, Object> createDefaultMap(int initSize) { 284 // respect order from YAML document 285 return new LinkedHashMap<Object, Object>(initSize); 286 } 287 createArray(Class<?> type, int size)288 protected Object createArray(Class<?> type, int size) { 289 return Array.newInstance(type.getComponentType(), size); 290 } 291 292 // <<<< DEFAULTS <<<< 293 finalizeConstruction(Node node, Object data)294 protected Object finalizeConstruction(Node node, Object data) { 295 final Class<? extends Object> type = node.getType(); 296 if (typeDefinitions.containsKey(type)) { 297 return typeDefinitions.get(type).finalizeConstruction(data); 298 } 299 return data; 300 } 301 302 // >>>> NEW instance newInstance(Node node)303 protected Object newInstance(Node node) { 304 return newInstance(Object.class, node); 305 } 306 newInstance(Class<?> ancestor, Node node)307 protected final Object newInstance(Class<?> ancestor, Node node) { 308 return newInstance(ancestor, node, true); 309 } 310 311 /** 312 * Tries to create a new object for the node. 313 * 314 * @param ancestor expected ancestor of the {@code node.getType()} 315 * @param node for which to create a corresponding java object 316 * @param tryDefault should default constructor to be tried when there is no corresponding 317 * {@code TypeDescription} or {@code TypeDescription.newInstance(node)} returns 318 * {@code null}. 319 * 320 * @return - a new object created for {@code node.getType()} by using corresponding 321 * TypeDescription.newInstance or default constructor. - {@code NOT_INSTANTIATED_OBJECT} 322 * in case no object has been created 323 */ newInstance(Class<?> ancestor, Node node, boolean tryDefault)324 protected Object newInstance(Class<?> ancestor, Node node, boolean tryDefault) { 325 try { 326 final Class<? extends Object> type = node.getType(); 327 if (typeDefinitions.containsKey(type)) { 328 TypeDescription td = typeDefinitions.get(type); 329 final Object instance = td.newInstance(node); 330 if (instance != null) { 331 return instance; 332 } 333 } 334 335 if (tryDefault) { 336 /* 337 * Removed <code> have InstantiationException in case of abstract type 338 */ 339 if (ancestor.isAssignableFrom(type) && !Modifier.isAbstract(type.getModifiers())) { 340 java.lang.reflect.Constructor<?> c = type.getDeclaredConstructor(); 341 c.setAccessible(true); 342 return c.newInstance(); 343 } 344 } 345 } catch (Exception e) { 346 throw new YAMLException(e); 347 } 348 349 return NOT_INSTANTIATED_OBJECT; 350 } 351 352 @SuppressWarnings("unchecked") newSet(CollectionNode<?> node)353 protected Set<Object> newSet(CollectionNode<?> node) { 354 Object instance = newInstance(Set.class, node); 355 if (instance != NOT_INSTANTIATED_OBJECT) { 356 return (Set<Object>) instance; 357 } else { 358 return createDefaultSet(node.getValue().size()); 359 } 360 } 361 362 @SuppressWarnings("unchecked") newList(SequenceNode node)363 protected List<Object> newList(SequenceNode node) { 364 Object instance = newInstance(List.class, node); 365 if (instance != NOT_INSTANTIATED_OBJECT) { 366 return (List<Object>) instance; 367 } else { 368 return createDefaultList(node.getValue().size()); 369 } 370 } 371 372 @SuppressWarnings("unchecked") newMap(MappingNode node)373 protected Map<Object, Object> newMap(MappingNode node) { 374 Object instance = newInstance(Map.class, node); 375 if (instance != NOT_INSTANTIATED_OBJECT) { 376 return (Map<Object, Object>) instance; 377 } else { 378 return createDefaultMap(node.getValue().size()); 379 } 380 } 381 382 // <<<< NEW instance 383 384 // >>>> Construct => NEW, 2ndStep(filling) constructSequence(SequenceNode node)385 protected List<? extends Object> constructSequence(SequenceNode node) { 386 List<Object> result = newList(node); 387 constructSequenceStep2(node, result); 388 return result; 389 } 390 constructSet(SequenceNode node)391 protected Set<? extends Object> constructSet(SequenceNode node) { 392 Set<Object> result = newSet(node); 393 constructSequenceStep2(node, result); 394 return result; 395 } 396 constructArray(SequenceNode node)397 protected Object constructArray(SequenceNode node) { 398 return constructArrayStep2(node, createArray(node.getType(), node.getValue().size())); 399 } 400 constructSequenceStep2(SequenceNode node, Collection<Object> collection)401 protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) { 402 for (Node child : node.getValue()) { 403 collection.add(constructObject(child)); 404 } 405 } 406 constructArrayStep2(SequenceNode node, Object array)407 protected Object constructArrayStep2(SequenceNode node, Object array) { 408 final Class<?> componentType = node.getType().getComponentType(); 409 410 int index = 0; 411 for (Node child : node.getValue()) { 412 // Handle multi-dimensional arrays... 413 if (child.getType() == Object.class) { 414 child.setType(componentType); 415 } 416 417 final Object value = constructObject(child); 418 419 if (componentType.isPrimitive()) { 420 // Null values are disallowed for primitives 421 if (value == null) { 422 throw new NullPointerException("Unable to construct element value for " + child); 423 } 424 425 // Primitive arrays require quite a lot of work. 426 if (byte.class.equals(componentType)) { 427 Array.setByte(array, index, ((Number) value).byteValue()); 428 429 } else if (short.class.equals(componentType)) { 430 Array.setShort(array, index, ((Number) value).shortValue()); 431 432 } else if (int.class.equals(componentType)) { 433 Array.setInt(array, index, ((Number) value).intValue()); 434 435 } else if (long.class.equals(componentType)) { 436 Array.setLong(array, index, ((Number) value).longValue()); 437 438 } else if (float.class.equals(componentType)) { 439 Array.setFloat(array, index, ((Number) value).floatValue()); 440 441 } else if (double.class.equals(componentType)) { 442 Array.setDouble(array, index, ((Number) value).doubleValue()); 443 444 } else if (char.class.equals(componentType)) { 445 Array.setChar(array, index, ((Character) value).charValue()); 446 447 } else if (boolean.class.equals(componentType)) { 448 Array.setBoolean(array, index, ((Boolean) value).booleanValue()); 449 450 } else { 451 throw new YAMLException("unexpected primitive type"); 452 } 453 454 } else { 455 // Non-primitive arrays can simply be assigned: 456 Array.set(array, index, value); 457 } 458 459 ++index; 460 } 461 return array; 462 } 463 constructSet(MappingNode node)464 protected Set<Object> constructSet(MappingNode node) { 465 final Set<Object> set = newSet(node); 466 constructSet2ndStep(node, set); 467 return set; 468 } 469 constructMapping(MappingNode node)470 protected Map<Object, Object> constructMapping(MappingNode node) { 471 final Map<Object, Object> mapping = newMap(node); 472 constructMapping2ndStep(node, mapping); 473 return mapping; 474 } 475 constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping)476 protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { 477 List<NodeTuple> nodeValue = node.getValue(); 478 for (NodeTuple tuple : nodeValue) { 479 Node keyNode = tuple.getKeyNode(); 480 Node valueNode = tuple.getValueNode(); 481 Object key = constructObject(keyNode); 482 if (key != null) { 483 try { 484 key.hashCode();// check circular dependencies 485 } catch (Exception e) { 486 throw new ConstructorException("while constructing a mapping", node.getStartMark(), 487 "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e); 488 } 489 } 490 Object value = constructObject(valueNode); 491 if (keyNode.isTwoStepsConstruction()) { 492 if (loadingConfig.getAllowRecursiveKeys()) { 493 postponeMapFilling(mapping, key, value); 494 } else { 495 throw new YAMLException( 496 "Recursive key for mapping is detected but it is not configured to be allowed."); 497 } 498 } else { 499 mapping.put(key, value); 500 } 501 } 502 } 503 504 /* 505 * if keyObject is created it 2 steps we should postpone putting it in map because it may have 506 * different hash after initialization compared to clean just created one. And map of course does 507 * not observe key hashCode changes. 508 */ postponeMapFilling(Map<Object, Object> mapping, Object key, Object value)509 protected void postponeMapFilling(Map<Object, Object> mapping, Object key, Object value) { 510 maps2fill.add(0, new RecursiveTuple(mapping, new RecursiveTuple(key, value))); 511 } 512 constructSet2ndStep(MappingNode node, Set<Object> set)513 protected void constructSet2ndStep(MappingNode node, Set<Object> set) { 514 List<NodeTuple> nodeValue = node.getValue(); 515 for (NodeTuple tuple : nodeValue) { 516 Node keyNode = tuple.getKeyNode(); 517 Object key = constructObject(keyNode); 518 if (key != null) { 519 try { 520 key.hashCode();// check circular dependencies 521 } catch (Exception e) { 522 throw new ConstructorException("while constructing a Set", node.getStartMark(), 523 "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e); 524 } 525 } 526 if (keyNode.isTwoStepsConstruction()) { 527 postponeSetFilling(set, key); 528 } else { 529 set.add(key); 530 } 531 } 532 } 533 534 /* 535 * if keyObject is created it 2 steps we should postpone putting it into the set because it may 536 * have different hash after initialization compared to clean just created one. And set of course 537 * does not observe value hashCode changes. 538 */ postponeSetFilling(Set<Object> set, Object key)539 protected void postponeSetFilling(Set<Object> set, Object key) { 540 sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key)); 541 } 542 setPropertyUtils(PropertyUtils propertyUtils)543 public void setPropertyUtils(PropertyUtils propertyUtils) { 544 this.propertyUtils = propertyUtils; 545 explicitPropertyUtils = true; 546 Collection<TypeDescription> tds = typeDefinitions.values(); 547 for (TypeDescription typeDescription : tds) { 548 typeDescription.setPropertyUtils(propertyUtils); 549 } 550 } 551 getPropertyUtils()552 public final PropertyUtils getPropertyUtils() { 553 if (propertyUtils == null) { 554 propertyUtils = new PropertyUtils(); 555 } 556 return propertyUtils; 557 } 558 559 /** 560 * Make YAML aware how to parse a custom Class. If there is no root Class assigned in constructor 561 * then the 'root' property of this definition is respected. 562 * 563 * @param definition to be added to the Constructor 564 * @return the previous value associated with <code>definition</code>, or <code>null</code> if 565 * there was no mapping for <code>definition</code>. 566 */ addTypeDescription(TypeDescription definition)567 public TypeDescription addTypeDescription(TypeDescription definition) { 568 if (definition == null) { 569 throw new NullPointerException("TypeDescription is required."); 570 } 571 Tag tag = definition.getTag(); 572 typeTags.put(tag, definition.getType()); 573 definition.setPropertyUtils(getPropertyUtils()); 574 return typeDefinitions.put(definition.getType(), definition); 575 } 576 577 private static class RecursiveTuple<T, K> { 578 579 private final T _1; 580 private final K _2; 581 RecursiveTuple(T _1, K _2)582 public RecursiveTuple(T _1, K _2) { 583 this._1 = _1; 584 this._2 = _2; 585 } 586 _2()587 public K _2() { 588 return _2; 589 } 590 _1()591 public T _1() { 592 return _1; 593 } 594 } 595 isExplicitPropertyUtils()596 public final boolean isExplicitPropertyUtils() { 597 return explicitPropertyUtils; 598 } 599 isAllowDuplicateKeys()600 public boolean isAllowDuplicateKeys() { 601 return allowDuplicateKeys; 602 } 603 setAllowDuplicateKeys(boolean allowDuplicateKeys)604 public void setAllowDuplicateKeys(boolean allowDuplicateKeys) { 605 this.allowDuplicateKeys = allowDuplicateKeys; 606 } 607 isWrappedToRootException()608 public boolean isWrappedToRootException() { 609 return wrappedToRootException; 610 } 611 setWrappedToRootException(boolean wrappedToRootException)612 public void setWrappedToRootException(boolean wrappedToRootException) { 613 this.wrappedToRootException = wrappedToRootException; 614 } 615 isEnumCaseSensitive()616 public boolean isEnumCaseSensitive() { 617 return enumCaseSensitive; 618 } 619 setEnumCaseSensitive(boolean enumCaseSensitive)620 public void setEnumCaseSensitive(boolean enumCaseSensitive) { 621 this.enumCaseSensitive = enumCaseSensitive; 622 } 623 } 624