1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
35 import org.yaml.snakeyaml.TypeDescription;
36 import org.yaml.snakeyaml.error.YAMLException;
37 import org.yaml.snakeyaml.introspector.Property;
38 import org.yaml.snakeyaml.nodes.MappingNode;
39 import org.yaml.snakeyaml.nodes.Node;
40 import org.yaml.snakeyaml.nodes.NodeId;
41 import org.yaml.snakeyaml.nodes.NodeTuple;
42 import org.yaml.snakeyaml.nodes.ScalarNode;
43 import org.yaml.snakeyaml.nodes.SequenceNode;
44 import org.yaml.snakeyaml.nodes.Tag;
45
46
47
48
49 public class Constructor extends SafeConstructor {
50 private final Map<Tag, Class<? extends Object>> typeTags;
51 private final Map<Class<? extends Object>, TypeDescription> typeDefinitions;
52
53 public Constructor() {
54 this(Object.class);
55 }
56
57
58
59
60
61
62
63 public Constructor(Class<? extends Object> theRoot) {
64 this(new TypeDescription(checkRoot(theRoot)));
65 }
66
67
68
69
70 private static Class<? extends Object> checkRoot(Class<? extends Object> theRoot) {
71 if (theRoot == null) {
72 throw new NullPointerException("Root class must be provided.");
73 } else
74 return theRoot;
75 }
76
77 public Constructor(TypeDescription theRoot) {
78 if (theRoot == null) {
79 throw new NullPointerException("Root type must be provided.");
80 }
81 this.yamlConstructors.put(null, new ConstructYamlObject());
82 if (!Object.class.equals(theRoot.getType())) {
83 rootTag = new Tag(theRoot.getType());
84 }
85 typeTags = new HashMap<Tag, Class<? extends Object>>();
86 typeDefinitions = new HashMap<Class<? extends Object>, TypeDescription>();
87 yamlClassConstructors.put(NodeId.scalar, new ConstructScalar());
88 yamlClassConstructors.put(NodeId.mapping, new ConstructMapping());
89 yamlClassConstructors.put(NodeId.sequence, new ConstructSequence());
90 addTypeDescription(theRoot);
91 }
92
93
94
95
96
97
98
99
100
101
102 public Constructor(String theRoot) throws ClassNotFoundException {
103 this(Class.forName(check(theRoot)));
104 }
105
106 private static final String check(String s) {
107 if (s == null) {
108 throw new NullPointerException("Root type must be provided.");
109 }
110 if (s.trim().length() == 0) {
111 throw new YAMLException("Root type must be provided.");
112 }
113 return s;
114 }
115
116
117
118
119
120
121
122
123
124
125
126 public TypeDescription addTypeDescription(TypeDescription definition) {
127 if (definition == null) {
128 throw new NullPointerException("TypeDescription is required.");
129 }
130 Tag tag = definition.getTag();
131 typeTags.put(tag, definition.getType());
132 return typeDefinitions.put(definition.getType(), definition);
133 }
134
135
136
137
138
139 protected class ConstructMapping implements Construct {
140
141
142
143
144
145
146
147
148
149
150 public Object construct(Node node) {
151 MappingNode mnode = (MappingNode) node;
152 if (Properties.class.isAssignableFrom(node.getType())) {
153 Properties properties = new Properties();
154 if (!node.isTwoStepsConstruction()) {
155 constructMapping2ndStep(mnode, properties);
156 } else {
157 throw new YAMLException("Properties must not be recursive.");
158 }
159 return properties;
160 } else if (SortedMap.class.isAssignableFrom(node.getType())) {
161 SortedMap<Object, Object> map = new TreeMap<Object, Object>();
162 if (!node.isTwoStepsConstruction()) {
163 constructMapping2ndStep(mnode, map);
164 }
165 return map;
166 } else if (Map.class.isAssignableFrom(node.getType())) {
167 if (node.isTwoStepsConstruction()) {
168 return createDefaultMap();
169 } else {
170 return constructMapping(mnode);
171 }
172 } else if (SortedSet.class.isAssignableFrom(node.getType())) {
173 SortedSet<Object> set = new TreeSet<Object>();
174
175
176 constructSet2ndStep(mnode, set);
177
178 return set;
179 } else if (Collection.class.isAssignableFrom(node.getType())) {
180 if (node.isTwoStepsConstruction()) {
181 return createDefaultSet();
182 } else {
183 return constructSet(mnode);
184 }
185 } else {
186 if (node.isTwoStepsConstruction()) {
187 return createEmptyJavaBean(mnode);
188 } else {
189 return constructJavaBean2ndStep(mnode, createEmptyJavaBean(mnode));
190 }
191 }
192 }
193
194 @SuppressWarnings("unchecked")
195 public void construct2ndStep(Node node, Object object) {
196 if (Map.class.isAssignableFrom(node.getType())) {
197 constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
198 } else if (Set.class.isAssignableFrom(node.getType())) {
199 constructSet2ndStep((MappingNode) node, (Set<Object>) object);
200 } else {
201 constructJavaBean2ndStep((MappingNode) node, object);
202 }
203 }
204
205 protected Object createEmptyJavaBean(MappingNode node) {
206 try {
207
208
209
210
211
212
213
214
215 java.lang.reflect.Constructor<?> c = node.getType().getDeclaredConstructor();
216 c.setAccessible(true);
217 return c.newInstance();
218 } catch (Exception e) {
219 throw new YAMLException(e);
220 }
221 }
222
223 protected Object constructJavaBean2ndStep(MappingNode node, Object object) {
224 flattenMapping(node);
225 Class<? extends Object> beanType = node.getType();
226 List<NodeTuple> nodeValue = node.getValue();
227 for (NodeTuple tuple : nodeValue) {
228 ScalarNode keyNode;
229 if (tuple.getKeyNode() instanceof ScalarNode) {
230
231 keyNode = (ScalarNode) tuple.getKeyNode();
232 } else {
233 throw new YAMLException("Keys must be scalars but found: " + tuple.getKeyNode());
234 }
235 Node valueNode = tuple.getValueNode();
236
237 keyNode.setType(String.class);
238 String key = (String) constructObject(keyNode);
239 try {
240 Property property = getProperty(beanType, key);
241 valueNode.setType(property.getType());
242 TypeDescription memberDescription = typeDefinitions.get(beanType);
243 boolean typeDetected = false;
244 if (memberDescription != null) {
245 switch (valueNode.getNodeId()) {
246 case sequence:
247 SequenceNode snode = (SequenceNode) valueNode;
248 Class<? extends Object> memberType = memberDescription
249 .getListPropertyType(key);
250 if (memberType != null) {
251 snode.setListType(memberType);
252 typeDetected = true;
253 } else if (property.getType().isArray()) {
254 snode.setListType(property.getType().getComponentType());
255 typeDetected = true;
256 }
257 break;
258 case mapping:
259 MappingNode mnode = (MappingNode) valueNode;
260 Class<? extends Object> keyType = memberDescription.getMapKeyType(key);
261 if (keyType != null) {
262 mnode.setTypes(keyType, memberDescription.getMapValueType(key));
263 typeDetected = true;
264 }
265 break;
266 default:
267 }
268 }
269 if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
270
271 Class<?>[] arguments = property.getActualTypeArguments();
272 if (arguments != null && arguments.length > 0) {
273
274
275 if (valueNode.getNodeId() == NodeId.sequence) {
276 Class<?> t = arguments[0];
277 SequenceNode snode = (SequenceNode) valueNode;
278 snode.setListType(t);
279 } else if (valueNode.getTag().equals(Tag.SET)) {
280 Class<?> t = arguments[0];
281 MappingNode mnode = (MappingNode) valueNode;
282 mnode.setOnlyKeyType(t);
283 mnode.setUseClassConstructor(true);
284 } else if (property.getType().isAssignableFrom(Map.class)) {
285 Class<?> ketType = arguments[0];
286 Class<?> valueType = arguments[1];
287 MappingNode mnode = (MappingNode) valueNode;
288 mnode.setTypes(ketType, valueType);
289 mnode.setUseClassConstructor(true);
290 } else {
291
292
293 }
294 }
295 }
296 Object value = constructObject(valueNode);
297 property.set(object, value);
298 } catch (Exception e) {
299 throw new YAMLException("Cannot create property=" + key + " for JavaBean="
300 + object + "; " + e.getMessage(), e);
301 }
302 }
303 return object;
304 }
305
306 protected Property getProperty(Class<? extends Object> type, String name)
307 throws IntrospectionException {
308 return getPropertyUtils().getProperty(type, name);
309 }
310 }
311
312
313
314
315
316
317
318 protected class ConstructYamlObject implements Construct {
319
320 private Construct getConstructor(Node node) {
321 Class<?> cl = getClassForNode(node);
322 node.setType(cl);
323
324 Construct constructor = yamlClassConstructors.get(node.getNodeId());
325 return constructor;
326 }
327
328 public Object construct(Node node) {
329 Object result = null;
330 try {
331 result = getConstructor(node).construct(node);
332 } catch (Exception e) {
333 throw new ConstructorException(null, null, "Can't construct a java object for "
334 + node.getTag() + "; exception=" + e.getMessage(), node.getStartMark(), e);
335 }
336 return result;
337 }
338
339 public void construct2ndStep(Node node, Object object) {
340 try {
341 getConstructor(node).construct2ndStep(node, object);
342 } catch (Exception e) {
343 throw new ConstructorException(null, null,
344 "Can't construct a second step for a java object for " + node.getTag()
345 + "; exception=" + e.getMessage(), node.getStartMark(), e);
346 }
347 }
348 }
349
350
351
352
353
354 protected class ConstructScalar extends AbstractConstruct {
355 public Object construct(Node nnode) {
356 ScalarNode node = (ScalarNode) nnode;
357 Class<?> type = node.getType();
358 Object result;
359 if (type.isPrimitive() || type == String.class || Number.class.isAssignableFrom(type)
360 || type == Boolean.class || Date.class.isAssignableFrom(type)
361 || type == Character.class || type == BigInteger.class
362 || type == BigDecimal.class || Enum.class.isAssignableFrom(type)
363 || Tag.BINARY.equals(node.getTag()) || Calendar.class.isAssignableFrom(type)) {
364
365 result = constructStandardJavaInstance(type, node);
366 } else {
367
368 java.lang.reflect.Constructor<?>[] javaConstructors = type.getConstructors();
369 int oneArgCount = 0;
370 java.lang.reflect.Constructor<?> javaConstructor = null;
371 for (java.lang.reflect.Constructor<?> c : javaConstructors) {
372 if (c.getParameterTypes().length == 1) {
373 oneArgCount++;
374 javaConstructor = c;
375 }
376 }
377 Object argument;
378 if (javaConstructor == null) {
379 throw new YAMLException("No single argument constructor found for " + type);
380 } else if (oneArgCount == 1) {
381 argument = constructStandardJavaInstance(
382 javaConstructor.getParameterTypes()[0], node);
383 } else {
384
385
386
387
388
389
390 argument = constructScalar(node);
391 try {
392 javaConstructor = type.getConstructor(String.class);
393 } catch (Exception e) {
394 throw new YAMLException("Can't construct a java object for scalar "
395 + node.getTag() + "; No String constructor found. Exception="
396 + e.getMessage(), e);
397 }
398 }
399 try {
400 result = javaConstructor.newInstance(argument);
401 } catch (Exception e) {
402 throw new ConstructorException(null, null,
403 "Can't construct a java object for scalar " + node.getTag()
404 + "; exception=" + e.getMessage(), node.getStartMark(), e);
405 }
406 }
407 return result;
408 }
409
410 @SuppressWarnings("unchecked")
411 private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type,
412 ScalarNode node) {
413 Object result;
414 if (type == String.class) {
415 Construct stringConstructor = yamlConstructors.get(Tag.STR);
416 result = stringConstructor.construct(node);
417 } else if (type == Boolean.class || type == Boolean.TYPE) {
418 Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
419 result = boolConstructor.construct(node);
420 } else if (type == Character.class || type == Character.TYPE) {
421 Construct charConstructor = yamlConstructors.get(Tag.STR);
422 String ch = (String) charConstructor.construct(node);
423 if (ch.length() == 0) {
424 result = null;
425 } else if (ch.length() != 1) {
426 throw new YAMLException("Invalid node Character: '" + ch + "'; length: "
427 + ch.length());
428 } else {
429 result = Character.valueOf(ch.charAt(0));
430 }
431 } else if (Date.class.isAssignableFrom(type)) {
432 Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
433 Date date = (Date) dateConstructor.construct(node);
434 if (type == Date.class) {
435 result = date;
436 } else {
437 try {
438 java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
439 result = constr.newInstance(date.getTime());
440 } catch (Exception e) {
441 throw new YAMLException("Cannot construct: '" + type + "'");
442 }
443 }
444 } else if (type == Float.class || type == Double.class || type == Float.TYPE
445 || type == Double.TYPE || type == BigDecimal.class) {
446 if (type == BigDecimal.class) {
447 result = new BigDecimal(node.getValue());
448 } else {
449 Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
450 result = doubleConstructor.construct(node);
451 if (type == Float.class || type == Float.TYPE) {
452 result = new Float((Double) result);
453 }
454 }
455 } else if (type == Byte.class || type == Short.class || type == Integer.class
456 || type == Long.class || type == BigInteger.class || type == Byte.TYPE
457 || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
458 Construct intConstructor = yamlConstructors.get(Tag.INT);
459 result = intConstructor.construct(node);
460 if (type == Byte.class || type == Byte.TYPE) {
461 result = new Byte(result.toString());
462 } else if (type == Short.class || type == Short.TYPE) {
463 result = new Short(result.toString());
464 } else if (type == Integer.class || type == Integer.TYPE) {
465 result = Integer.parseInt(result.toString());
466 } else if (type == Long.class || type == Long.TYPE) {
467 result = new Long(result.toString());
468 } else {
469
470 result = new BigInteger(result.toString());
471 }
472 } else if (Enum.class.isAssignableFrom(type)) {
473 String enumValueName = node.getValue();
474 try {
475 result = Enum.valueOf(type, enumValueName);
476 } catch (Exception ex) {
477 throw new YAMLException("Unable to find enum value '" + enumValueName
478 + "' for enum class: " + type.getName());
479 }
480 } else if (Calendar.class.isAssignableFrom(type)) {
481 ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
482 contr.construct(node);
483 result = contr.getCalendar();
484 } else {
485 throw new YAMLException("Unsupported class: " + type);
486 }
487 return result;
488 }
489 }
490
491
492
493
494
495 protected class ConstructSequence implements Construct {
496 @SuppressWarnings("unchecked")
497 public Object construct(Node node) {
498 SequenceNode snode = (SequenceNode) node;
499 if (Set.class.isAssignableFrom(node.getType())) {
500 if (node.isTwoStepsConstruction()) {
501 throw new YAMLException("Set cannot be recursive.");
502 } else {
503 return constructSet(snode);
504 }
505 } else if (Collection.class.isAssignableFrom(node.getType())) {
506 if (node.isTwoStepsConstruction()) {
507 return createDefaultList(snode.getValue().size());
508 } else {
509 return constructSequence(snode);
510 }
511 } else if (node.getType().isArray()) {
512 if (node.isTwoStepsConstruction()) {
513 return createArray(node.getType(), snode.getValue().size());
514 } else {
515 return constructArray(snode);
516 }
517 } else {
518
519 List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>(
520 snode.getValue().size());
521 for (java.lang.reflect.Constructor<?> constructor : node.getType()
522 .getConstructors()) {
523 if (snode.getValue().size() == constructor.getParameterTypes().length) {
524 possibleConstructors.add(constructor);
525 }
526 }
527 if (!possibleConstructors.isEmpty()) {
528 if (possibleConstructors.size() == 1) {
529 Object[] argumentList = new Object[snode.getValue().size()];
530 java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
531 int index = 0;
532 for (Node argumentNode : snode.getValue()) {
533 Class<?> type = c.getParameterTypes()[index];
534
535 argumentNode.setType(type);
536 argumentList[index++] = constructObject(argumentNode);
537 }
538
539 try {
540 return c.newInstance(argumentList);
541 } catch (Exception e) {
542 throw new YAMLException(e);
543 }
544 }
545
546
547 List<Object> argumentList = (List<Object>) constructSequence(snode);
548 Class<?>[] parameterTypes = new Class[argumentList.size()];
549 int index = 0;
550 for (Object parameter : argumentList) {
551 parameterTypes[index] = parameter.getClass();
552 index++;
553 }
554
555 for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
556 Class<?>[] argTypes = c.getParameterTypes();
557 boolean foundConstructor = true;
558 for (int i = 0; i < argTypes.length; i++) {
559 if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
560 foundConstructor = false;
561 break;
562 }
563 }
564 if (foundConstructor) {
565 try {
566 return c.newInstance(argumentList.toArray());
567 } catch (Exception e) {
568 throw new YAMLException(e);
569 }
570 }
571 }
572 }
573 throw new YAMLException("No suitable constructor with "
574 + String.valueOf(snode.getValue().size()) + " arguments found for "
575 + node.getType());
576
577 }
578 }
579
580 private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
581 if (!clazz.isPrimitive()) {
582 return clazz;
583 }
584 if (clazz == Integer.TYPE) {
585 return Integer.class;
586 }
587 if (clazz == Float.TYPE) {
588 return Float.class;
589 }
590 if (clazz == Double.TYPE) {
591 return Double.class;
592 }
593 if (clazz == Boolean.TYPE) {
594 return Boolean.class;
595 }
596 if (clazz == Long.TYPE) {
597 return Long.class;
598 }
599 if (clazz == Character.TYPE) {
600 return Character.class;
601 }
602 if (clazz == Short.TYPE) {
603 return Short.class;
604 }
605 if (clazz == Byte.TYPE) {
606 return Byte.class;
607 }
608 throw new YAMLException("Unexpected primitive " + clazz);
609 }
610
611 @SuppressWarnings("unchecked")
612 public void construct2ndStep(Node node, Object object) {
613 SequenceNode snode = (SequenceNode) node;
614 if (List.class.isAssignableFrom(node.getType())) {
615 List<Object> list = (List<Object>) object;
616 constructSequenceStep2(snode, list);
617 } else if (node.getType().isArray()) {
618 constructArrayStep2(snode, object);
619 } else {
620 throw new YAMLException("Immutable objects cannot be recursive.");
621 }
622 }
623 }
624
625 protected Class<?> getClassForNode(Node node) {
626 Class<? extends Object> classForTag = typeTags.get(node.getTag());
627 if (classForTag == null) {
628 String name = node.getTag().getClassName();
629 Class<?> cl;
630 try {
631 cl = getClassForName(name);
632 } catch (ClassNotFoundException e) {
633 throw new YAMLException("Class not found: " + name);
634 }
635 typeTags.put(node.getTag(), cl);
636 return cl;
637 } else {
638 return classForTag;
639 }
640 }
641
642 protected Class<?> getClassForName(String name) throws ClassNotFoundException {
643 return Class.forName(name);
644 }
645 }