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