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