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