Coverage Report - org.simpleframework.xml.core.GroupExtractor
 
Classes in this File Line Coverage Branch Coverage Complexity
GroupExtractor
100%
33/33
78%
11/14
1.708
GroupExtractor$Registry
100%
36/36
100%
18/18
1.708
 
 1  
 /*
 2  
  * GroupExtractor.java March 2011
 3  
  *
 4  
  * Copyright (C) 2011, 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.util.Iterator;
 23  
 import java.util.LinkedHashMap;
 24  
 
 25  
 import org.simpleframework.xml.Text;
 26  
 import org.simpleframework.xml.stream.Format;
 27  
 
 28  
 /**
 29  
  * The <code>GroupExtractor</code> represents an extractor for labels
 30  
  * associated with a particular union annotation. This extractor 
 31  
  * registers <code>Label</code> by name and by type. Acquiring
 32  
  * the label by type allows the serialization process to dynamically
 33  
  * select a label, and thus converter, based on the instance type.
 34  
  * On deserialization a label is dynamically selected based on name.
 35  
  * 
 36  
  * @author Niall Gallagher
 37  
  * 
 38  
  * @see org.simpleframework.xml.core.Group
 39  
  */
 40  
 class GroupExtractor implements Group {
 41  
 
 42  
    /**
 43  
     * This represents a factory for creating union extractors.
 44  
     */
 45  
    private final ExtractorFactory factory;
 46  
    
 47  
    /**
 48  
     * This represents the union label to be used for this group.
 49  
     */
 50  
    private final Annotation label;
 51  
    
 52  
    /**
 53  
     * This contains each label registered by name and by type.
 54  
     */
 55  
    private final Registry registry;
 56  
    
 57  
    /**
 58  
     * This contains each label registered by label name.
 59  
     */
 60  
    private final LabelMap elements;
 61  
    
 62  
    /**
 63  
     * Constructor for the <code>GroupExtractor</code> object. This
 64  
     * will create an extractor for the provided union annotation.
 65  
     * Each individual declaration within the union is extracted
 66  
     * and made available within the group.
 67  
     * 
 68  
     * @param contact this is the annotated field or method
 69  
     * @param label this is the label associated with the contact
 70  
     * @param format this is the format used by this extractor
 71  
     */
 72  524
    public GroupExtractor(Contact contact, Annotation label, Format format) throws Exception{
 73  524
       this.factory = new ExtractorFactory(contact, label, format);
 74  524
       this.elements = new LabelMap();
 75  524
       this.registry = new Registry(elements);
 76  524
       this.label = label;
 77  524
       this.extract();
 78  524
    } 
 79  
    
 80  
    /**
 81  
     * This is used to acquire the names for each label associated
 82  
     * with this <code>Group</code> instance. The names provided
 83  
     * here are not styled according to a serialization context.
 84  
     * 
 85  
     * @return this returns the names of each union extracted
 86  
     */
 87  
    public String[] getNames() throws Exception {
 88  488
       return elements.getKeys();
 89  
    }
 90  
    
 91  
    /**
 92  
     * This is used to acquire the paths for each label associated
 93  
     * with this <code>Group</code> instance. The paths provided
 94  
     * here are not styled according to a serialization context.
 95  
     * 
 96  
     * @return this returns the paths of each union extracted
 97  
     */
 98  
    public String[] getPaths() throws Exception {
 99  1010
       return elements.getPaths();
 100  
    }
 101  
    
 102  
    /**
 103  
     * This is used to acquire a <code>LabelMap</code> containing the
 104  
     * labels available to the group. Providing a context object 
 105  
     * ensures that each of the labels is mapped to a name that is
 106  
     * styled according to its internal style.
 107  
     * 
 108  
     * @return this returns a label map containing the labels 
 109  
     */
 110  
    public LabelMap getElements() throws Exception {
 111  527
       return elements.getLabels();
 112  
    }
 113  
 
 114  
    /**
 115  
     * This is used to acquire a <code>Label</code> based on the type
 116  
     * of an object. Selecting a label based on the type ensures that
 117  
     * the serialization process can dynamically convert an object
 118  
     * to XML. If the type is not supported, this returns null.
 119  
     * 
 120  
     * @param type this is the type to select the label from
 121  
     * 
 122  
     * @return this returns the label based on the type
 123  
     */
 124  
    public Label getLabel(Class type) {
 125  726
       return registry.resolve(type);
 126  
    }
 127  
    
 128  
    /**
 129  
     * This is used to get a <code>Label</code> that represents the
 130  
     * text between elements on an element union. Providing a label
 131  
     * here ensures that the free text found between elements can
 132  
     * be converted in to strings and added to the list.
 133  
     * 
 134  
     * @return a label if a text annotation has been declared
 135  
     */
 136  
    public Label getText() {
 137  228
       return registry.resolveText();
 138  
    }
 139  
    
 140  
    /**
 141  
     * This is used to determine if the associated type represents a
 142  
     * label defined within the union group. If the label exists
 143  
     * this returns true, if not then this returns false.
 144  
     * 
 145  
     * @param type this is the type to check for
 146  
     * 
 147  
     * @return this returns true if a label for the type exists
 148  
     */
 149  
    public boolean isValid(Class type) {
 150  285
       return registry.resolve(type) != null;
 151  
    }
 152  
    
 153  
    /**
 154  
     * This is used to determine if a type has been declared by the
 155  
     * annotation associated with the group. Unlike determining if
 156  
     * the type is valid this will not consider super types.
 157  
     * 
 158  
     * @param type this is the type to determine if it is declared
 159  
     * 
 160  
     * @return this returns true if the type has been declared
 161  
     */
 162  
    public boolean isDeclared(Class type) {
 163  142
       return registry.containsKey(type);
 164  
    }
 165  
    
 166  
    /**
 167  
     * This is used to determine if the group is inline. A group is
 168  
     * inline if all of the elements in the group is inline. If any of
 169  
     * the <code>Label<code> objects in the group is not inline then
 170  
     * the entire group is not inline, although this is unlikely.
 171  
     * 
 172  
     * @return this returns true if each label in the group is inline
 173  
     */
 174  
    public boolean isInline() {
 175  157
       for(Label label : registry) {
 176  415
          if(!label.isInline()) {
 177  20
             return false;
 178  
          }
 179  
       }
 180  137
       return !registry.isEmpty();
 181  
    }
 182  
    
 183  
    /**
 184  
     * This is used to determine if an annotated list is a text 
 185  
     * list. A text list is a list of elements that also accepts
 186  
     * free text. Typically this will be an element list union that
 187  
     * will allow unstructured XML such as XHTML to be parsed.
 188  
     * 
 189  
     * @return returns true if the label represents a text list
 190  
     */
 191  
    public boolean isTextList() {
 192  196
       return registry.isText();
 193  
    }
 194  
    
 195  
    /**
 196  
     * This is used to extract the labels associated with the group.
 197  
     * Extraction will instantiate a <code>Label</code> object for
 198  
     * an individual annotation declared within the union. Each of
 199  
     * the label instances is then registered by both name and type.
 200  
     */
 201  
    private void extract() throws Exception {
 202  524
       Extractor extractor = factory.getInstance();
 203  
 
 204  524
       if(extractor != null) {
 205  524
          extract(extractor);
 206  
       }
 207  524
    }
 208  
    
 209  
    /**
 210  
     * This is used to extract the labels associated with the group.
 211  
     * Extraction will instantiate a <code>Label</code> object for
 212  
     * an individual annotation declared within the union. Each of
 213  
     * the label instances is then registered by both name and type.
 214  
     * 
 215  
     * @param extractor this is the extractor to get labels for
 216  
     */
 217  
    private void extract(Extractor extractor) throws Exception {
 218  524
       Annotation[] list = extractor.getAnnotations();
 219  
       
 220  2160
       for(Annotation label : list) {
 221  1636
          extract(extractor, label);
 222  
       }
 223  524
    }
 224  
    
 225  
    /**
 226  
     * This is used to extract the labels associated with the group.
 227  
     * Extraction will instantiate a <code>Label</code> object for
 228  
     * an individual annotation declared within the union. Each of
 229  
     * the label instances is then registered by both name and type.
 230  
     * 
 231  
     * @param extractor this is the extractor to get labels for
 232  
     * @param value this is an individual annotation declared
 233  
     */
 234  
    private void extract(Extractor extractor, Annotation value) throws Exception {
 235  1636
       Label label = extractor.getLabel(value);
 236  1636
       Class type = extractor.getType(value);
 237  
       
 238  1636
       if(registry != null) {
 239  1636
          registry.register(type, label);
 240  
       }
 241  1636
    }
 242  
    
 243  
    /**
 244  
     * This returns a string representation of the union group.
 245  
     * Providing a string representation in this way ensures that the
 246  
     * group can be used in exception messages and for any debugging.
 247  
     * 
 248  
     * @return this returns a string representation of the group
 249  
     */
 250  
    public String toString() {
 251  1
       return label.toString();
 252  
    }
 253  
    
 254  
    /**
 255  
     * The <code>Registry</code> object is used to maintain mappings
 256  
     * from types to labels. Each of the mappings can be used to 
 257  
     * dynamically select a label based on the instance type that is
 258  
     * to be serialized. This also registers based on the label name.
 259  
     */
 260  
    private static class Registry extends LinkedHashMap<Class, Label> implements Iterable<Label> {
 261  
    
 262  
       /**
 263  
        * This maintains a mapping between label names and labels.
 264  
        */
 265  
       private LabelMap elements;
 266  
       
 267  
       /**
 268  
        * This label represents the free text between elements.
 269  
        */
 270  
       private Label text;
 271  
       
 272  
       /**
 273  
        * Constructor for the <code>Registry</code> object. This is
 274  
        * used to register label instances using both the name and
 275  
        * type of the label. Registration in this way ensures that
 276  
        * each label can be dynamically selected.
 277  
        * 
 278  
        * @param elements this contains name to label mappings
 279  
        */
 280  524
       public Registry(LabelMap elements){
 281  524
          this.elements = elements;
 282  524
       }
 283  
       
 284  
       /**
 285  
        * This is used to determine if an annotated list is a text 
 286  
        * list. A text list is a list of elements that also accepts
 287  
        * free text. Typically this will be an element list union that
 288  
        * will allow unstructured XML such as XHTML to be parsed.
 289  
        * 
 290  
        * @return returns true if the label represents a text list
 291  
        */
 292  
       public boolean isText() {
 293  196
          return text != null;
 294  
       }
 295  
       
 296  
       /**
 297  
        * This is used so that all the <code>Label</code> objects
 298  
        * that have been added to the registry can be iterated over.
 299  
        * Iteration over the labels allows for easy validation.
 300  
        * 
 301  
        * @return this returns an iterator for all the labels
 302  
        */
 303  
       public Iterator<Label> iterator() {
 304  157
          return values().iterator();
 305  
       }
 306  
       
 307  
       /**
 308  
        * This is used to resolve the text for this registry. If there
 309  
        * is a text annotation declared with the union then this will
 310  
        * return a <code>Label</code> that can be used to convert the
 311  
        * free text between elements in to strings.
 312  
        * 
 313  
        * @return this returns the label representing free text
 314  
        */
 315  
       public Label resolveText() {
 316  228
          return resolveText(String.class);
 317  
       }
 318  
       
 319  
       /**
 320  
        * Here we resolve the <code>Label</code> the type is matched
 321  
        * with by checking if the type is directly mapped or if any of
 322  
        * the super classes of the type are mapped. If there are no
 323  
        * classes in the hierarchy of the type that are mapped then
 324  
        * this will return null otherwise the label will be returned.
 325  
        * 
 326  
        * @param type this is the type to resolve the label for
 327  
        * 
 328  
        * @return this will return the label that is best matched
 329  
        */
 330  
       public Label resolve(Class type) {
 331  1011
          Label label = resolveText(type);
 332  
          
 333  1011
          if(label == null) {
 334  950
             return resolveElement(type);
 335  
          }
 336  61
          return label;
 337  
       }
 338  
       
 339  
       /**
 340  
        * This is used to resolve the text for this registry. If there
 341  
        * is a text annotation declared with the union then this will
 342  
        * return a <code>Label</code> that can be used to convert the
 343  
        * free text between elements in to strings.
 344  
        * 
 345  
        * @param type this is the type to resolve the text as
 346  
        * 
 347  
        * @return this returns the label representing free text
 348  
        */
 349  
       private Label resolveText(Class type) {
 350  1239
          if(text != null) {
 351  207
             if(type == String.class) {
 352  151
                return text;
 353  
             }
 354  
          }
 355  1088
          return null;
 356  
       }
 357  
       
 358  
       /**
 359  
        * Here we resolve the <code>Label</code> the type is matched
 360  
        * with by checking if the type is directly mapped or if any of
 361  
        * the super classes of the type are mapped. If there are no
 362  
        * classes in the hierarchy of the type that are mapped then
 363  
        * this will return null otherwise the label will be returned.
 364  
        * 
 365  
        * @param type this is the type to resolve the label for
 366  
        * 
 367  
        * @return this will return the label that is best matched
 368  
        */
 369  
       private Label resolveElement(Class type) {
 370  968
          while(type != null) {
 371  966
             Label label = get(type);
 372  
             
 373  966
             if(label != null) {
 374  948
                return label;
 375  
             }
 376  18
             type = type.getSuperclass();
 377  18
          }
 378  2
          return null; 
 379  
       }
 380  
       
 381  
       /**
 382  
        * This is used to register a label based on the name. This is
 383  
        * done to ensure the label instance can be dynamically found
 384  
        * during the deserialization process by providing the name.
 385  
        * 
 386  
        * @param name this is the name of the label to be registered
 387  
        * @param label this is the label that is to be registered
 388  
        */
 389  
       public void register(Class type, Label label) throws Exception {
 390  1636
          Label cache = new CacheLabel(label);
 391  
          
 392  1636
          registerElement(type, cache);
 393  1636
          registerText(cache);
 394  1636
       }
 395  
       
 396  
       
 397  
       /**
 398  
        * This is used to register a label based on the name. This is
 399  
        * done to ensure the label instance can be dynamically found
 400  
        * during the deserialization process by providing the name.
 401  
        * 
 402  
        * @param name this is the name of the label to be registered
 403  
        * @param label this is the label that is to be registered
 404  
        */
 405  
       private void registerElement(Class type, Label label) throws Exception {
 406  1636
          String name = label.getName();
 407  
          
 408  1636
          if(!elements.containsKey(name)) {
 409  1634
             elements.put(name, label);
 410  
          }
 411  1636
          if(!containsKey(type)) {
 412  1291
             put(type, label);
 413  
          }
 414  1636
       }
 415  
       
 416  
       /**
 417  
        * This is used to register the provided label is a text label.
 418  
        * Registration as a text label can only happen if the field or
 419  
        * method has a <code>Text</code> annotation declared on it.
 420  
        * 
 421  
        * @param label this is the label to register as text
 422  
        */
 423  
       private void registerText(Label label) throws Exception {
 424  1636
          Contact contact = label.getContact();
 425  1636
          Text value = contact.getAnnotation(Text.class);
 426  
          
 427  1636
          if(value != null) {
 428  84
             text = new TextListLabel(label, value);
 429  
          }
 430  1636
       }
 431  
    }
 432  
 }