View Javadoc

1   /**
2    * Copyright (c) 2008-2011, 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  
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   * Construct standard Java classes
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          // 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          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                 // we need to construct keys to avoid duplications
126                 Object key = constructObject(keyNode);
127                 if (!key2index.containsKey(key)) { // 1st time merging key
128                     values.add(nodeTuple);
129                     // keep track where tuple for the key is
130                     key2index.put(key, values.size() - 1);
131                 } else if (isPreffered) { // there is value for the key, but we
132                                           // need to override it
133                     // change value for the key using saved position
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                 // Java's months are zero-based...
296                 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
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                 // seconds and milliseconds
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                 // timezone
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                     // no time zone provided
327                     timeZone = TimeZone.getTimeZone("UTC");
328                 }
329                 calendar = Calendar.getInstance(timeZone);
330                 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
331                 // Java's months are zero-based...
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             // Note: we do not check for duplicate keys, because it's too
346             // CPU-expensive.
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     // Note: the same code as `construct_yaml_omap`.
377     public class ConstructYamlPairs extends AbstractConstruct {
378         public Object construct(Node node) {
379             // Note: we do not check for duplicate keys, because it's too
380             // CPU-expensive.
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 }