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.math.BigInteger;
19 import java.util.ArrayList;
20 import java.util.Calendar;
21 import java.util.Collections;
22 import java.util.HashMap;
23 import java.util.Iterator;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.TimeZone;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.yaml.snakeyaml.error.YAMLException;
33 import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
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
43
44
45 public class SafeConstructor extends BaseConstructor {
46
47 public static final ConstructUndefined undefinedConstructor = new ConstructUndefined();
48
49 public SafeConstructor() {
50 this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
51 this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
52 this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
53 this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
54 this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
55 this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
56 this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
57 this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
58 this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
59 this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
60 this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
61 this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
62 this.yamlConstructors.put(null, undefinedConstructor);
63 this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
64 this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
65 this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
66 }
67
68 protected void flattenMapping(MappingNode node) {
69
70 if (node.isMerged()) {
71 node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
72 new ArrayList<NodeTuple>()));
73 }
74 }
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90 private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
91 Map<Object, Integer> key2index, List<NodeTuple> values) {
92 List<NodeTuple> nodeValue = node.getValue();
93
94 Collections.reverse(nodeValue);
95 for (Iterator<NodeTuple> iter = nodeValue.iterator(); iter.hasNext();) {
96 final NodeTuple nodeTuple = iter.next();
97 final Node keyNode = nodeTuple.getKeyNode();
98 final Node valueNode = nodeTuple.getValueNode();
99 if (keyNode.getTag().equals(Tag.MERGE)) {
100 iter.remove();
101 switch (valueNode.getNodeId()) {
102 case mapping:
103 MappingNode mn = (MappingNode) valueNode;
104 mergeNode(mn, false, key2index, values);
105 break;
106 case sequence:
107 SequenceNode sn = (SequenceNode) valueNode;
108 List<Node> vals = sn.getValue();
109 for (Node subnode : vals) {
110 if (!(subnode instanceof MappingNode)) {
111 throw new ConstructorException("while constructing a mapping",
112 node.getStartMark(),
113 "expected a mapping for merging, but found "
114 + subnode.getNodeId(), subnode.getStartMark());
115 }
116 MappingNode mnode = (MappingNode) subnode;
117 mergeNode(mnode, false, key2index, values);
118 }
119 break;
120 default:
121 throw new ConstructorException("while constructing a mapping",
122 node.getStartMark(),
123 "expected a mapping or list of mappings for merging, but found "
124 + valueNode.getNodeId(), valueNode.getStartMark());
125 }
126 } else {
127
128 Object key = constructObject(keyNode);
129 if (!key2index.containsKey(key)) {
130 values.add(nodeTuple);
131
132 key2index.put(key, values.size() - 1);
133 } else if (isPreffered) {
134
135
136 values.set(key2index.get(key), nodeTuple);
137 }
138 }
139 }
140 return values;
141 }
142
143 protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
144 flattenMapping(node);
145 super.constructMapping2ndStep(node, mapping);
146 }
147
148 @Override
149 protected void constructSet2ndStep(MappingNode node, java.util.Set<Object> set) {
150 flattenMapping(node);
151 super.constructSet2ndStep(node, set);
152 }
153
154 public class ConstructYamlNull extends AbstractConstruct {
155 public Object construct(Node node) {
156 constructScalar((ScalarNode) node);
157 return null;
158 }
159 }
160
161 private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>();
162 static {
163 BOOL_VALUES.put("yes", Boolean.TRUE);
164 BOOL_VALUES.put("no", Boolean.FALSE);
165 BOOL_VALUES.put("true", Boolean.TRUE);
166 BOOL_VALUES.put("false", Boolean.FALSE);
167 BOOL_VALUES.put("on", Boolean.TRUE);
168 BOOL_VALUES.put("off", Boolean.FALSE);
169 }
170
171 public class ConstructYamlBool extends AbstractConstruct {
172 public Object construct(Node node) {
173 String val = (String) constructScalar((ScalarNode) node);
174 return BOOL_VALUES.get(val.toLowerCase());
175 }
176 }
177
178 public class ConstructYamlInt extends AbstractConstruct {
179 public Object construct(Node node) {
180 String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");
181 int sign = +1;
182 char first = value.charAt(0);
183 if (first == '-') {
184 sign = -1;
185 value = value.substring(1);
186 } else if (first == '+') {
187 value = value.substring(1);
188 }
189 int base = 10;
190 if ("0".equals(value)) {
191 return Integer.valueOf(0);
192 } else if (value.startsWith("0b")) {
193 value = value.substring(2);
194 base = 2;
195 } else if (value.startsWith("0x")) {
196 value = value.substring(2);
197 base = 16;
198 } else if (value.startsWith("0")) {
199 value = value.substring(1);
200 base = 8;
201 } else if (value.indexOf(':') != -1) {
202 String[] digits = value.split(":");
203 int bes = 1;
204 int val = 0;
205 for (int i = 0, j = digits.length; i < j; i++) {
206 val += (Long.parseLong(digits[(j - i) - 1]) * bes);
207 bes *= 60;
208 }
209 return createNumber(sign, String.valueOf(val), 10);
210 } else {
211 return createNumber(sign, value, 10);
212 }
213 return createNumber(sign, value, base);
214 }
215 }
216
217 private Number createNumber(int sign, String number, int radix) {
218 Number result;
219 if (sign < 0) {
220 number = "-" + number;
221 }
222 try {
223 result = Integer.valueOf(number, radix);
224 } catch (NumberFormatException e) {
225 try {
226 result = Long.valueOf(number, radix);
227 } catch (NumberFormatException e1) {
228 result = new BigInteger(number, radix);
229 }
230 }
231 return result;
232 }
233
234 public class ConstructYamlFloat extends AbstractConstruct {
235 public Object construct(Node node) {
236 String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");
237 int sign = +1;
238 char first = value.charAt(0);
239 if (first == '-') {
240 sign = -1;
241 value = value.substring(1);
242 } else if (first == '+') {
243 value = value.substring(1);
244 }
245 String valLower = value.toLowerCase();
246 if (".inf".equals(valLower)) {
247 return new Double(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
248 } else if (".nan".equals(valLower)) {
249 return new Double(Double.NaN);
250 } else if (value.indexOf(':') != -1) {
251 String[] digits = value.split(":");
252 int bes = 1;
253 double val = 0.0;
254 for (int i = 0, j = digits.length; i < j; i++) {
255 val += (Double.parseDouble(digits[(j - i) - 1]) * bes);
256 bes *= 60;
257 }
258 return new Double(sign * val);
259 } else {
260 Double d = Double.valueOf(value);
261 return new Double(d.doubleValue() * sign);
262 }
263 }
264 }
265
266 public class ConstructYamlBinary extends AbstractConstruct {
267 public Object construct(Node node) {
268 byte[] decoded = Base64Coder.decode(constructScalar((ScalarNode) node).toString()
269 .toCharArray());
270 return decoded;
271 }
272 }
273
274 private final static Pattern TIMESTAMP_REGEXP = Pattern
275 .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$");
276 private final static Pattern YMD_REGEXP = Pattern
277 .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$");
278
279 public static class ConstructYamlTimestamp extends AbstractConstruct {
280 private Calendar calendar;
281
282 public Calendar getCalendar() {
283 return calendar;
284 }
285
286 public Object construct(Node node) {
287 ScalarNode scalar = (ScalarNode) node;
288 String nodeValue = scalar.getValue();
289 Matcher match = YMD_REGEXP.matcher(nodeValue);
290 if (match.matches()) {
291 String year_s = match.group(1);
292 String month_s = match.group(2);
293 String day_s = match.group(3);
294 calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
295 calendar.clear();
296 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
297
298 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
299 calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
300 return calendar.getTime();
301 } else {
302 match = TIMESTAMP_REGEXP.matcher(nodeValue);
303 if (!match.matches()) {
304 throw new YAMLException("Unexpected timestamp: " + nodeValue);
305 }
306 String year_s = match.group(1);
307 String month_s = match.group(2);
308 String day_s = match.group(3);
309 String hour_s = match.group(4);
310 String min_s = match.group(5);
311
312 String seconds = match.group(6);
313 String millis = match.group(7);
314 if (millis != null) {
315 seconds = seconds + "." + millis;
316 }
317 double fractions = Double.parseDouble(seconds);
318 int sec_s = (int) Math.round(Math.floor(fractions));
319 int usec = (int) Math.round(((fractions - sec_s) * 1000));
320
321 String timezoneh_s = match.group(8);
322 String timezonem_s = match.group(9);
323 TimeZone timeZone;
324 if (timezoneh_s != null) {
325 String time = timezonem_s != null ? ":" + timezonem_s : "00";
326 timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time);
327 } else {
328
329 timeZone = TimeZone.getTimeZone("UTC");
330 }
331 calendar = Calendar.getInstance(timeZone);
332 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
333
334 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
335 calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
336 calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s));
337 calendar.set(Calendar.MINUTE, Integer.parseInt(min_s));
338 calendar.set(Calendar.SECOND, sec_s);
339 calendar.set(Calendar.MILLISECOND, usec);
340 return calendar.getTime();
341 }
342 }
343 }
344
345 public class ConstructYamlOmap extends AbstractConstruct {
346 public Object construct(Node node) {
347
348
349 Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
350 if (!(node instanceof SequenceNode)) {
351 throw new ConstructorException("while constructing an ordered map",
352 node.getStartMark(), "expected a sequence, but found " + node.getNodeId(),
353 node.getStartMark());
354 }
355 SequenceNode snode = (SequenceNode) node;
356 for (Node subnode : snode.getValue()) {
357 if (!(subnode instanceof MappingNode)) {
358 throw new ConstructorException("while constructing an ordered map",
359 node.getStartMark(), "expected a mapping of length 1, but found "
360 + subnode.getNodeId(), subnode.getStartMark());
361 }
362 MappingNode mnode = (MappingNode) subnode;
363 if (mnode.getValue().size() != 1) {
364 throw new ConstructorException("while constructing an ordered map",
365 node.getStartMark(), "expected a single mapping item, but found "
366 + mnode.getValue().size() + " items", mnode.getStartMark());
367 }
368 Node keyNode = mnode.getValue().get(0).getKeyNode();
369 Node valueNode = mnode.getValue().get(0).getValueNode();
370 Object key = constructObject(keyNode);
371 Object value = constructObject(valueNode);
372 omap.put(key, value);
373 }
374 return omap;
375 }
376 }
377
378
379 public class ConstructYamlPairs extends AbstractConstruct {
380 public Object construct(Node node) {
381
382
383 if (!(node instanceof SequenceNode)) {
384 throw new ConstructorException("while constructing pairs", node.getStartMark(),
385 "expected a sequence, but found " + node.getNodeId(), node.getStartMark());
386 }
387 SequenceNode snode = (SequenceNode) node;
388 List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size());
389 for (Node subnode : snode.getValue()) {
390 if (!(subnode instanceof MappingNode)) {
391 throw new ConstructorException("while constructingpairs", node.getStartMark(),
392 "expected a mapping of length 1, but found " + subnode.getNodeId(),
393 subnode.getStartMark());
394 }
395 MappingNode mnode = (MappingNode) subnode;
396 if (mnode.getValue().size() != 1) {
397 throw new ConstructorException("while constructing pairs", node.getStartMark(),
398 "expected a single mapping item, but found " + mnode.getValue().size()
399 + " items", mnode.getStartMark());
400 }
401 Node keyNode = mnode.getValue().get(0).getKeyNode();
402 Node valueNode = mnode.getValue().get(0).getValueNode();
403 Object key = constructObject(keyNode);
404 Object value = constructObject(valueNode);
405 pairs.add(new Object[] { key, value });
406 }
407 return pairs;
408 }
409 }
410
411 public class ConstructYamlSet implements Construct {
412 public Object construct(Node node) {
413 if (node.isTwoStepsConstruction()) {
414 return createDefaultSet();
415 } else {
416 return constructSet((MappingNode) node);
417 }
418 }
419
420 @SuppressWarnings("unchecked")
421 public void construct2ndStep(Node node, Object object) {
422 if (node.isTwoStepsConstruction()) {
423 constructSet2ndStep((MappingNode) node, (Set<Object>) object);
424 } else {
425 throw new YAMLException("Unexpected recursive set structure. Node: " + node);
426 }
427 }
428 }
429
430 public class ConstructYamlStr extends AbstractConstruct {
431 public Object construct(Node node) {
432 return constructScalar((ScalarNode) node);
433 }
434 }
435
436 public class ConstructYamlSeq implements Construct {
437 public Object construct(Node node) {
438 SequenceNode seqNode = (SequenceNode) node;
439 if (node.isTwoStepsConstruction()) {
440 return createDefaultList((seqNode.getValue()).size());
441 } else {
442 return constructSequence(seqNode);
443 }
444 }
445
446 @SuppressWarnings("unchecked")
447 public void construct2ndStep(Node node, Object data) {
448 if (node.isTwoStepsConstruction()) {
449 constructSequenceStep2((SequenceNode) node, (List<Object>) data);
450 } else {
451 throw new YAMLException("Unexpected recursive sequence structure. Node: " + node);
452 }
453 }
454 }
455
456 public class ConstructYamlMap implements Construct {
457 public Object construct(Node node) {
458 if (node.isTwoStepsConstruction()) {
459 return createDefaultMap();
460 } else {
461 return constructMapping((MappingNode) node);
462 }
463 }
464
465 @SuppressWarnings("unchecked")
466 public void construct2ndStep(Node node, Object object) {
467 if (node.isTwoStepsConstruction()) {
468 constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
469 } else {
470 throw new YAMLException("Unexpected recursive mapping structure. Node: " + node);
471 }
472 }
473 }
474
475 public static final class ConstructUndefined extends AbstractConstruct {
476 public Object construct(Node node) {
477 throw new ConstructorException(null, null,
478 "could not determine a constructor for the tag " + node.getTag(),
479 node.getStartMark());
480 }
481 }
482 }