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.lang.reflect.Array; 19 import java.util.ArrayList; 20 import java.util.Collection; 21 import java.util.EnumMap; 22 import java.util.HashMap; 23 import java.util.HashSet; 24 import java.util.LinkedHashMap; 25 import java.util.LinkedHashSet; 26 import java.util.List; 27 import java.util.Map; 28 import java.util.Set; 29 30 import org.yaml.snakeyaml.composer.Composer; 31 import org.yaml.snakeyaml.composer.ComposerException; 32 import org.yaml.snakeyaml.error.YAMLException; 33 import org.yaml.snakeyaml.introspector.PropertyUtils; 34 import org.yaml.snakeyaml.nodes.MappingNode; 35 import org.yaml.snakeyaml.nodes.Node; 36 import org.yaml.snakeyaml.nodes.NodeId; 37 import org.yaml.snakeyaml.nodes.NodeTuple; 38 import org.yaml.snakeyaml.nodes.ScalarNode; 39 import org.yaml.snakeyaml.nodes.SequenceNode; 40 import org.yaml.snakeyaml.nodes.Tag; 41 42 public abstract class BaseConstructor { 43 /** 44 * It maps the node kind to the the Construct implementation. When the 45 * runtime class is known then the implicit tag is ignored. 46 */ 47 protected final Map<NodeId, Construct> yamlClassConstructors = new EnumMap<NodeId, Construct>( 48 NodeId.class); 49 /** 50 * It maps the (explicit or implicit) tag to the Construct implementation. 51 * It is used: <br/> 52 * 1) explicit tag - if present. <br/> 53 * 2) implicit tag - when the runtime class of the instance is unknown (the 54 * node has the Object.class) 55 */ 56 protected final Map<Tag, Construct> yamlConstructors = new HashMap<Tag, Construct>(); 57 /** 58 * It maps the (explicit or implicit) tag to the Construct implementation. 59 * It is used when no exact match found. 60 */ 61 protected final Map<String, Construct> yamlMultiConstructors = new HashMap<String, Construct>(); 62 63 protected Composer composer; 64 private final Map<Node, Object> constructedObjects; 65 private final Set<Node> recursiveObjects; 66 private final ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>> maps2fill; 67 private final ArrayList<RecursiveTuple<Set<Object>, Object>> sets2fill; 68 69 protected Tag rootTag; 70 private PropertyUtils propertyUtils; 71 private boolean explicitPropertyUtils; 72 BaseConstructor()73 public BaseConstructor() { 74 constructedObjects = new HashMap<Node, Object>(); 75 recursiveObjects = new HashSet<Node>(); 76 maps2fill = new ArrayList<RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>>(); 77 sets2fill = new ArrayList<RecursiveTuple<Set<Object>, Object>>(); 78 rootTag = null; 79 explicitPropertyUtils = false; 80 } 81 setComposer(Composer composer)82 public void setComposer(Composer composer) { 83 this.composer = composer; 84 } 85 86 /** 87 * Check if more documents available 88 * 89 * @return true when there are more YAML documents in the stream 90 */ checkData()91 public boolean checkData() { 92 // If there are more documents available? 93 return composer.checkNode(); 94 } 95 96 /** 97 * Construct and return the next document 98 * 99 * @return constructed instance 100 */ getData()101 public Object getData() { 102 // Construct and return the next document. 103 composer.checkNode(); 104 Node node = composer.getNode(); 105 if (rootTag != null) { 106 node.setTag(rootTag); 107 } 108 return constructDocument(node); 109 } 110 111 /** 112 * Ensure that the stream contains a single document and construct it 113 * 114 * @return constructed instance 115 * @throws ComposerException 116 * in case there are more documents in the stream 117 */ getSingleData(Class<?> type)118 public Object getSingleData(Class<?> type) { 119 // Ensure that the stream contains a single document and construct it 120 Node node = composer.getSingleNode(); 121 if (node != null) { 122 if (Object.class != type) { 123 node.setTag(new Tag(type)); 124 } else if (rootTag != null) { 125 node.setTag(rootTag); 126 } 127 return constructDocument(node); 128 } 129 return null; 130 } 131 132 /** 133 * Construct complete YAML document. Call the second step in case of 134 * recursive structures. At the end cleans all the state. 135 * 136 * @param node 137 * root Node 138 * @return Java instance 139 */ constructDocument(Node node)140 protected final Object constructDocument(Node node) { 141 Object data = constructObject(node); 142 fillRecursive(); 143 constructedObjects.clear(); 144 recursiveObjects.clear(); 145 return data; 146 } 147 fillRecursive()148 private void fillRecursive() { 149 if (!maps2fill.isEmpty()) { 150 for (RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>> entry : maps2fill) { 151 RecursiveTuple<Object, Object> key_value = entry._2(); 152 entry._1().put(key_value._1(), key_value._2()); 153 } 154 maps2fill.clear(); 155 } 156 if (!sets2fill.isEmpty()) { 157 for (RecursiveTuple<Set<Object>, Object> value : sets2fill) { 158 value._1().add(value._2()); 159 } 160 sets2fill.clear(); 161 } 162 } 163 164 /** 165 * Construct object from the specified Node. Return existing instance if the 166 * node is already constructed. 167 * 168 * @param node 169 * Node to be constructed 170 * @return Java instance 171 */ constructObject(Node node)172 protected Object constructObject(Node node) { 173 if (constructedObjects.containsKey(node)) { 174 return constructedObjects.get(node); 175 } 176 if (recursiveObjects.contains(node)) { 177 throw new ConstructorException(null, null, "found unconstructable recursive node", 178 node.getStartMark()); 179 } 180 recursiveObjects.add(node); 181 Construct constructor = getConstructor(node); 182 Object data = constructor.construct(node); 183 constructedObjects.put(node, data); 184 recursiveObjects.remove(node); 185 if (node.isTwoStepsConstruction()) { 186 constructor.construct2ndStep(node, data); 187 } 188 return data; 189 } 190 191 /** 192 * Get the constructor to construct the Node. For implicit tags if the 193 * runtime class is known a dedicated Construct implementation is used. 194 * Otherwise the constructor is chosen by the tag. 195 * 196 * @param node 197 * Node to be constructed 198 * @return Construct implementation for the specified node 199 */ getConstructor(Node node)200 protected Construct getConstructor(Node node) { 201 if (node.useClassConstructor()) { 202 return yamlClassConstructors.get(node.getNodeId()); 203 } else { 204 Construct constructor = yamlConstructors.get(node.getTag()); 205 if (constructor == null) { 206 for (String prefix : yamlMultiConstructors.keySet()) { 207 if (node.getTag().startsWith(prefix)) { 208 return yamlMultiConstructors.get(prefix); 209 } 210 } 211 return yamlConstructors.get(null); 212 } 213 return constructor; 214 } 215 } 216 constructScalar(ScalarNode node)217 protected Object constructScalar(ScalarNode node) { 218 return node.getValue(); 219 } 220 createDefaultList(int initSize)221 protected List<Object> createDefaultList(int initSize) { 222 return new ArrayList<Object>(initSize); 223 } 224 createDefaultSet(int initSize)225 protected Set<Object> createDefaultSet(int initSize) { 226 return new LinkedHashSet<Object>(initSize); 227 } 228 createArray(Class<?> type, int size)229 protected Object createArray(Class<?> type, int size) { 230 return Array.newInstance(type.getComponentType(), size); 231 } 232 233 @SuppressWarnings("unchecked") constructSequence(SequenceNode node)234 protected List<? extends Object> constructSequence(SequenceNode node) { 235 List<Object> result; 236 if (List.class.isAssignableFrom(node.getType()) && !node.getType().isInterface()) { 237 // the root class may be defined (Vector for instance) 238 try { 239 result = (List<Object>) node.getType().newInstance(); 240 } catch (Exception e) { 241 throw new YAMLException(e); 242 } 243 } else { 244 result = createDefaultList(node.getValue().size()); 245 } 246 constructSequenceStep2(node, result); 247 return result; 248 249 } 250 251 @SuppressWarnings("unchecked") constructSet(SequenceNode node)252 protected Set<? extends Object> constructSet(SequenceNode node) { 253 Set<Object> result; 254 if (!node.getType().isInterface()) { 255 // the root class may be defined 256 try { 257 result = (Set<Object>) node.getType().newInstance(); 258 } catch (Exception e) { 259 throw new YAMLException(e); 260 } 261 } else { 262 result = createDefaultSet(node.getValue().size()); 263 } 264 constructSequenceStep2(node, result); 265 return result; 266 267 } 268 constructArray(SequenceNode node)269 protected Object constructArray(SequenceNode node) { 270 return constructArrayStep2(node, createArray(node.getType(), node.getValue().size())); 271 } 272 constructSequenceStep2(SequenceNode node, Collection<Object> collection)273 protected void constructSequenceStep2(SequenceNode node, Collection<Object> collection) { 274 for (Node child : node.getValue()) { 275 collection.add(constructObject(child)); 276 } 277 } 278 constructArrayStep2(SequenceNode node, Object array)279 protected Object constructArrayStep2(SequenceNode node, Object array) { 280 final Class<?> componentType = node.getType().getComponentType(); 281 282 int index = 0; 283 for (Node child : node.getValue()) { 284 // Handle multi-dimensional arrays... 285 if (child.getType() == Object.class) { 286 child.setType(componentType); 287 } 288 289 final Object value = constructObject(child); 290 291 if (componentType.isPrimitive()) { 292 // Null values are disallowed for primitives 293 if (value == null) { 294 throw new NullPointerException("Unable to construct element value for " + child); 295 } 296 297 // Primitive arrays require quite a lot of work. 298 if (byte.class.equals(componentType)) { 299 Array.setByte(array, index, ((Number) value).byteValue()); 300 301 } else if (short.class.equals(componentType)) { 302 Array.setShort(array, index, ((Number) value).shortValue()); 303 304 } else if (int.class.equals(componentType)) { 305 Array.setInt(array, index, ((Number) value).intValue()); 306 307 } else if (long.class.equals(componentType)) { 308 Array.setLong(array, index, ((Number) value).longValue()); 309 310 } else if (float.class.equals(componentType)) { 311 Array.setFloat(array, index, ((Number) value).floatValue()); 312 313 } else if (double.class.equals(componentType)) { 314 Array.setDouble(array, index, ((Number) value).doubleValue()); 315 316 } else if (char.class.equals(componentType)) { 317 Array.setChar(array, index, ((Character) value).charValue()); 318 319 } else if (boolean.class.equals(componentType)) { 320 Array.setBoolean(array, index, ((Boolean) value).booleanValue()); 321 322 } else { 323 throw new YAMLException("unexpected primitive type"); 324 } 325 326 } else { 327 // Non-primitive arrays can simply be assigned: 328 Array.set(array, index, value); 329 } 330 331 ++index; 332 } 333 return array; 334 } 335 createDefaultMap()336 protected Map<Object, Object> createDefaultMap() { 337 // respect order from YAML document 338 return new LinkedHashMap<Object, Object>(); 339 } 340 createDefaultSet()341 protected Set<Object> createDefaultSet() { 342 // respect order from YAML document 343 return new LinkedHashSet<Object>(); 344 } 345 constructSet(MappingNode node)346 protected Set<Object> constructSet(MappingNode node) { 347 Set<Object> set = createDefaultSet(); 348 constructSet2ndStep(node, set); 349 return set; 350 } 351 constructMapping(MappingNode node)352 protected Map<Object, Object> constructMapping(MappingNode node) { 353 Map<Object, Object> mapping = createDefaultMap(); 354 constructMapping2ndStep(node, mapping); 355 return mapping; 356 } 357 constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping)358 protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) { 359 List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue(); 360 for (NodeTuple tuple : nodeValue) { 361 Node keyNode = tuple.getKeyNode(); 362 Node valueNode = tuple.getValueNode(); 363 Object key = constructObject(keyNode); 364 if (key != null) { 365 try { 366 key.hashCode();// check circular dependencies 367 } catch (Exception e) { 368 throw new ConstructorException("while constructing a mapping", 369 node.getStartMark(), "found unacceptable key " + key, tuple 370 .getKeyNode().getStartMark(), e); 371 } 372 } 373 Object value = constructObject(valueNode); 374 if (keyNode.isTwoStepsConstruction()) { 375 /* 376 * if keyObject is created it 2 steps we should postpone putting 377 * it in map because it may have different hash after 378 * initialization compared to clean just created one. And map of 379 * course does not observe key hashCode changes. 380 */ 381 maps2fill.add(0, 382 new RecursiveTuple<Map<Object, Object>, RecursiveTuple<Object, Object>>( 383 mapping, new RecursiveTuple<Object, Object>(key, value))); 384 } else { 385 mapping.put(key, value); 386 } 387 } 388 } 389 constructSet2ndStep(MappingNode node, Set<Object> set)390 protected void constructSet2ndStep(MappingNode node, Set<Object> set) { 391 List<NodeTuple> nodeValue = (List<NodeTuple>) node.getValue(); 392 for (NodeTuple tuple : nodeValue) { 393 Node keyNode = tuple.getKeyNode(); 394 Object key = constructObject(keyNode); 395 if (key != null) { 396 try { 397 key.hashCode();// check circular dependencies 398 } catch (Exception e) { 399 throw new ConstructorException("while constructing a Set", node.getStartMark(), 400 "found unacceptable key " + key, tuple.getKeyNode().getStartMark(), e); 401 } 402 } 403 if (keyNode.isTwoStepsConstruction()) { 404 /* 405 * if keyObject is created it 2 steps we should postpone putting 406 * it into the set because it may have different hash after 407 * initialization compared to clean just created one. And set of 408 * course does not observe value hashCode changes. 409 */ 410 sets2fill.add(0, new RecursiveTuple<Set<Object>, Object>(set, key)); 411 } else { 412 set.add(key); 413 } 414 } 415 } 416 setPropertyUtils(PropertyUtils propertyUtils)417 public void setPropertyUtils(PropertyUtils propertyUtils) { 418 this.propertyUtils = propertyUtils; 419 explicitPropertyUtils = true; 420 } 421 getPropertyUtils()422 public final PropertyUtils getPropertyUtils() { 423 if (propertyUtils == null) { 424 propertyUtils = new PropertyUtils(); 425 } 426 return propertyUtils; 427 } 428 429 private static class RecursiveTuple<T, K> { 430 private final T _1; 431 private final K _2; 432 RecursiveTuple(T _1, K _2)433 public RecursiveTuple(T _1, K _2) { 434 this._1 = _1; 435 this._2 = _2; 436 } 437 _2()438 public K _2() { 439 return _2; 440 } 441 _1()442 public T _1() { 443 return _1; 444 } 445 } 446 isExplicitPropertyUtils()447 public final boolean isExplicitPropertyUtils() { 448 return explicitPropertyUtils; 449 } 450 } 451