• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2008, 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 
constructCompactFormat(ScalarNode node, CompactData data)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 
createInstance(ScalarNode node, CompactData data)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 
setProperties(Object bean, Map<String, Object> data)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 
getCompactData(String scalar)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 
getCompactConstruct()123     private Construct getCompactConstruct() {
124         if (compactConstruct == null) {
125             compactConstruct = createCompactConstruct();
126         }
127         return compactConstruct;
128     }
129 
createCompactConstruct()130     protected Construct createCompactConstruct() {
131         return new ConstructCompactObject();
132     }
133 
134     @Override
getConstructor(Node node)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
construct2ndStep(Node node, Object object)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          */
construct(Node node)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 
applySequence(Object bean, List<?> value)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      */
getSequencePropertyName(Class<?> bean)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 }
237