View Javadoc

1   /**
2    * Copyright (c) 2008-2012, 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.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   * Construct standard Java classes
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          // perform merging only on nodes containing merge node(s)
70          if (node.isMerged()) {
71              node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
72                      new ArrayList<NodeTuple>()));
73          }
74      }
75  
76      /**
77       * Does merge for supplied mapping node.
78       * 
79       * @param node
80       *            where to merge
81       * @param isPreffered
82       *            true if keys of node should take precedence over others...
83       * @param key2index
84       *            maps already merged keys to index from values
85       * @param values
86       *            collects merged NodeTuple
87       * @return list of the merged NodeTuple (to be set as value for the
88       *         MappingNode)
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          // reversed for http://code.google.com/p/snakeyaml/issues/detail?id=139
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                 // we need to construct keys to avoid duplications
128                 Object key = constructObject(keyNode);
129                 if (!key2index.containsKey(key)) { // 1st time merging key
130                     values.add(nodeTuple);
131                     // keep track where tuple for the key is
132                     key2index.put(key, values.size() - 1);
133                 } else if (isPreffered) { // there is value for the key, but we
134                                           // need to override it
135                     // change value for the key using saved position
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                 // Java's months are zero-based...
298                 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
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                 // seconds and milliseconds
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                 // timezone
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                     // no time zone provided
329                     timeZone = TimeZone.getTimeZone("UTC");
330                 }
331                 calendar = Calendar.getInstance(timeZone);
332                 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
333                 // Java's months are zero-based...
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             // Note: we do not check for duplicate keys, because it's too
348             // CPU-expensive.
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     // Note: the same code as `construct_yaml_omap`.
379     public class ConstructYamlPairs extends AbstractConstruct {
380         public Object construct(Node node) {
381             // Note: we do not check for duplicate keys, because it's too
382             // CPU-expensive.
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 }