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 }