Coverage Report - org.simpleframework.xml.core.LabelExtractor
 
Classes in this File Line Coverage Branch Coverage Complexity
LabelExtractor
93%
75/80
81%
39/48
3.706
LabelExtractor$LabelBuilder
100%
12/12
100%
2/2
3.706
 
 1  
 /*
 2  
  * LabelFactory.java July 2006
 3  
  *
 4  
  * Copyright (C) 2006, 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 java.util.Collections.emptyList;
 22  
 
 23  
 import java.lang.annotation.Annotation;
 24  
 import java.lang.reflect.Constructor;
 25  
 import java.lang.reflect.Method;
 26  
 import java.util.LinkedList;
 27  
 import java.util.List;
 28  
 
 29  
 import org.simpleframework.xml.Attribute;
 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.Version;
 39  
 import org.simpleframework.xml.stream.Format;
 40  
 import org.simpleframework.xml.util.Cache;
 41  
 import org.simpleframework.xml.util.ConcurrentCache;
 42  
 
 43  
 /**
 44  
  * The <code>LabelExtractor</code> object is used to create instances of
 45  
  * the <code>Label</code> object that can be used to convert an XML
 46  
  * node into a Java object. Each label created requires the contact it
 47  
  * represents and the XML annotation it is marked with.  
 48  
  * <p>
 49  
  * The <code>Label</code> objects created by this factory a selected
 50  
  * using the XML annotation type. If the annotation type is not known
 51  
  * the factory will throw an exception, otherwise a label instance
 52  
  * is created that will expose the properties of the annotation.
 53  
  * 
 54  
  * @author Niall Gallagher
 55  
  */
 56  
 class LabelExtractor {
 57  
 
 58  
    /**
 59  
     * This is used to cache the list of labels that have been created.
 60  
     */
 61  
    private final Cache<LabelGroup> cache;
 62  
   
 63  
    /**
 64  
     * Contains the format that is associated with the serializer.
 65  
     */
 66  
    private final Format format;
 67  
    
 68  
    /**
 69  
     * Constructor for the <code>LabelExtractor</code> object. This
 70  
     * creates an extractor that will extract labels for a specific
 71  
     * contact. Labels are cached within the extractor so that they 
 72  
     * can be looked up without having to rebuild it each time.
 73  
     * 
 74  
     * @param format this is the format used by the serializer
 75  
     */
 76  950
    public LabelExtractor(Format format) {
 77  950
       this.cache = new ConcurrentCache<LabelGroup>();
 78  950
       this.format = format;
 79  950
    }
 80  
    
 81  
    /**
 82  
     * Creates a <code>Label</code> using the provided contact and XML
 83  
     * annotation. The label produced contains all information related
 84  
     * to an object member. It knows the name of the XML entity, as
 85  
     * well as whether it is required. Once created the converter can
 86  
     * transform an XML node into Java object and vice versa.
 87  
     * 
 88  
     * @param contact this is contact that the label is produced for
 89  
     * @param label represents the XML annotation for the contact
 90  
     * 
 91  
     * @return returns the label instantiated for the contact
 92  
     */
 93  
    public Label getLabel(Contact contact, Annotation label) throws Exception {
 94  3472
       Object key = getKey(contact, label);
 95  3472
       LabelGroup list = getGroup(contact, label, key);
 96  
       
 97  3472
       if(list != null) {
 98  3472
          return list.getPrimary();
 99  
       }
 100  0
       return null;
 101  
    } 
 102  
    
 103  
    /**
 104  
     * Creates a <code>List</code> using the provided contact and XML
 105  
     * annotation. The labels produced contain all information related
 106  
     * to an object member. It knows the name of the XML entity, as
 107  
     * well as whether it is required. Once created the converter can
 108  
     * transform an XML node into Java object and vice versa.
 109  
     * 
 110  
     * @param contact this is contact that the label is produced for
 111  
     * @param label represents the XML annotation for the contact
 112  
     * 
 113  
     * @return returns the list of labels associated with the contact
 114  
     */
 115  
    public List<Label> getList(Contact contact, Annotation label) throws Exception {
 116  165
       Object key = getKey(contact, label);
 117  165
       LabelGroup list = getGroup(contact, label, key);
 118  
       
 119  165
       if(list != null) {
 120  165
          return list.getList();
 121  
       }
 122  0
       return emptyList();
 123  
    }
 124  
 
 125  
    /**
 126  
     * Creates a <code>LabelGroup</code> using the provided contact and
 127  
     * annotation. The labels produced contain all information related
 128  
     * to an object member. It knows the name of the XML entity, as
 129  
     * well as whether it is required. Once created the converter can
 130  
     * transform an XML node into Java object and vice versa.
 131  
     * 
 132  
     * @param contact this is contact that the label is produced for
 133  
     * @param label represents the XML annotation for the contact
 134  
     * @param key this is the key that uniquely represents the contact
 135  
     * 
 136  
     * @return returns the list of labels associated with the contact
 137  
     */
 138  
    private LabelGroup getGroup(Contact contact, Annotation label, Object key) throws Exception {
 139  3637
       LabelGroup value = cache.fetch(key);
 140  
       
 141  3637
       if(value == null) {
 142  3489
          LabelGroup list = getLabels(contact, label);
 143  
          
 144  3489
          if(list != null) {
 145  3489
             cache.cache(key, list);
 146  
          }
 147  3489
          return list;
 148  
       }
 149  148
       return value;
 150  
    }
 151  
    
 152  
    /**
 153  
     * Creates a <code>LabelGroup</code> using the provided contact and
 154  
     * annotation. The labels produced contain all information related
 155  
     * to an object member. It knows the name of the XML entity, as
 156  
     * well as whether it is required. Once created the converter can
 157  
     * transform an XML node into Java object and vice versa.
 158  
     * 
 159  
     * @param contact this is contact that the label is produced for
 160  
     * @param label represents the XML annotation for the contact
 161  
     * 
 162  
     * @return returns the list of labels associated with the contact
 163  
     */
 164  
    private LabelGroup getLabels(Contact contact, Annotation label) throws Exception {
 165  3489
       if(label instanceof ElementUnion) {
 166  74
          return getUnion(contact, label);
 167  
       }
 168  3415
       if(label instanceof ElementListUnion) {
 169  69
          return getUnion(contact, label);
 170  
       }
 171  3346
       if(label instanceof ElementMapUnion) {
 172  21
          return getUnion(contact, label);
 173  
       }
 174  3325
       return getSingle(contact, label);
 175  
    }
 176  
    
 177  
    /**
 178  
     * Creates a <code>LabelGroup</code> using the provided contact and
 179  
     * annotation. The labels produced contain all information related
 180  
     * to an object member. It knows the name of the XML entity, as
 181  
     * well as whether it is required. Once created the converter can
 182  
     * transform an XML node into Java object and vice versa.
 183  
     * 
 184  
     * @param contact this is contact that the label is produced for
 185  
     * @param label represents the XML annotation for the contact
 186  
     * 
 187  
     * @return returns the list of labels associated with the contact
 188  
     */
 189  
    private LabelGroup getSingle(Contact contact, Annotation label) throws Exception {
 190  3325
       Label value = getLabel(contact, label, null);
 191  
       
 192  3325
       if(value != null) {
 193  3325
          value = new CacheLabel(value);
 194  
       }
 195  3325
       return new LabelGroup(value);
 196  
    }
 197  
    
 198  
    /**
 199  
     * Creates a <code>LabelGroup</code> using the provided contact and
 200  
     * annotation. The labels produced contain all information related
 201  
     * to an object member. It knows the name of the XML entity, as
 202  
     * well as whether it is required. Once created the converter can
 203  
     * transform an XML node into Java object and vice versa.
 204  
     * 
 205  
     * @param contact this is contact that the label is produced for
 206  
     * @param label represents the XML annotation for the contact
 207  
     * 
 208  
     * @return returns the list of labels associated with the contact
 209  
     */
 210  
    private LabelGroup getUnion(Contact contact, Annotation label) throws Exception {
 211  164
       Annotation[] list = getAnnotations(label);
 212  
       
 213  164
       if(list.length > 0) {
 214  164
          List<Label> labels = new LinkedList<Label>();
 215  
       
 216  652
          for(Annotation value : list) {
 217  488
             Label entry = getLabel(contact, label, value);
 218  
             
 219  488
             if(entry != null) {
 220  488
                entry = new CacheLabel(entry);
 221  
             }
 222  488
             labels.add(entry);
 223  
          }
 224  164
          return new LabelGroup(labels);
 225  
       }
 226  0
       return null;
 227  
    }
 228  
    
 229  
    /**
 230  
     * This is used to extract the individual annotations associated
 231  
     * with the union annotation provided. If the annotation does
 232  
     * not represent a union then this will return null.
 233  
     * 
 234  
     * @param label this is the annotation to extract from
 235  
     * 
 236  
     * @return this returns an array of annotations from the union
 237  
     */
 238  
    private Annotation[] getAnnotations(Annotation label) throws Exception {
 239  164
       Class union = label.annotationType();
 240  164
       Method[] list = union.getDeclaredMethods();
 241  
       
 242  164
       if(list.length > 0) {
 243  164
          Method method = list[0];
 244  164
          Object value = method.invoke(label);
 245  
       
 246  164
          return (Annotation[])value;
 247  
       }
 248  0
       return new Annotation[0];
 249  
    }
 250  
    
 251  
    /**
 252  
     * Creates a <code>Label</code> using the provided contact and XML
 253  
     * annotation. The label produced contains all information related
 254  
     * to an object member. It knows the name of the XML entity, as
 255  
     * well as whether it is required. Once created the converter can
 256  
     * transform an XML node into Java object and vice versa.
 257  
     * 
 258  
     * @param contact this is contact that the label is produced for
 259  
     * @param label represents the XML annotation for the contact
 260  
     * @param entry this is the annotation used for the entries
 261  
     * 
 262  
     * @return returns the label instantiated for the field
 263  
     */
 264  
    private Label getLabel(Contact contact, Annotation label, Annotation entry) throws Exception {     
 265  3813
       Constructor factory = getConstructor(label);    
 266  
       
 267  3813
       if(entry != null) {
 268  488
          return (Label)factory.newInstance(contact, label, entry, format);
 269  
       }
 270  3325
       return (Label)factory.newInstance(contact, label, format);
 271  
    }
 272  
     
 273  
    /**
 274  
     * This is used to create a key to uniquely identify a label that
 275  
     * is associated with a contact. A key contains the contact type,
 276  
     * the declaring class, the name, and the annotation type. This will
 277  
     * uniquely identify the label within the class.
 278  
     * 
 279  
     * @param contact this is contact that the label is produced for
 280  
     * @param label represents the XML annotation for the contact
 281  
     * 
 282  
     * @return this returns the key associated with the label
 283  
     */
 284  
    private Object getKey(Contact contact, Annotation label) {
 285  3637
       return new LabelKey(contact, label);
 286  
    }
 287  
    
 288  
     /**
 289  
      * Creates a constructor that can be used to instantiate the label
 290  
      * used to represent the specified annotation. The constructor
 291  
      * created by this method takes two arguments, a contact object 
 292  
      * and an <code>Annotation</code> of the type specified.
 293  
      * 
 294  
      * @param label the XML annotation representing the label
 295  
      * 
 296  
      * @return returns a constructor for instantiating the label 
 297  
      */
 298  
     private Constructor getConstructor(Annotation label) throws Exception {
 299  3813
        LabelBuilder builder = getBuilder(label);
 300  3813
        Constructor factory = builder.getConstructor();
 301  
        
 302  3813
        if(!factory.isAccessible()) {
 303  3813
           factory.setAccessible(true);
 304  
        }
 305  3813
        return factory;
 306  
     }
 307  
     
 308  
     /**
 309  
      * Creates an entry that is used to select the constructor for the
 310  
      * label. Each label must implement a constructor that takes a
 311  
      * contact and the specific XML annotation for that field. If the
 312  
      * annotation is not know this method throws an exception.
 313  
      * 
 314  
      * @param label the XML annotation used to create the label
 315  
      * 
 316  
      * @return this returns the entry used to create a constructor
 317  
      */
 318  
     private LabelBuilder getBuilder(Annotation label) throws Exception{   
 319  3813
        if(label instanceof Element) {
 320  1759
           return new LabelBuilder(ElementLabel.class, Element.class);
 321  
        }
 322  2054
        if(label instanceof ElementList) {
 323  343
           return new LabelBuilder(ElementListLabel.class, ElementList.class);
 324  
        }
 325  1711
        if(label instanceof ElementArray) {
 326  92
           return new LabelBuilder(ElementArrayLabel.class, ElementArray.class);               
 327  
        }
 328  1619
        if(label instanceof ElementMap) {
 329  160
           return new LabelBuilder(ElementMapLabel.class, ElementMap.class);
 330  
        }
 331  1459
        if(label instanceof ElementUnion) {
 332  230
           return new LabelBuilder(ElementUnionLabel.class, ElementUnion.class, Element.class);
 333  
        }
 334  1229
        if(label instanceof ElementListUnion) {
 335  196
           return new LabelBuilder(ElementListUnionLabel.class, ElementListUnion.class, ElementList.class);
 336  
        }
 337  1033
        if(label instanceof ElementMapUnion) {
 338  62
           return new LabelBuilder(ElementMapUnionLabel.class, ElementMapUnion.class, ElementMap.class);
 339  
        }
 340  971
        if(label instanceof Attribute) {
 341  858
           return new LabelBuilder(AttributeLabel.class, Attribute.class);
 342  
        }
 343  113
        if(label instanceof Version) {
 344  4
           return new LabelBuilder(VersionLabel.class, Version.class);
 345  
        }
 346  109
        if(label instanceof Text) {
 347  109
           return new LabelBuilder(TextLabel.class, Text.class);
 348  
        }
 349  0
        throw new PersistenceException("Annotation %s not supported", label);
 350  
     }
 351  
     
 352  
     /**
 353  
      * The <code>LabelBuilder<code> object will create a constructor 
 354  
      * that can be used to instantiate the correct label for the XML
 355  
      * annotation specified. The constructor requires two arguments
 356  
      * a <code>Contact</code> and the specified XML annotation.
 357  
      * 
 358  
      * @see java.lang.reflect.Constructor
 359  
      */
 360  
     private static class LabelBuilder {
 361  
        
 362  
        /**       
 363  
         * This is the XML annotation type within the constructor.
 364  
         */
 365  
        private final Class label;
 366  
        
 367  
        /**
 368  
         * This is the individual entry annotation used for the label.
 369  
         */
 370  
        private final Class entry;
 371  
        
 372  
        /**
 373  
         * This is the label type that is to be instantiated.
 374  
         */
 375  
        private final Class type;
 376  
        
 377  
        /**
 378  
         * Constructor for the <code>LabelBuilder</code> object. This 
 379  
         * pairs the label type with the XML annotation argument used 
 380  
         * within the constructor. This create the constructor.
 381  
         * 
 382  
         * @param type this is the label type to be instantiated
 383  
         * @param label type that is used within the constructor
 384  
         */
 385  
        public LabelBuilder(Class type, Class label) {
 386  3325
           this(type, label, null);
 387  3325
        }
 388  
        
 389  
        /**
 390  
         * Constructor for the <code>LabelBuilder</code> object. This 
 391  
         * pairs the label type with the XML annotation argument used 
 392  
         * within the constructor. This will create the constructor.
 393  
         * 
 394  
         * @param type this is the label type to be instantiated
 395  
         * @param label type that is used within the constructor
 396  
         * @param entry entry that is used within the constructor
 397  
         */
 398  3813
        public LabelBuilder(Class type, Class label, Class entry) {
 399  3813
           this.entry = entry;
 400  3813
           this.label = label;
 401  3813
           this.type = type;
 402  3813
        }
 403  
        
 404  
        /**
 405  
         * Creates the constructor used to instantiate the label for
 406  
         * the XML annotation. The constructor returned will take two
 407  
         * arguments, a contact and the XML annotation type. 
 408  
         * 
 409  
         * @return returns the constructor for the label object
 410  
         */
 411  
        public Constructor getConstructor() throws Exception {
 412  3813
           if(entry != null) {
 413  488
              return getConstructor(label, entry);
 414  
           }
 415  3325
           return getConstructor(label);
 416  
        }
 417  
        
 418  
        /**
 419  
         * Creates the constructor used to instantiate the label for
 420  
         * the XML annotation. The constructor returned will take two
 421  
         * arguments, a contact and the XML annotation type. 
 422  
         * 
 423  
         * @return returns the constructor for the label object
 424  
         */
 425  
        private Constructor getConstructor(Class label) throws Exception {
 426  3325
           return type.getConstructor(Contact.class, label, Format.class);
 427  
        }
 428  
        
 429  
        /**
 430  
         * Creates the constructor used to instantiate the label for
 431  
         * the XML annotation. The constructor returned will take two
 432  
         * arguments, a contact and the XML annotation type.
 433  
         * 
 434  
         * @param label this is the XML annotation argument type used
 435  
         * @param entry this is the entry type to use for the label
 436  
         * 
 437  
         * @return returns the constructor for the label object
 438  
         */
 439  
        private Constructor getConstructor(Class label, Class entry) throws Exception {
 440  488
           return type.getConstructor(Contact.class, label, entry, Format.class);
 441  
        }
 442  
     }
 443  
 }