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.extensions.compactnotation;
    18
    19  import java.beans.IntrospectionException;
    20  import java.util.HashMap;
    21  import java.util.Iterator;
    22  import java.util.List;
    23  import java.util.Map;
    24  import java.util.Set;
    25  import java.util.regex.Matcher;
    26  import java.util.regex.Pattern;
    27
    28  import org.yaml.snakeyaml.constructor.AbstractConstruct;
    29  import org.yaml.snakeyaml.constructor.Construct;
    30  import org.yaml.snakeyaml.constructor.Constructor;
    31  import org.yaml.snakeyaml.error.YAMLException;
    32  import org.yaml.snakeyaml.introspector.Property;
    33  import org.yaml.snakeyaml.nodes.MappingNode;
    34  import org.yaml.snakeyaml.nodes.Node;
    35  import org.yaml.snakeyaml.nodes.NodeTuple;
    36  import org.yaml.snakeyaml.nodes.ScalarNode;
    37
    38  /**
    39   * Construct a custom Java instance out of a compact object notation format.
    40   */
    41  public class CompactConstructor extends Constructor {
    42      private static final Pattern FIRST_PATTERN = Pattern.compile("(\\p{Alpha}.*)(\\s*)\\((.*?)\\)");
    43      private static final Pattern PROPERTY_NAME_PATTERN = Pattern
    44              .compile("\\s*(\\p{Alpha}\\w*)\\s*=(.+)");
    45
    46      @Override
    47      protected Object constructScalar(ScalarNode node) {
    48          CompactData data = getCompactData(node.getValue());
    49          if (data != null) {
    50              return constructCompactFormat(node, data);
    51          } else {
    52              return super.constructScalar(node);
    53          }
    54      }
    55
    56      protected Object constructCompactFormat(ScalarNode node, CompactData data) {
    57          try {
    58              Object obj = createInstance(node, data);
    59              Map<String, Object> properties = new HashMap<String, Object>(data.getProperties());
    60              setProperties(obj, properties);
    61              return obj;
    62          } catch (Exception e) {
    63              throw new YAMLException(e);
    64          }
    65      }
    66
    67      protected Object createInstance(ScalarNode node, CompactData data) throws Exception {
    68          Class<?> clazz = getClassForName(data.getPrefix());
    69          Class<?>[] args = new Class[data.getArguments().size()];
    70          for (int i = 0; i < args.length; i++) {
    71              // assume all the arguments are Strings
    72              args[i] = String.class;
    73          }
    74          java.lang.reflect.Constructor<?> c = clazz.getDeclaredConstructor(args);
    75          c.setAccessible(true);
    76          return c.newInstance(data.getArguments().toArray());
    77
    78      }
    79
    80      protected void setProperties(Object bean, Map<String, Object> data) throws Exception {
    81          if (data == null) {
    82              throw new NullPointerException("Data for Compact Object Notation cannot be null.");
    83          }
    84          for (String key : data.keySet()) {
    85              Property property = getPropertyUtils().getProperty(bean.getClass(), key);
    86              try {
    87                  property.set(bean, data.get(key));
    88              } catch (IllegalArgumentException e) {
    89                  throw new YAMLException("Cannot set property='" + key + "' with value='"
    90                          + data.get(key) + "' (" + data.get(key).getClass() + ") in " + bean);
    91              }
    92          }
    93      }
    94
    95      public CompactData getCompactData(String scalar) {
    96          if (!scalar.endsWith(")")) {
    97              return null;
    98          }
    99          if (scalar.indexOf('(') < 0) {
    100             return null;
    101         }
    102         Matcher m = FIRST_PATTERN.matcher(scalar);
    103         if (m.matches()) {
    104             String tag = m.group(1).trim();
    105             String content = m.group(3);
    106             CompactData data = new CompactData(tag);
    107             if (content.length() == 0)
    108                 return data;
    109             String[] names = content.split("\\s*,\\s*");
    110             for (int i = 0; i < names.length; i++) {
    111                 String section = names[i];
    112                 if (section.indexOf('=') < 0) {
    113                     data.getArguments().add(section);
    114                 } else {
    115                     Matcher sm = PROPERTY_NAME_PATTERN.matcher(section);
    116                     if (sm.matches()) {
    117                         String name = sm.group(1);
    118                         String value = sm.group(2).trim();
    119                         data.getProperties().put(name, value);
    120                     } else {
    121                         return null;
    122                     }
    123                 }
    124             }
    125             return data;
    126         }
    127         return null;
    128     }
    129
    130     @Override
    131     protected Construct getConstructor(Node node) {
    132         if (node instanceof MappingNode) {
    133             MappingNode mnode = (MappingNode) node;
    134             List<NodeTuple> list = mnode.getValue();
    135             if (list.size() == 1) {
    136                 NodeTuple tuple = list.get(0);
    137                 Node key = tuple.getKeyNode();
    138                 if (key instanceof ScalarNode) {
    139                     ScalarNode scalar = (ScalarNode) key;
    140                     CompactData data = getCompactData(scalar.getValue());
    141                     if (data != null) {
    142                         return new ConstructCompactObject();
    143                     }
    144                 }
    145             }
    146         }
    147         return super.getConstructor(node);
    148     }
    149
    150     public class ConstructCompactObject extends AbstractConstruct {
    151         @SuppressWarnings("unchecked")
    152         public Object construct(Node node) {
    153             Map<Object, Object> map = constructMapping((MappingNode) node);
    154             // Compact Object Notation may contain only one entry
    155             Map.Entry<Object, Object> entry = map.entrySet().iterator().next();
    156             Object result = entry.getKey();
    157             Object value = entry.getValue();
    158             if (value instanceof Map) {
    159                 Map<String, Object> properties = (Map<String, Object>) value;
    160                 try {
    161                     setProperties(result, properties);
    162                 } catch (Exception e) {
    163                     throw new YAMLException(e);
    164                 }
    165             } else {
    166                 // value is a list
    167                 applySequence(result, (List<?>) value);
    168             }
    169             return result;
    170         }
    171     }
    172
    173     protected void applySequence(Object bean, List<?> value) {
    174         try {
    175             Property property = getPropertyUtils().getProperty(bean.getClass(),
    176                     getSequencePropertyName(bean.getClass()));
    177             property.set(bean, value);
    178         } catch (Exception e) {
    179             throw new YAMLException(e);
    180         }
    181     }
    182
    183     /**
    184      * Provide the name of the property which is used when the entries form a
    185      * sequence. The property must be a List.
    186      *
    187      * @throws IntrospectionException
    188      */
    189     protected String getSequencePropertyName(Class<?> bean) throws IntrospectionException {
    190         Set<Property> properties = getPropertyUtils().getProperties(bean);
    191         for (Iterator<Property> iterator = properties.iterator(); iterator.hasNext();) {
    192             Property property = iterator.next();
    193             if (!List.class.isAssignableFrom(property.getType())) {
    194                 iterator.remove();
    195             }
    196         }
    197         if (properties.size() == 0) {
    198             throw new YAMLException("No list property found in " + bean);
    199         } else if (properties.size() > 1) {
    200             throw new YAMLException(
    201                     "Many list properties found in "
    202                             + bean
    203                             + "; Please override getSequencePropertyName() to specify which property to use.");
    204         }
    205         return properties.iterator().next().getName();
    206     }
    207 }