Coverage Report - org.simpleframework.xml.core.MethodPartFactory
 
Classes in this File Line Coverage Branch Coverage Complexity
MethodPartFactory
91%
63/69
81%
31/38
4.167
 
 1  
 /*
 2  
  * MethodPartFactory.java April 2007
 3  
  *
 4  
  * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * Licensed under the Apache License, Version 2.0 (the "License");
 7  
  * you may not use this file except in compliance with the License.
 8  
  * You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing, software
 13  
  * distributed under the License is distributed on an "AS IS" BASIS,
 14  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 15  
  * implied. See the License for the specific language governing 
 16  
  * permissions and limitations under the License.
 17  
  */
 18  
 
 19  
 package org.simpleframework.xml.core;
 20  
 
 21  
 import java.lang.annotation.Annotation;
 22  
 import java.lang.reflect.Method;
 23  
 
 24  
 /**
 25  
  * The <code>MethodPartFactory</code> is used to create method parts
 26  
  * based on the method signature and the XML annotation. This is 
 27  
  * effectively where a method is classified as either a getter or a
 28  
  * setter method within an object. In order to determine the type of
 29  
  * method the method name is checked to see if it is prefixed with
 30  
  * either the "get", "is", or "set" tokens.
 31  
  * <p>
 32  
  * Once the method is determined to be a Java Bean method according 
 33  
  * to conventions the method signature is validated. If the method
 34  
  * signature does not follow a return type with no arguments for the
 35  
  * get method, and a single argument for the set method then this
 36  
  * will throw an exception.
 37  
  * 
 38  
  * @author Niall Gallagher
 39  
  * 
 40  
  * @see org.simpleframework.xml.core.MethodScanner
 41  
  */
 42  
 class MethodPartFactory {
 43  
    
 44  
    /**
 45  
     * This is used to create the synthetic annotations for methods.
 46  
     */
 47  
    private final AnnotationFactory factory;
 48  
    
 49  
    /**
 50  
     * Constructor for the <code>MethodPartFactory</code> object. This
 51  
     * is used to create method parts based on the method signature 
 52  
     * and the XML annotation is uses. The created part can be used to
 53  
     * either set or get values depending on its type.
 54  
     * 
 55  
     * @param detail this contains details for the annotated class
 56  
     */
 57  3414
    public MethodPartFactory(Detail detail) {
 58  3414
       this.factory = new AnnotationFactory(detail);
 59  3414
    }
 60  
    
 61  
    /**
 62  
     * This is used to acquire a <code>MethodPart</code> for the method
 63  
     * provided. This will synthesize an XML annotation to be used for
 64  
     * the method. If the method provided is not a setter or a getter
 65  
     * then this will return null, otherwise it will return a part with
 66  
     * a synthetic XML annotation. In order to be considered a valid
 67  
     * method the Java Bean conventions must be followed by the method.
 68  
     * 
 69  
     * @param method this is the method to acquire the part for
 70  
     * @param list this is the list of annotations on the method
 71  
     * 
 72  
     * @return this is the method part object for the method
 73  
     */
 74  
    public MethodPart getInstance(Method method, Annotation[] list) throws Exception {
 75  107
       Annotation label = getAnnotation(method);
 76  
       
 77  107
       if(label != null) {
 78  107
          return getInstance(method, label, list);
 79  
       }
 80  0
       return null;
 81  
    }
 82  
    
 83  
    /**
 84  
     * This is used to acquire a <code>MethodPart</code> for the name
 85  
     * and annotation of the provided method. This will determine the
 86  
     * method type by examining its signature. If the method follows
 87  
     * Java Bean conventions then either a setter method part or a
 88  
     * getter method part is returned. If the method does not comply
 89  
     * with the conventions an exception is thrown.
 90  
     * 
 91  
     * @param method this is the method to acquire the part for
 92  
     * @param label this is the annotation associated with the method
 93  
     * @param list this is the list of annotations on the method
 94  
     * 
 95  
     * @return this is the method part object for the method
 96  
     */
 97  
    public MethodPart getInstance(Method method, Annotation label, Annotation[] list) throws Exception {
 98  537
       MethodName name = getName(method, label);
 99  534
       MethodType type = name.getType();
 100  
       
 101  534
       if(type == MethodType.SET) {
 102  256
          return new SetPart(name, label, list);
 103  
       }
 104  278
       return new GetPart(name, label, list);
 105  
    }
 106  
    
 107  
    /**
 108  
     * This is used to acquire a <code>MethodName</code> for the name
 109  
     * and annotation of the provided method. This will determine the
 110  
     * method type by examining its signature. If the method follows
 111  
     * Java Bean conventions then either a setter method name or a
 112  
     * getter method name is returned. If the method does not comply
 113  
     * with the conventions an exception is thrown.
 114  
     * 
 115  
     * @param method this is the method to acquire the name for
 116  
     * @param label this is the annotation associated with the method
 117  
     * 
 118  
     * @return this is the method name object for the method
 119  
     */
 120  
    private MethodName getName(Method method, Annotation label) throws Exception {
 121  537
       MethodType type = getMethodType(method);
 122  
       
 123  537
       if(type == MethodType.GET) {
 124  277
          return getRead(method, type);
 125  
       }
 126  260
       if(type == MethodType.IS) {
 127  3
          return getRead(method, type);         
 128  
       }
 129  257
       if(type == MethodType.SET) {
 130  256
          return getWrite(method, type);
 131  
       }
 132  1
       throw new MethodException("Annotation %s must mark a set or get method", label);      
 133  
    }    
 134  
 
 135  
    /**
 136  
     * This is used to acquire a <code>MethodType</code> for the name
 137  
     * of the method provided. This will determine the method type by 
 138  
     * examining its prefix. If the name follows Java Bean conventions 
 139  
     * then either a setter method type is returned. If the name does
 140  
     * not comply with the naming conventions then null is returned.
 141  
     * 
 142  
     * @param method this is the method to acquire the type for
 143  
     * 
 144  
     * @return this is the method name object for the method    
 145  
     */
 146  
    private MethodType getMethodType(Method method) {
 147  759
       String name = method.getName();      
 148  
       
 149  759
       if(name.startsWith("get")) {
 150  380
          return MethodType.GET;
 151  
       }
 152  379
       if(name.startsWith("is")) {
 153  9
          return MethodType.IS;         
 154  
       }
 155  370
       if(name.startsWith("set")) {
 156  358
          return MethodType.SET;
 157  
       }
 158  12
       return MethodType.NONE;
 159  
    }
 160  
    
 161  
    /**
 162  
     * This is used to synthesize an XML annotation given a method. The
 163  
     * provided method must follow the Java Bean conventions and either
 164  
     * be a getter or a setter. If this criteria is satisfied then a
 165  
     * suitable XML annotation is created to be used. Typically a match
 166  
     * is performed on whether the method type is a Java collection or
 167  
     * an array, if neither criteria are true a normal XML element is
 168  
     * used. Synthesizing in this way ensures the best results.
 169  
     * 
 170  
     * @param method this is the method to extract the annotation for
 171  
     * 
 172  
     * @return an XML annotation or null if the method is not suitable
 173  
     */
 174  
    private Annotation getAnnotation(Method method) throws Exception {
 175  107
       Class type = getType(method);
 176  
       
 177  107
       if(type != null) {
 178  107
          return factory.getInstance(type);
 179  
       }
 180  0
       return null;
 181  
    }
 182  
    
 183  
    /**
 184  
     * This is used to extract the type from a method. Type type of a
 185  
     * method is the return type for a getter and a parameter type for
 186  
     * a setter. Such a parameter will only be returned if the method
 187  
     * observes the Java Bean conventions for a property method.
 188  
     * 
 189  
     * @param method this is the method to acquire the type for
 190  
     * 
 191  
     * @return this returns the type associated with the method
 192  
     */
 193  
    public Class getType(Method method) throws Exception {
 194  222
       MethodType type = getMethodType(method);
 195  
       
 196  222
       if(type == MethodType.SET) {
 197  102
          return getParameterType(method);
 198  
       }
 199  120
       if(type == MethodType.GET) {
 200  103
          return getReturnType(method);
 201  
       }
 202  17
       if(type == MethodType.IS) {
 203  6
          return getReturnType(method);
 204  
       }
 205  11
       return null;
 206  
    }
 207  
    
 208  
    /**
 209  
     * This is the parameter type associated with the provided method.
 210  
     * The first parameter is returned if the provided method is a
 211  
     * setter. If the method takes more than one parameter or if it
 212  
     * takes no parameters then null is returned from this.
 213  
     * 
 214  
     * @param method this is the method to get the parameter type for
 215  
     * 
 216  
     * @return this returns the parameter type associated with it
 217  
     */
 218  
    private Class getParameterType(Method method) throws Exception {
 219  102
       Class[] list = method.getParameterTypes();
 220  
       
 221  102
       if(list.length == 1) {
 222  102
          return method.getParameterTypes()[0];
 223  
       }
 224  0
       return null;
 225  
    }
 226  
    
 227  
    /**
 228  
     * This is the return type associated with the provided method.
 229  
     * The return type of the method is provided only if the method
 230  
     * adheres to the Java Bean conventions regarding getter methods.
 231  
     * If the method takes a parameter then this will return null.
 232  
     * 
 233  
     * @param method this is the method to get the return type for
 234  
     * 
 235  
     * @return this returns the return type associated with it
 236  
     */
 237  
    private Class getReturnType(Method method) throws Exception {
 238  109
       Class[] list = method.getParameterTypes();
 239  
       
 240  109
       if(list.length == 0) {
 241  104
          return method.getReturnType();
 242  
       }
 243  5
       return null;
 244  
    }
 245  
    
 246  
    /**
 247  
     * This is used to acquire a <code>MethodName</code> for the name
 248  
     * and annotation of the provided method. This must be a getter
 249  
     * method, and so must have a return type that is not void and 
 250  
     * have not arguments. If the method has arguments an exception 
 251  
     * is thrown, if not the Java Bean method name is provided.
 252  
     *
 253  
     * @param method this is the method to acquire the name for
 254  
     * @param type this is the method type to acquire the name for    
 255  
     * 
 256  
     * @return this is the method name object for the method
 257  
     */
 258  
    private MethodName getRead(Method method, MethodType type) throws Exception {
 259  280
       Class[] list = method.getParameterTypes();
 260  280
       String real = method.getName();
 261  
          
 262  280
       if(list.length != 0) {
 263  2
          throw new MethodException("Get method %s is not a valid property", method);
 264  
       }
 265  278
       String name = getTypeName(real, type);
 266  
       
 267  278
       if(name == null) {
 268  0
          throw new MethodException("Could not get name for %s", method);
 269  
       }
 270  278
       return new MethodName(method, type, name);
 271  
    }
 272  
 
 273  
    /**
 274  
     * This is used to acquire a <code>MethodName</code> for the name
 275  
     * and annotation of the provided method. This must be a setter
 276  
     * method, and so must accept a single argument, if it contains 
 277  
     * more or less than one argument an exception is thrown.
 278  
     * return type that is not void and
 279  
     *
 280  
     * @param method this is the method to acquire the name for
 281  
     * @param type this is the method type to acquire the name for    
 282  
     * 
 283  
     * @return this is the method name object for the method
 284  
     */
 285  
    private MethodName getWrite(Method method, MethodType type) throws Exception {
 286  256
       Class[] list = method.getParameterTypes();
 287  256
       String real = method.getName();
 288  
       
 289  256
       if(list.length != 1) {
 290  0
          throw new MethodException("Set method %s is not a valid property", method);         
 291  
       }
 292  256
       String name = getTypeName(real, type);
 293  
       
 294  256
       if(name == null) {
 295  0
          throw new MethodException("Could not get name for %s", method);
 296  
       }
 297  256
       return new MethodName(method, type, name);
 298  
    }
 299  
    
 300  
    /**
 301  
     * This is used to acquire the name of the method in a Java Bean
 302  
     * property style. Thus any "get", "is", or "set" prefix is 
 303  
     * removed from the name and the following character is changed
 304  
     * to lower case if it does not represent an acronym.
 305  
     * 
 306  
     * @param name this is the name of the method to be converted
 307  
     * @param type this is the type of method the name represents
 308  
     * 
 309  
     * @return this returns the Java Bean name for the method
 310  
     */
 311  
    private String getTypeName(String name, MethodType type) {
 312  534
       int prefix = type.getPrefix();
 313  534
       int size = name.length();
 314  
       
 315  534
       if(size > prefix) {
 316  534
          name = name.substring(prefix, size);
 317  
       }
 318  534
       return Reflector.getName(name);          
 319  
    }
 320  
 }