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