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