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 }