Coverage Report - org.simpleframework.xml.core.FieldScanner
 
Classes in this File Line Coverage Branch Coverage Complexity
FieldScanner
97%
86/88
92%
46/50
2.824
FieldScanner$FieldKey
81%
9/11
50%
2/4
2.824
 
 1  
 /*
 2  
  * FieldScanner.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 static org.simpleframework.xml.DefaultType.FIELD;
 22  
 
 23  
 import java.lang.annotation.Annotation;
 24  
 import java.lang.reflect.Field;
 25  
 import java.lang.reflect.Modifier;
 26  
 import java.util.List;
 27  
 
 28  
 import org.simpleframework.xml.Attribute;
 29  
 import org.simpleframework.xml.DefaultType;
 30  
 import org.simpleframework.xml.Element;
 31  
 import org.simpleframework.xml.ElementArray;
 32  
 import org.simpleframework.xml.ElementList;
 33  
 import org.simpleframework.xml.ElementListUnion;
 34  
 import org.simpleframework.xml.ElementMap;
 35  
 import org.simpleframework.xml.ElementMapUnion;
 36  
 import org.simpleframework.xml.ElementUnion;
 37  
 import org.simpleframework.xml.Text;
 38  
 import org.simpleframework.xml.Transient;
 39  
 import org.simpleframework.xml.Version;
 40  
 
 41  
 /**
 42  
  * The <code>FieldScanner</code> object is used to scan an class for
 43  
  * fields marked with an XML annotation. All fields that contain an
 44  
  * XML annotation are added as <code>Contact</code> objects to the
 45  
  * list of contacts for the class. This scans the object by checking
 46  
  * the class hierarchy, this allows a subclass to override a super
 47  
  * class annotated field, although this should be used rarely.
 48  
  * 
 49  
  * @author Niall Gallagher
 50  
  */
 51  
 class FieldScanner extends ContactList {
 52  
    
 53  
    /**
 54  
     * This is used to create the synthetic annotations for fields.
 55  
     */
 56  
    private final AnnotationFactory factory;
 57  
    
 58  
    /**
 59  
     * This is used to determine which fields have been scanned.
 60  
     */
 61  
    private final ContactMap done;
 62  
    
 63  
    /**
 64  
     * This object contains various support functions for the class.
 65  
     */
 66  
    private final Support support;
 67  
    
 68  
    /**
 69  
     * Constructor for the <code>FieldScanner</code> object. This is
 70  
     * used to perform a scan on the specified class in order to find
 71  
     * all fields that are labeled with an XML annotation.
 72  
     * 
 73  
     * @param detail this contains the details for the class scanned
 74  
     * @param support this contains various support functions
 75  
     */
 76  3393
    public FieldScanner(Detail detail, Support support) throws Exception {
 77  3393
       this.factory = new AnnotationFactory(detail);
 78  3393
       this.done = new ContactMap();
 79  3393
       this.support = support;
 80  3393
       this.scan(detail);
 81  3393
    }
 82  
    
 83  
    /**
 84  
     * This method is used to scan the class hierarchy for each class
 85  
     * in order to extract fields that contain XML annotations. If
 86  
     * the field is annotated it is converted to a contact so that
 87  
     * it can be used during serialization and deserialization.
 88  
     * 
 89  
     * @param detail this contains the details for the class scanned
 90  
     */
 91  
    private void scan(Detail detail) throws Exception {
 92  3393
       DefaultType access = detail.getAccess();
 93  3393
       Class base = detail.getSuper();
 94  
       
 95  3393
       if(base != null) {
 96  1012
          extend(base);
 97  
       }
 98  3393
       extract(detail, access);
 99  3393
       extract(detail);
 100  3393
       build();
 101  3393
    }
 102  
    
 103  
    /**
 104  
     * This method is used to extend the provided class. Extending a
 105  
     * class in this way basically means that the fields that have
 106  
     * been scanned in the specific class will be added to this. Doing
 107  
     * this improves the performance of classes within a hierarchy.
 108  
     * 
 109  
     * @param base the class to inherit scanned fields from
 110  
     */
 111  
    private void extend(Class base) throws Exception {
 112  1012
       ContactList list = support.getFields(base);
 113  
       
 114  1012
       if(list != null) {
 115  1012
          addAll(list);
 116  
       }
 117  1012
    }
 118  
    
 119  
    /**
 120  
     * This is used to scan the declared fields within the specified
 121  
     * class. Each method will be check to determine if it contains
 122  
     * an XML element and can be used as a <code>Contact</code> for
 123  
     * an entity within the object.
 124  
     * 
 125  
     * @param detail this is one of the super classes for the object
 126  
     */  
 127  
    private void extract(Detail detail) {
 128  3393
       List<FieldDetail> fields = detail.getFields();
 129  
       
 130  3393
       for(FieldDetail entry : fields) {
 131  7134
          Annotation[] list = entry.getAnnotations();
 132  7134
          Field field = entry.getField();
 133  
          
 134  10731
          for(Annotation label : list) {
 135  3597
             scan(field, label, list);                  
 136  
          }
 137  7134
       }   
 138  3393
    }
 139  
    
 140  
    /**
 141  
     * This is used to scan all the fields of the class in order to
 142  
     * determine if it should have a default annotation. If the field
 143  
     * should have a default XML annotation then it is added to the
 144  
     * list of contacts to be used to form the class schema.
 145  
     * 
 146  
     * @param detail this is the detail to have its fields scanned
 147  
     * @param access this is the default access type for the class
 148  
     */
 149  
    private void extract(Detail detail, DefaultType access) throws Exception {
 150  3393
       List<FieldDetail> fields = detail.getFields();
 151  
       
 152  3393
       if(access == FIELD) {
 153  144
          for(FieldDetail entry : fields) {
 154  353
             Annotation[] list = entry.getAnnotations();
 155  353
             Field field = entry.getField();
 156  353
             Class real = field.getType();
 157  
             
 158  353
             if(!isStatic(field)) {
 159  352
                process(field, real, list);
 160  
             }
 161  353
          }   
 162  
       }
 163  3393
    }
 164  
    
 165  
    /**
 166  
     * This reflectively checks the annotation to determine the type 
 167  
     * of annotation it represents. If it represents an XML schema
 168  
     * annotation it is used to create a <code>Contact</code> which 
 169  
     * can be used to represent the field within the source object.
 170  
     * 
 171  
     * @param field the field that the annotation comes from
 172  
     * @param label the annotation used to model the XML schema
 173  
     * @param list this is the list of annotations on the field
 174  
     */
 175  
    private void scan(Field field, Annotation label, Annotation[] list) {
 176  3597
       if(label instanceof Attribute) {
 177  854
          process(field, label, list);
 178  
       }
 179  3597
       if(label instanceof ElementUnion) {
 180  71
          process(field, label, list);
 181  
       }
 182  3597
       if(label instanceof ElementListUnion) {
 183  66
          process(field, label, list);
 184  
       }
 185  3597
       if(label instanceof ElementMapUnion) {
 186  21
          process(field, label, list);
 187  
       }
 188  3597
       if(label instanceof ElementList) {
 189  326
          process(field, label, list);
 190  
       }     
 191  3597
       if(label instanceof ElementArray) {
 192  101
          process(field, label, list);
 193  
       }
 194  3597
       if(label instanceof ElementMap) {
 195  139
          process(field, label, list);
 196  
       }
 197  3597
       if(label instanceof Element) {
 198  1449
          process(field, label, list);
 199  
       }       
 200  3597
       if(label instanceof Version) {
 201  4
          process(field, label, list);
 202  
       }
 203  3597
       if(label instanceof Text) {
 204  132
          process(field, label, list);
 205  
       }
 206  3597
       if(label instanceof Transient) {
 207  17
          remove(field, label);
 208  
       }
 209  3597
    }
 210  
    
 211  
    /**
 212  
     * This method is used to process the field an annotation given.
 213  
     * This will check to determine if the field is accessible, if it
 214  
     * is not accessible then it is made accessible so that private
 215  
     * member fields can be used during the serialization process.
 216  
     * 
 217  
     * @param field this is the field to be added as a contact
 218  
     * @param type this is the type to acquire the annotation
 219  
     * @param list this is the list of annotations on the field
 220  
     */
 221  
    private void process(Field field, Class type, Annotation[] list) throws Exception {
 222  352
       Annotation label = factory.getInstance(type);
 223  
       
 224  352
       if(label != null) {
 225  352
          process(field, label, list);
 226  
       }
 227  352
    }
 228  
    
 229  
    /**
 230  
     * This method is used to process the field an annotation given.
 231  
     * This will check to determine if the field is accessible, if it
 232  
     * is not accessible then it is made accessible so that private
 233  
     * member fields can be used during the serialization process.
 234  
     * 
 235  
     * @param field this is the field to be added as a contact
 236  
     * @param label this is the XML annotation used by the field
 237  
     * @param list this is the list of annotations on the field
 238  
     */
 239  
    private void process(Field field, Annotation label, Annotation[] list) {
 240  3515
       Contact contact = new FieldContact(field, label, list);
 241  3515
       Object key = new FieldKey(field);
 242  
       
 243  3515
       if(!field.isAccessible()) {
 244  3366
          field.setAccessible(true);              
 245  
       }  
 246  3515
       insert(key, contact);
 247  3515
    }
 248  
    
 249  
    /**
 250  
     * This is used to insert a contact to this contact list. Here if
 251  
     * a <code>Text</code> annotation is declared on a field that
 252  
     * already has an annotation then the other annotation is given
 253  
     * the priority, this is to so text can be processes separately.
 254  
     * 
 255  
     * @param key this is the key that uniquely identifies the field
 256  
     * @param contact this is the contact that is to be inserted
 257  
     */
 258  
    private void insert(Object key, Contact contact) {
 259  3515
       Contact existing = done.remove(key);
 260  
       
 261  3515
       if(existing != null)  {
 262  36
          if(isText(contact)) {
 263  0
             contact = existing;
 264  
          }
 265  
       }
 266  3515
       done.put(key, contact);
 267  3515
    }
 268  
 
 269  
    /**
 270  
     * This is used to determine if the <code>Text</code> annotation
 271  
     * has been declared on the field. If this annotation is used
 272  
     * then this will return true, otherwise this returns false.
 273  
     * 
 274  
     * @param contact the contact to check for the text annotation
 275  
     * 
 276  
     * @return true if the text annotation was declared on the field
 277  
     */
 278  
    private boolean isText(Contact contact) {
 279  36
       Annotation label = contact.getAnnotation();
 280  
       
 281  36
       if(label instanceof Text) {
 282  0
          return true;
 283  
       }
 284  36
       return false;
 285  
    }
 286  
    
 287  
    /**
 288  
     * This is used to remove a field from the map of processed fields.
 289  
     * A field is removed with the <code>Transient</code> annotation
 290  
     * is used to indicate that it should not be processed by the
 291  
     * scanner. This is required when default types are used.
 292  
     * 
 293  
     * @param field this is the field to be removed from the map
 294  
     * @param label this is the label associated with the field
 295  
     */
 296  
    private void remove(Field field, Annotation label) {
 297  17
       done.remove(new FieldKey(field));
 298  17
    }
 299  
  
 300  
    /**
 301  
     * This is used to build a list of valid contacts for this scanner.
 302  
     * Valid contacts are fields that are either defaulted or those
 303  
     * that have an explicit XML annotation. Any field that has been
 304  
     * marked as transient will not be considered as valid.
 305  
     */
 306  
    private void build() {
 307  3393
       for(Contact contact : done) {
 308  3474
          add(contact);
 309  
       }
 310  3393
    }
 311  
    
 312  
    /**
 313  
     * This is used to determine if a field is static. If a field is
 314  
     * static it should not be considered as a default field. This
 315  
     * ensures the default annotation does not pick up static finals.
 316  
     * 
 317  
     * @param field this is the field to determine if it is static
 318  
     * 
 319  
     * @return true if the field is static, false otherwise
 320  
     */
 321  
    private boolean isStatic(Field field) {
 322  353
       int modifier = field.getModifiers();
 323  
       
 324  353
       if(Modifier.isStatic(modifier)) {
 325  1
          return true;
 326  
       }
 327  352
       return false;
 328  
    }
 329  
    
 330  
    /**
 331  
     * The <code>FieldKey</code> object is used to create a key that
 332  
     * can store a contact using a field without using the methods
 333  
     * of <code>hashCode</code> and <code>equals</code> on the field
 334  
     * directly, as these can perform poorly on certain platforms.
 335  
     */
 336  
    private static class FieldKey {
 337  
       
 338  
       /**
 339  
        * This is the class that the field has been declared on.
 340  
        */
 341  
       private final Class type;
 342  
       
 343  
       /**
 344  
        * This is the name of the field that this represents.
 345  
        */
 346  
       private final String name;
 347  
       
 348  
       /**
 349  
        * Constructor of the <code>FieldKey</code> object. This is
 350  
        * used to create an object that can reference something
 351  
        * in a similar manner to a field. 
 352  
        * 
 353  
        * @param field this is the field to create the key with
 354  
        */
 355  3532
       public FieldKey(Field field) {
 356  3532
          this.type = field.getDeclaringClass();
 357  3532
          this.name = field.getName();
 358  3532
       }
 359  
       
 360  
       /**
 361  
        * This is basically the hash code for the field name. Because
 362  
        * field names are unique within a class collisions using 
 363  
        * just the name for the hash code should be infrequent.
 364  
        * 
 365  
        * @return this returns the hash code for this key
 366  
        */
 367  
       public int hashCode() {
 368  7047
          return name.hashCode();
 369  
       }
 370  
       
 371  
       /**
 372  
        * This method is used to compare this key to other keys. The
 373  
        * declaring class and the name of the field are used to test
 374  
        * for equality. If both are the same this returns true.
 375  
        * 
 376  
        * @param value this is the value that is to be compared to
 377  
        * 
 378  
        * @return this returns true if the field values are equal
 379  
        */
 380  
       public boolean equals(Object value) {
 381  41
          if(value instanceof FieldKey) {
 382  41
             return equals((FieldKey)value);
 383  
          }
 384  0
          return false;
 385  
       }
 386  
       
 387  
       /**
 388  
        * This method is used to compare this key to other keys. The
 389  
        * declaring class and the name of the field are used to test
 390  
        * for equality. If both are the same this returns true.
 391  
        * 
 392  
        * @param other this is the value that is to be compared to
 393  
        * 
 394  
        * @return this returns true if the field values are equal
 395  
        */
 396  
       private boolean equals(FieldKey other) {
 397  41
          if(other.type != type) {
 398  0
             return false;
 399  
          }
 400  41
          return other.name.equals(name);
 401  
       }
 402  
    }
 403  
 }