Coverage Report - org.simpleframework.xml.core.Introspector
 
Classes in this File Line Coverage Branch Coverage Complexity
Introspector
100%
54/54
100%
24/24
2.462
 
 1  
 /*
 2  
  * Introspector.java February 2005
 3  
  *
 4  
  * Copyright (C) 2005, 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  
 
 23  
 import org.simpleframework.xml.Path;
 24  
 import org.simpleframework.xml.Root;
 25  
 import org.simpleframework.xml.strategy.Type;
 26  
 import org.simpleframework.xml.stream.Format;
 27  
 
 28  
 /**
 29  
  * The <code>Introspector</code> object is used to determine the details
 30  
  * to use for an annotated field or method using both the field an
 31  
  * annotation details. This allows defaults to be picked up from the
 32  
  * method or field type if that have not been explicitly overridden
 33  
  * in the annotation. 
 34  
  * 
 35  
  * @author Niall Gallagher
 36  
  */
 37  
 class Introspector {
 38  
    
 39  
    /**
 40  
     * This is the actual annotation from the specified contact.
 41  
     */
 42  
    private final Annotation marker;
 43  
    
 44  
    /**
 45  
     * This is the field or method contact that has been annotated.
 46  
     */
 47  
    private final Contact contact; 
 48  
    
 49  
    /**
 50  
     * This is the format used to style the paths created.
 51  
     */
 52  
    private final Format format;
 53  
    
 54  
    /**
 55  
     * This is the label used to expose the annotation details.
 56  
     */
 57  
    private final Label label;
 58  
    
 59  
    /**
 60  
     * Constructor for the <code>Introspector</code> object. This is 
 61  
     * used to create an object that will use information available
 62  
     * within the field and annotation to determine exactly what 
 63  
     * the name of the XML element is to be and the type to use.
 64  
     * 
 65  
     * @param contact this is the method or field contact used
 66  
     * @param label this is the annotation on the contact object
 67  
     * @param format this is used to style the paths created
 68  
     */
 69  6044
    public Introspector(Contact contact, Label label, Format format) {
 70  6044
       this.marker = contact.getAnnotation();
 71  6044
       this.contact = contact;
 72  6044
       this.format = format;
 73  6044
       this.label = label;
 74  6044
    }
 75  
    
 76  
    /**
 77  
     * This is used to acquire the <code>Contact</code> for this. The
 78  
     * contact is the actual method or field that has been annotated
 79  
     * and is used to set or get information from the object instance.
 80  
     * 
 81  
     * @return the method or field that this signature represents
 82  
     */
 83  
    public Contact getContact() {
 84  3013229
       return contact;
 85  
    }
 86  
    
 87  
    /**
 88  
     * This returns the dependent type for the annotation. This type
 89  
     * is the type other than the annotated field or method type that
 90  
     * the label depends on. For the <code>ElementList</code> this 
 91  
     * can be the generic parameter to an annotated collection type.
 92  
     * 
 93  
     * @return this is the type that the annotation depends on
 94  
     */
 95  
    public Type getDependent() throws Exception {
 96  818
       return label.getDependent();
 97  
    }
 98  
 
 99  
    /**
 100  
     * This method is used to get the entry name of a label using 
 101  
     * the type of the label. This ensures that if there is no
 102  
     * entry XML element name declared by the annotation that a
 103  
     * suitable name can be calculated from the annotated type.
 104  
     * 
 105  
     * @return this returns a suitable XML entry element name
 106  
     */
 107  
    public String getEntry() throws Exception {
 108  818
       Type depend = getDependent();   
 109  818
       Class type = depend.getType();
 110  
       
 111  818
       if(type.isArray()) {
 112  26
          type = type.getComponentType();
 113  
       }
 114  818
       return getName(type);
 115  
    }
 116  
    
 117  
    /**
 118  
     * This is used to acquire the name of the specified type using
 119  
     * the <code>Root</code> annotation for the class. This will 
 120  
     * use either the name explicitly provided by the annotation or
 121  
     * it will use the name of the class that the annotation was
 122  
     * placed on if there is no explicit name for the root.
 123  
     * 
 124  
     * @param type this is the type to acquire the root name for
 125  
     * 
 126  
     * @return this returns the name of the type from the root
 127  
     * 
 128  
     * @throws Exception if the class contains an illegal schema
 129  
     */
 130  
    private String getName(Class type) throws Exception {
 131  818
       String name = getRoot(type);
 132  
       
 133  818
       if(name != null) {
 134  335
          return name;
 135  
       } else {
 136  483
          name = type.getSimpleName();
 137  
       }
 138  483
       return Reflector.getName(name);
 139  
    }
 140  
    
 141  
    /**
 142  
     * This will acquire the name of the <code>Root</code> annotation
 143  
     * for the specified class. This will traverse the inheritance
 144  
     * hierarchy looking for the root annotation, when it is found it
 145  
     * is used to acquire a name for the XML element it represents.
 146  
     *  
 147  
     * @param type this is the type to acquire the root name with
 148  
     * 
 149  
     * @return the root name for the specified type if it exists
 150  
     */
 151  
    private String getRoot(Class type) { 
 152  818
       Class real = type;
 153  
          
 154  1787
       while(type != null) {
 155  1304
          String name = getRoot(real, type);
 156  
          
 157  1304
          if(name != null) {
 158  335
            return name;
 159  
          }
 160  969
          type = type.getSuperclass();
 161  969
       }
 162  483
       return null;     
 163  
    }
 164  
    
 165  
    /**
 166  
     * This will acquire the name of the <code>Root</code> annotation
 167  
     * for the specified class. This will traverse the inheritance
 168  
     * hierarchy looking for the root annotation, when it is found it
 169  
     * is used to acquire a name for the XML element it represents.
 170  
     *  
 171  
     * @param real the actual type of the object being searched
 172  
     * @param type this is the type to acquire the root name with    
 173  
     * 
 174  
     * @return the root name for the specified type if it exists
 175  
     */
 176  
    private String getRoot(Class<?> real, Class<?> type) {
 177  1304
       String name = type.getSimpleName();
 178  1304
       Root root = type.getAnnotation(Root.class);
 179  
       
 180  1304
       if(root != null) {
 181  335
          String text = root.name();
 182  
           
 183  335
          if(!isEmpty(text)) {
 184  159
             return text;
 185  
          }
 186  176
          return Reflector.getName(name);
 187  
       }
 188  969
       return null;
 189  
    }
 190  
    
 191  
    /**
 192  
     * This is used to determine the name of the XML element that the
 193  
     * annotated field or method represents. This will determine based
 194  
     * on the annotation attributes and the dependent type required
 195  
     * what the name of the XML element this represents is. 
 196  
     * 
 197  
     * @return this returns the name of the XML element expected
 198  
     */
 199  
    public String getName() throws Exception {
 200  10921
       String entry = label.getEntry(); 
 201  
          
 202  10921
       if(!label.isInline()) {
 203  10084
          entry = getDefault();
 204  
       }
 205  10921
       return entry;
 206  
    }
 207  
    
 208  
    /**
 209  
     * This is used to acquire the name for an element by firstly
 210  
     * checking for an override in the annotation. If one exists
 211  
     * then this is returned if not then the name of the field
 212  
     * or method contact is returned. 
 213  
     * 
 214  
     * @return this returns the XML element name to be used
 215  
     */
 216  
    private String getDefault() throws Exception {
 217  10084
       String name = label.getOverride();
 218  
 
 219  10084
       if(!isEmpty(name)) {
 220  5970
          return name;
 221  
       }
 222  4114
       return contact.getName();
 223  
    }  
 224  
    
 225  
    /**
 226  
     * This method is used to return an XPath expression that is 
 227  
     * used to represent the position of a label. If there is no
 228  
     * XPath expression associated with this then an empty path is
 229  
     * returned. This will never return a null expression.
 230  
     * 
 231  
     * @return the XPath expression identifying the location
 232  
     */
 233  
    public Expression getExpression() throws Exception {
 234  6044
       String path = getPath();
 235  
 
 236  6044
       if(path != null) {
 237  935
          return new PathParser(path, contact, format);
 238  
       }
 239  5109
       return new EmptyExpression(format);
 240  
    }
 241  
    
 242  
    /**
 243  
     * This is used to acquire the path of the element or attribute
 244  
     * that is used by the class schema. The path is determined by
 245  
     * acquiring the XPath expression and appending the name of the
 246  
     * label to form a fully qualified path.
 247  
     * 
 248  
     * @return returns the path that is used for the XML property
 249  
     */
 250  
    public String getPath() throws Exception {
 251  6044
       Path path = contact.getAnnotation(Path.class);
 252  
       
 253  6044
       if(path == null) {
 254  5109
          return null;
 255  
       }
 256  935
       return path.value();
 257  
    }
 258  
    
 259  
    /**
 260  
     * This method is used to determine if a root annotation value is
 261  
     * an empty value. Rather than determining if a string is empty
 262  
     * be comparing it to an empty string this method allows for the
 263  
     * value an empty string represents to be changed in future.
 264  
     * 
 265  
     * @param value this is the value to determine if it is empty
 266  
     * 
 267  
     * @return true if the string value specified is an empty value
 268  
     */
 269  
    public boolean isEmpty(String value) {
 270  1524309
       if(value != null) {
 271  1523897
          return value.length() == 0;
 272  
       }
 273  412
       return true;      
 274  
    }
 275  
    
 276  
    /**
 277  
     * This method is used to construct a string that describes the
 278  
     * signature of an XML annotated field or method. This will use
 279  
     * the <code>Contact</code> object and the annotation used for
 280  
     * that contact to construct a string that has sufficient
 281  
     * information such that it can be used in error reporting.
 282  
     * 
 283  
     * @return returns a string used to represent this signature 
 284  
     */
 285  
    public String toString() {
 286  30
       return String.format("%s on %s", marker, contact);
 287  
    }
 288  
 }