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