Coverage Report - org.simpleframework.xml.load.MethodScanner
 
Classes in this File Line Coverage Branch Coverage Complexity
MethodScanner
92%
73/79
100%
21/21
0
MethodScanner$1
N/A
N/A
0
MethodScanner$PartMap
100%
3/3
N/A
0
 
 1  
 /*
 2  
  * MethodScanner.java April 2007
 3  
  *
 4  
  * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net>
 5  
  *
 6  
  * This library is free software; you can redistribute it and/or
 7  
  * modify it under the terms of the GNU Lesser General Public
 8  
  * License as published by the Free Software Foundation.
 9  
  *
 10  
  * This library is distributed in the hope that it will be useful,
 11  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  
  * GNU Lesser General Public License for more details.
 14  
  *
 15  
  * You should have received a copy of the GNU Lesser General 
 16  
  * Public License along with this library; if not, write to the 
 17  
  * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 18  
  * Boston, MA  02111-1307  USA
 19  
  */
 20  
 
 21  
 package org.simpleframework.xml.load;
 22  
 
 23  
 import org.simpleframework.xml.Attribute;
 24  
 import org.simpleframework.xml.Element;
 25  
 import org.simpleframework.xml.ElementArray;
 26  
 import org.simpleframework.xml.ElementList;
 27  
 import org.simpleframework.xml.ElementMap;
 28  
 import org.simpleframework.xml.Text;
 29  
 import java.lang.annotation.Annotation;
 30  
 import java.lang.reflect.Method;
 31  
 import java.util.LinkedHashMap;
 32  
 import java.util.Iterator;
 33  
 
 34  
 /**
 35  
  * The <code>MethodScanner</code> object is used to scan an object 
 36  
  * for matching get and set methods for an XML annotation. This will
 37  
  * scan for annotated methods starting with the most specialized
 38  
  * class up the class hierarchy. Thus, annotated methods can be 
 39  
  * overridden in a type specialization.
 40  
  * <p>
 41  
  * The annotated methods must be either a getter or setter method
 42  
  * following the Java Beans naming conventions. This convention is
 43  
  * such that a method must begin with "get", "set", or "is". A pair
 44  
  * of set and get methods for an annotation must make use of the
 45  
  * same type. For instance if the return type for the get method
 46  
  * was <code>String</code> then the set method must have a single
 47  
  * argument parameter that takes a <code>String</code> type.
 48  
  * <p>
 49  
  * For a method to be considered there must be both the get and set
 50  
  * methods. If either method is missing then the scanner fails with
 51  
  * an exception. Also, if an annotation marks a method which does
 52  
  * not follow Java Bean naming conventions an exception is thrown.
 53  
  *    
 54  
  * @author Niall Gallagher
 55  
  */
 56  
 class MethodScanner extends ContactList {
 57  
    
 58  
    /**
 59  
     * This is used to acquire the hierarchy for the class scanned.
 60  
     */
 61  
    private Hierarchy hierarchy;
 62  
    
 63  
    /**
 64  
     * This is used to collect all the set methods from the object.
 65  
     */
 66  
    private PartMap write;
 67  
    
 68  
    /**
 69  
     * This is used to collect all the get methods from the object.
 70  
     */
 71  
    private PartMap read;
 72  
    
 73  
    /**
 74  
     * This is the type of the object that is being scanned.
 75  
     */
 76  
    private Class type;
 77  
    
 78  
    /**
 79  
     * Constructor for the <code>MethodScanner</code> object. This is
 80  
     * used to create an object that will scan the specified class
 81  
     * such that all bean property methods can be paired under the
 82  
     * XML annotation specified within the class.
 83  
     * 
 84  
     * @param type this is the type that is to be scanned for methods
 85  
     * 
 86  
     * @throws Exception thrown if there was a problem scanning
 87  
     */
 88  306
    public MethodScanner(Class type) throws Exception {
 89  306
       this.hierarchy = new Hierarchy(type);
 90  306
       this.write = new PartMap();
 91  306
       this.read = new PartMap();
 92  306
       this.type = type;
 93  306
       this.scan(type);
 94  302
    }
 95  
    
 96  
    /**
 97  
     * This method is used to scan the class hierarchy for each class
 98  
     * in order to extract methods that contain XML annotations. If
 99  
     * a method is annotated it is converted to a contact so that
 100  
     * it can be used during serialization and deserialization.
 101  
     * 
 102  
     * @param type this is the type to be scanned for methods
 103  
     * 
 104  
     * @throws Exception thrown if the object schema is invalid
 105  
     */
 106  
    private void scan(Class type) throws Exception {
 107  306
       for(Class next : hierarchy) {
 108  732
          scan(type, next);
 109  731
       } 
 110  305
       build();
 111  303
       validate();
 112  302
    }
 113  
    
 114  
    /**
 115  
     * This is used to scan the declared methods within the specified
 116  
     * class. Each method will be checked to determine if it contains
 117  
     * an XML element and can be used as a <code>Contact</code> for
 118  
     * an entity within the object.
 119  
     * 
 120  
     * @param real this is the actual type of the object scanned
 121  
     * @param type this is one of the super classes for the object
 122  
     * 
 123  
     * @throws Exception thrown if the class schema is invalid
 124  
     */
 125  
    private void scan(Class real, Class type) throws Exception {
 126  732
       Method[] method = type.getDeclaredMethods();
 127  
 
 128  9630
       for(int i = 0; i < method.length; i++) {
 129  8899
          scan(method[i]);              
 130  
       }     
 131  731
    }
 132  
    
 133  
    /**
 134  
     * This is used to scan all annotations within the given method.
 135  
     * Each annotation is checked against the set of supported XML
 136  
     * annotations. If the annotation is one of the XML annotations
 137  
     * then the method is considered for acceptance as either a
 138  
     * get method or a set method for the annotated property.
 139  
     * 
 140  
     * @param method the method to be scanned for XML annotations
 141  
     * 
 142  
     * @throws Exception if the method is not a Java Bean method
 143  
     */
 144  
    private void scan(Method method) throws Exception {
 145  8899
       Annotation[] list = method.getDeclaredAnnotations();
 146  
       
 147  9121
       for(int i = 0; i < list.length; i++) {
 148  223
          scan(method, list[i]);                       
 149  
       }  
 150  8898
    }
 151  
    
 152  
    /**
 153  
     * This reflectively checks the annotation to determine the type 
 154  
     * of annotation it represents. If it represents an XML schema
 155  
     * annotation it is used to create a <code>Contact</code> which 
 156  
     * can be used to represent the method within the source object.
 157  
     * 
 158  
     * @param method the method that the annotation comes from
 159  
     * @param label the annotation used to model the XML schema
 160  
     * 
 161  
     * @throws Exception if there is more than one text annotation
 162  
     */ 
 163  
    private void scan(Method method, Annotation label) throws Exception {
 164  223
       if(label instanceof Attribute) {
 165  26
          process(method, label);
 166  
       }
 167  223
       if(label instanceof ElementList) {
 168  8
          process(method, label);
 169  
       }
 170  223
       if(label instanceof ElementArray) {
 171  0
          process(method, label);
 172  
       }
 173  223
       if(label instanceof ElementMap) {
 174  0
          process(method, label);
 175  
       }
 176  223
       if(label instanceof Element) {
 177  82
          process(method, label);
 178  
       }             
 179  222
       if(label instanceof Text) {
 180  4
          process(method, label);
 181  
       }
 182  222
    }
 183  
   
 184  
    /**
 185  
     * This is used to classify the specified method into either a get
 186  
     * or set method. If the method is neither then an exception is
 187  
     * thrown to indicate that the XML annotations can only be used
 188  
     * with methods following the Java Bean naming conventions. Once
 189  
     * the method is classified is is added to either the read or 
 190  
     * write map so that it can be paired after scanning is complete.
 191  
     * 
 192  
     * @param method this is the method that is to be classified
 193  
     * @param label this is the annotation applied to the method
 194  
     */  
 195  
    private void process(Method method, Annotation label) throws Exception {
 196  120
       MethodPart part = MethodPartFactory.getInstance(method, label);
 197  119
       MethodType type = part.getMethodType();     
 198  
       
 199  119
       if(type == MethodType.GET) {
 200  58
          process(part, read);
 201  
       }
 202  119
       if(type == MethodType.IS) {
 203  0
          process(part, read);
 204  
       }
 205  119
       if(type == MethodType.SET) {
 206  61
          process(part, write);
 207  
       }
 208  119
    } 
 209  
    
 210  
    /**
 211  
     * This is used to determine whether the specified method can be
 212  
     * inserted into the given <code>PartMap</code>. This ensures 
 213  
     * that only the most specialized method is considered, which 
 214  
     * enables annotated methods to be overridden in subclasses.
 215  
     * 
 216  
     * @param method this is the method part that is to be inserted
 217  
     * @param map this is the part map used to contain the method
 218  
     */
 219  
    private void process(MethodPart method, PartMap map) {
 220  119
       String name = method.getName();
 221  
       
 222  119
       if(name != null) {
 223  119
          map.put(name, method);
 224  
       }
 225  119
    }
 226  
    
 227  
    /**
 228  
     * This method is used to pair the get methods with a matching set
 229  
     * method. This pairs methods using the Java Bean method name, the
 230  
     * names must match exactly, meaning that the case and value of
 231  
     * the strings must be identical. Also in order for this to succeed
 232  
     * the types for the methods and the annotation must also match.
 233  
     *  
 234  
     * @throws Exception thrown if there is a problem matching methods
 235  
     */
 236  
    private void build() throws Exception {
 237  305
       for(String name : read) {
 238  48
          MethodPart part = read.get(name);
 239  
          
 240  48
          if(part != null) {
 241  48
             build(part, name);
 242  
          }
 243  46
       }
 244  303
    }
 245  
    
 246  
    /**
 247  
     * This method is used to pair the get methods with a matching set
 248  
     * method. This pairs methods using the Java Bean method name, the
 249  
     * names must match exactly, meaning that the case and value of
 250  
     * the strings must be identical. Also in order for this to succeed
 251  
     * the types for the methods and the annotation must also match.
 252  
     * 
 253  
     * @param read this is a get method that has been extracted
 254  
     * @param name this is the Java Bean methos name to be matched   
 255  
     *  
 256  
     * @throws Exception thrown if there is a problem matching methods
 257  
     */
 258  
    private void build(MethodPart read, String name) throws Exception {      
 259  48
       MethodPart match = write.take(name);
 260  48
       Method method = read.getMethod();
 261  
       
 262  48
       if(match == null) {
 263  0
          throw new MethodException("No matching set method for %s in %s", method, type);
 264  
       } 
 265  48
       build(read, match);     
 266  46
    }   
 267  
    
 268  
    /**
 269  
     * This method is used to pair the get methods with a matching set
 270  
     * method. This pairs methods using the Java Bean method name, the
 271  
     * names must match exactly, meaning that the case and value of
 272  
     * the strings must be identical. Also in order for this to succeed
 273  
     * the types for the methods and the annotation must also match.
 274  
     * 
 275  
     * @param read this is a get method that has been extracted
 276  
     * @param write this is the write method to compare details with      
 277  
     *  
 278  
     * @throws Exception thrown if there is a problem matching methods
 279  
     */
 280  
    private void build(MethodPart read, MethodPart write) throws Exception {
 281  48
       Annotation label = read.getAnnotation();
 282  48
       String name = read.getName();
 283  
       
 284  48
       if(!write.getAnnotation().equals(label)) {
 285  1
          throw new MethodException("Annotations do not match for '%s' in %s", name, type);
 286  
       }
 287  47
       Class type = read.getType();
 288  
       
 289  47
       if(type != write.getType()) {
 290  1
          throw new MethodException("Method types do not match for %s in %s", name, type);
 291  
       }
 292  46
       add(new MethodContact(read, write));
 293  46
    }
 294  
    
 295  
    /**
 296  
     * This is used to validate the object once all the get methods
 297  
     * have been matched with a set method. This ensures that there
 298  
     * is not a set method within the object that does not have a
 299  
     * match, therefore violating the contract of a property.
 300  
     * 
 301  
     * @throws Exception thrown if there is a unmatched set method
 302  
     */
 303  
    private void validate() throws Exception {
 304  303
       for(String name : write) {
 305  1
          MethodPart part = write.get(name);
 306  
          
 307  1
          if(part != null) {
 308  1
             validate(part, name);
 309  
          }
 310  0
       }
 311  302
    }
 312  
    
 313  
    /**
 314  
     * This is used to validate the object once all the get methods
 315  
     * have been matched with a set method. This ensures that there
 316  
     * is not a set method within the object that does not have a
 317  
     * match, therefore violating the contract of a property.
 318  
     * 
 319  
     * @param write this is a get method that has been extracted
 320  
     * @param name this is the Java Bean methods name to be matched 
 321  
     * 
 322  
     * @throws Exception thrown if there is a unmatched set method
 323  
     */
 324  
    private void validate(MethodPart write, String name) throws Exception {      
 325  1
       MethodPart match = read.take(name);     
 326  1
       Method method = write.getMethod();      
 327  
          
 328  1
       if(match == null) {
 329  1
          throw new MethodException("No matching get method for %s in %s", method, type);
 330  
       }      
 331  0
    }
 332  
    
 333  
    /**
 334  
     * The <code>PartMap</code> is used to contain method parts using
 335  
     * the Java Bean method name for the part. This ensures that the
 336  
     * scanned and extracted methods can be acquired using a common 
 337  
     * name, which should be the parsed Java Bean method name.
 338  
     * 
 339  
     * @see org.simpleframework.xml.load.MethodPart
 340  
     */
 341  1224
    private class PartMap extends LinkedHashMap<String, MethodPart> implements Iterable<String>{
 342  
       
 343  
       /**
 344  
        * This returns an iterator for the Java Bean method names for
 345  
        * the <code>MethodPart</code> objects that are stored in the
 346  
        * map. This allows names to be iterated easily in a for loop.
 347  
        * 
 348  
        * @return this returns an iterator for the method name keys
 349  
        */
 350  
       public Iterator<String> iterator() {
 351  608
          return keySet().iterator();
 352  
       }
 353  
       
 354  
       /**
 355  
        * This is used to acquire the method part for the specified
 356  
        * method name. This will remove the method part from this map
 357  
        * so that it can be checked later to ensure what remains.
 358  
        * 
 359  
        * @param name this is the method name to get the method with       
 360  
        * 
 361  
        * @return this returns the method part for the given key
 362  
        */
 363  
       public MethodPart take(String name) {
 364  49
          return remove(name);
 365  
       }
 366  
    }
 367  
 }