1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.yaml.snakeyaml.constructor;
18
19 import java.beans.IntrospectionException;
20 import java.math.BigDecimal;
21 import java.math.BigInteger;
22 import java.util.ArrayList;
23 import java.util.Calendar;
24 import java.util.Collection;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Properties;
30 import java.util.Set;
31 import java.util.SortedMap;
32 import java.util.SortedSet;
33 import java.util.TreeMap;
34 import java.util.TreeSet;
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
49
50 public class Constructor extends SafeConstructor {
51 private final Map<Tag, Class<? extends Object>> typeTags;
52 private final Map<Class<? extends Object>, TypeDescription> typeDefinitions;
53
54 public Constructor() {
55 this(Object.class);
56 }
57
58
59
60
61
62
63
64 public Constructor(Class<? extends Object> theRoot) {
65 this(new TypeDescription(checkRoot(theRoot)));
66 }
67
68
69
70
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
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
96
97
98
99
100
101
102
103 public Constructor(String theRoot) throws ClassNotFoundException {
104 this(Class.forName(check(theRoot)));
105 }
106
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
119
120
121
122
123
124
125
126
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
138
139
140 protected class ConstructMapping implements Construct {
141
142
143
144
145
146
147
148
149
150
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
176
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")
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
206 protected Object createEmptyJavaBean(MappingNode node) {
207 try {
208
209
210
211
212
213
214
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
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
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
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 }
268 }
269 if (!typeDetected && valueNode.getNodeId() != NodeId.scalar) {
270
271 Class<?>[] arguments = property.getActualTypeArguments();
272 if (arguments != null) {
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 ConstructorException(null, null,
395 "Can't construct a java object for scalar " + node.getTag()
396 + "; No String constructor found. Exception="
397 + e.getMessage(), node.getStartMark(), e);
398 }
399 }
400 try {
401 result = javaConstructor.newInstance(argument);
402 } catch (Exception e) {
403 throw new ConstructorException(null, null,
404 "Can't construct a java object for scalar " + node.getTag()
405 + "; exception=" + e.getMessage(), node.getStartMark(), e);
406 }
407 }
408 return result;
409 }
410
411 @SuppressWarnings("unchecked")
412 private Object constructStandardJavaInstance(@SuppressWarnings("rawtypes") Class type,
413 ScalarNode node) {
414 Object result;
415 if (type == String.class) {
416 Construct stringConstructor = yamlConstructors.get(Tag.STR);
417 result = stringConstructor.construct(node);
418 } else if (type == Boolean.class || type == Boolean.TYPE) {
419 Construct boolConstructor = yamlConstructors.get(Tag.BOOL);
420 result = boolConstructor.construct(node);
421 } else if (type == Character.class || type == Character.TYPE) {
422 Construct charConstructor = yamlConstructors.get(Tag.STR);
423 String ch = (String) charConstructor.construct(node);
424 if (ch.length() == 0) {
425 result = null;
426 } else if (ch.length() != 1) {
427 throw new YAMLException("Invalid node Character: '" + ch + "'; length: "
428 + ch.length());
429 } else {
430 result = new Character(ch.charAt(0));
431 }
432 } else if (Date.class.isAssignableFrom(type)) {
433 Construct dateConstructor = yamlConstructors.get(Tag.TIMESTAMP);
434 Date date = (Date) dateConstructor.construct(node);
435 if (type == Date.class) {
436 result = date;
437 } else {
438 try {
439 java.lang.reflect.Constructor<?> constr = type.getConstructor(long.class);
440 result = constr.newInstance(date.getTime());
441 } catch (Exception e) {
442 throw new YAMLException("Cannot construct: '" + type + "'");
443 }
444 }
445 } else if (type == Float.class || type == Double.class || type == Float.TYPE
446 || type == Double.TYPE || type == BigDecimal.class) {
447 if (type == BigDecimal.class) {
448 result = new BigDecimal(node.getValue());
449 } else {
450 Construct doubleConstructor = yamlConstructors.get(Tag.FLOAT);
451 result = doubleConstructor.construct(node);
452 if (type == Float.class || type == Float.TYPE) {
453 result = new Float((Double) result);
454 }
455 }
456 } else if (type == Byte.class || type == Short.class || type == Integer.class
457 || type == Long.class || type == BigInteger.class || type == Byte.TYPE
458 || type == Short.TYPE || type == Integer.TYPE || type == Long.TYPE) {
459 Construct intConstructor = yamlConstructors.get(Tag.INT);
460 result = intConstructor.construct(node);
461 if (type == Byte.class || type == Byte.TYPE) {
462 result = new Byte(result.toString());
463 } else if (type == Short.class || type == Short.TYPE) {
464 result = new Short(result.toString());
465 } else if (type == Integer.class || type == Integer.TYPE) {
466 result = new Integer(result.toString());
467 } else if (type == Long.class || type == Long.TYPE) {
468 result = new Long(result.toString());
469 } else {
470
471 result = new BigInteger(result.toString());
472 }
473 } else if (Enum.class.isAssignableFrom(type)) {
474 String enumValueName = node.getValue();
475 try {
476 result = Enum.valueOf(type, enumValueName);
477 } catch (Exception ex) {
478 throw new YAMLException("Unable to find enum value '" + enumValueName
479 + "' for enum class: " + type.getName());
480 }
481 } else if (Calendar.class.isAssignableFrom(type)) {
482 ConstructYamlTimestamp contr = new ConstructYamlTimestamp();
483 contr.construct(node);
484 result = contr.getCalendar();
485 } else {
486 throw new YAMLException("Unsupported class: " + type);
487 }
488 return result;
489 }
490 }
491
492
493
494
495
496 protected class ConstructSequence implements Construct {
497 @SuppressWarnings("unchecked")
498 public Object construct(Node node) {
499 SequenceNode snode = (SequenceNode) node;
500 if (Set.class.isAssignableFrom(node.getType())) {
501 if (node.isTwoStepsConstruction()) {
502 throw new YAMLException("Set cannot be recursive.");
503 } else {
504 return constructSet(snode);
505 }
506 } else if (Collection.class.isAssignableFrom(node.getType())) {
507 if (node.isTwoStepsConstruction()) {
508 return createDefaultList(snode.getValue().size());
509 } else {
510 return constructSequence(snode);
511 }
512 } else if (node.getType().isArray()) {
513 if (node.isTwoStepsConstruction()) {
514 return createArray(node.getType(), snode.getValue().size());
515 } else {
516 return constructArray(snode);
517 }
518 } else {
519
520 List<java.lang.reflect.Constructor<?>> possibleConstructors = new ArrayList<java.lang.reflect.Constructor<?>>(
521 snode.getValue().size());
522 for (java.lang.reflect.Constructor<?> constructor : node.getType()
523 .getConstructors()) {
524 if (snode.getValue().size() == constructor.getParameterTypes().length) {
525 possibleConstructors.add(constructor);
526 }
527 }
528 if (!possibleConstructors.isEmpty()) {
529 if (possibleConstructors.size() == 1) {
530 Object[] argumentList = new Object[snode.getValue().size()];
531 java.lang.reflect.Constructor<?> c = possibleConstructors.get(0);
532 int index = 0;
533 for (Node argumentNode : snode.getValue()) {
534 Class<?> type = c.getParameterTypes()[index];
535
536 argumentNode.setType(type);
537 argumentList[index++] = constructObject(argumentNode);
538 }
539
540 try {
541 return c.newInstance(argumentList);
542 } catch (Exception e) {
543 throw new YAMLException(e);
544 }
545 }
546
547
548 List<Object> argumentList = (List<Object>) constructSequence(snode);
549 Class<?>[] parameterTypes = new Class[argumentList.size()];
550 int index = 0;
551 for (Object parameter : argumentList) {
552 parameterTypes[index] = parameter.getClass();
553 index++;
554 }
555
556 for (java.lang.reflect.Constructor<?> c : possibleConstructors) {
557 Class<?>[] argTypes = c.getParameterTypes();
558 boolean foundConstructor = true;
559 for (int i = 0; i < argTypes.length; i++) {
560 if (!wrapIfPrimitive(argTypes[i]).isAssignableFrom(parameterTypes[i])) {
561 foundConstructor = false;
562 break;
563 }
564 }
565 if (foundConstructor) {
566 try {
567 return c.newInstance(argumentList.toArray());
568 } catch (Exception e) {
569 throw new YAMLException(e);
570 }
571 }
572 }
573 }
574 throw new YAMLException("No suitable constructor with "
575 + String.valueOf(snode.getValue().size()) + " arguments found for "
576 + node.getType());
577
578 }
579 }
580
581 private final Class<? extends Object> wrapIfPrimitive(Class<?> clazz) {
582 if (!clazz.isPrimitive()) {
583 return clazz;
584 }
585 if (clazz == Integer.TYPE) {
586 return Integer.class;
587 }
588 if (clazz == Float.TYPE) {
589 return Float.class;
590 }
591 if (clazz == Double.TYPE) {
592 return Double.class;
593 }
594 if (clazz == Boolean.TYPE) {
595 return Boolean.class;
596 }
597 if (clazz == Long.TYPE) {
598 return Long.class;
599 }
600 if (clazz == Character.TYPE) {
601 return Character.class;
602 }
603 if (clazz == Short.TYPE) {
604 return Short.class;
605 }
606 if (clazz == Byte.TYPE) {
607 return Byte.class;
608 }
609 throw new YAMLException("Unexpected primitive " + clazz);
610 }
611
612 @SuppressWarnings("unchecked")
613 public void construct2ndStep(Node node, Object object) {
614 SequenceNode snode = (SequenceNode) node;
615 if (List.class.isAssignableFrom(node.getType())) {
616 List<Object> list = (List<Object>) object;
617 constructSequenceStep2(snode, list);
618 } else if (node.getType().isArray()) {
619 constructArrayStep2(snode, object);
620 } else {
621 throw new YAMLException("Immutable objects cannot be recursive.");
622 }
623 }
624 }
625
626 protected Class<?> getClassForNode(Node node) {
627 Class<? extends Object> classForTag = typeTags.get(node.getTag());
628 if (classForTag == null) {
629 String name = node.getTag().getClassName();
630 Class<?> cl;
631 try {
632 cl = getClassForName(name);
633 } catch (ClassNotFoundException e) {
634 throw new YAMLException("Class not found: " + name);
635 }
636 typeTags.put(node.getTag(), cl);
637 return cl;
638 } else {
639 return classForTag;
640 }
641 }
642
643 protected Class<?> getClassForName(String name) throws ClassNotFoundException {
644 return Class.forName(name);
645 }
646 }