Coverage Report - org.simpleframework.xml.core.SignatureScanner
 
Classes in this File Line Coverage Branch Coverage Complexity
SignatureScanner
93%
75/80
85%
36/42
4.091
 
 1  
 /*
 2  
  * SignatureScanner.java July 2009
 3  
  *
 4  
  * Copyright (C) 2009, 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  
 import static java.util.Collections.singletonList;
 23  
 
 24  
 import java.lang.annotation.Annotation;
 25  
 import java.lang.reflect.Constructor;
 26  
 import java.lang.reflect.Method;
 27  
 import java.util.Collection;
 28  
 import java.util.List;
 29  
 
 30  
 import org.simpleframework.xml.Attribute;
 31  
 import org.simpleframework.xml.Element;
 32  
 import org.simpleframework.xml.ElementArray;
 33  
 import org.simpleframework.xml.ElementList;
 34  
 import org.simpleframework.xml.ElementListUnion;
 35  
 import org.simpleframework.xml.ElementMap;
 36  
 import org.simpleframework.xml.ElementMapUnion;
 37  
 import org.simpleframework.xml.ElementUnion;
 38  
 import org.simpleframework.xml.Text;
 39  
 
 40  
 /**
 41  
  * The <code>SignatureScanner</code> object is used to scan each of
 42  
  * the parameters within a constructor for annotations. When each of
 43  
  * the annotations has been extracted it is used to build a parameter
 44  
  * which is then used to build a grid of parameter annotation pairs.
 45  
  * A single constructor can result in multiple signatures and if a
 46  
  * union annotation is used like <code>ElementUnion</code> then this
 47  
  * can result is several annotations being declared.
 48  
  * 
 49  
  * @author Niall Gallagher
 50  
  */
 51  
 class SignatureScanner {
 52  
    
 53  
    /**
 54  
     * This is used to build permutations of parameters extracted.
 55  
     */
 56  
    private final SignatureBuilder builder;
 57  
    
 58  
    /**
 59  
     * This factory is used to creating annotated parameter objects.
 60  
     */
 61  
    private final ParameterFactory factory;
 62  
    
 63  
    /**
 64  
     * This is used to collect all the parameters that are extracted.
 65  
     */
 66  
    private final ParameterMap registry;
 67  
    
 68  
    /**
 69  
     * This is the constructor that is scanned for the parameters.
 70  
     */
 71  
    private final Constructor constructor;
 72  
    
 73  
    /**
 74  
     * This is the declaring class for the constructor scanned.
 75  
     */
 76  
    private final Class type;
 77  
  
 78  
    /**
 79  
     * Constructor for the <code>SignatureScanner</code> object. This
 80  
     * creates a scanner for a single constructor. As well as scanning
 81  
     * for parameters within the constructor this will collect each
 82  
     * of the scanned parameters in a registry so it can be validated.
 83  
     * 
 84  
     * @param constructor this is the constructor that will be scanned
 85  
     * @param registry this is the registry used to collect parameters
 86  
     * @param format this is the format used to style parameters
 87  
     */
 88  3664
    public SignatureScanner(Constructor constructor, ParameterMap registry, Support support) throws Exception {
 89  3664
       this.builder = new SignatureBuilder(constructor);
 90  3664
       this.factory = new ParameterFactory(support);
 91  3664
       this.type = constructor.getDeclaringClass();
 92  3664
       this.constructor = constructor;
 93  3664
       this.registry = registry;
 94  3664
       this.scan(type);
 95  3661
    }
 96  
    
 97  
    /**
 98  
     * This is used to determine if this scanner is valid. The scanner
 99  
     * may not be valid for various reasons. Currently this method 
 100  
     * determines if a scanner is valid by checking the constructor
 101  
     * to see if the object can be instantiated.
 102  
     * 
 103  
     * @return this returns true if this scanner is valid
 104  
     */
 105  
    public boolean isValid() {
 106  3661
       return builder.isValid();
 107  
    }
 108  
    
 109  
    /**
 110  
     * This is used to acquire the signature permutations for the
 111  
     * constructor. Typically this will return a single signature. If
 112  
     * the constructor parameters are annotated with unions then this
 113  
     * will return several signatures representing each permutation.
 114  
     * 
 115  
     * @return this signatures that have been extracted from this
 116  
     */
 117  
    public List<Signature> getSignatures() throws Exception {
 118  2102
       return builder.build();
 119  
    }
 120  
    
 121  
    /**
 122  
     * This is used to scan the specified constructor for annotations
 123  
     * that it contains. Each parameter annotation is evaluated and 
 124  
     * if it is an XML annotation it is considered to be a valid
 125  
     * parameter and is added to the signature builder.
 126  
     * 
 127  
     * @param type this is the constructor that is to be scanned
 128  
     */
 129  
    private void scan(Class type) throws Exception {
 130  3664
       Class[] types = constructor.getParameterTypes();
 131  
 
 132  6297
       for(int i = 0; i < types.length; i++) {         
 133  2636
          scan(types[i], i);
 134  
       }
 135  3661
    }
 136  
    
 137  
    /**
 138  
     * This is used to scan the specified constructor for annotations
 139  
     * that it contains. Each parameter annotation is evaluated and 
 140  
     * if it is an XML annotation it is considered to be a valid
 141  
     * parameter and is added to the signature builder.
 142  
     * 
 143  
     * @param type this is the parameter type to be evaluated
 144  
     * @param index this is the index of the parameter to scan
 145  
     */
 146  
    private void scan(Class type, int index) throws Exception {
 147  2636
       Annotation[][] labels = constructor.getParameterAnnotations();
 148  
         
 149  3282
       for(int j = 0; j < labels[index].length; j++) {
 150  649
          Collection<Parameter> value = process(labels[index][j], index); 
 151  
          
 152  646
          for(Parameter parameter : value) {
 153  591
             builder.insert(parameter, index);
 154  
          }
 155  
       }
 156  2633
    }
 157  
    
 158  
    /**
 159  
     * This is used to create <code>Parameter</code> objects which are
 160  
     * used to represent the parameters in a constructor. Each parameter
 161  
     * contains an annotation an the index it appears in.
 162  
     * 
 163  
     * @param label this is the annotation used for the parameter
 164  
     * @param ordinal this is the position the parameter appears at
 165  
     * 
 166  
     * @return this returns the parameters for the constructor
 167  
     */
 168  
    private List<Parameter> process(Annotation label, int ordinal) throws Exception{
 169  649
       if(label instanceof Attribute) {
 170  120
          return create(label, ordinal);
 171  
       }
 172  529
       if(label instanceof Element) {
 173  378
          return create(label, ordinal);
 174  
       }
 175  151
       if(label instanceof ElementList) {
 176  32
          return create(label, ordinal);
 177  
       }     
 178  119
       if(label instanceof ElementArray) {
 179  3
          return create(label, ordinal);
 180  
       }
 181  116
       if(label instanceof ElementMap) {
 182  7
          return create(label, ordinal);
 183  
       }
 184  109
       if(label instanceof ElementListUnion) {
 185  4
          return union(label, ordinal);
 186  
       }     
 187  105
       if(label instanceof ElementMapUnion) {
 188  0
          return union(label, ordinal);
 189  
       }
 190  105
       if(label instanceof ElementUnion) {
 191  9
          return union(label, ordinal);
 192  
       }
 193  96
       if(label instanceof Text) {
 194  21
          return create(label, ordinal);
 195  
       }
 196  75
       return emptyList();
 197  
    }
 198  
    
 199  
    /**
 200  
     * This is used to create a <code>Parameter</code> object which is
 201  
     * used to represent a parameter to a constructor. Each parameter
 202  
     * contains an annotation an the index it appears in.
 203  
     * 
 204  
     * @param label this is the annotation used for the parameter
 205  
     * @param ordinal this is the position the parameter appears at
 206  
     * 
 207  
     * @return this returns the parameter for the constructor
 208  
     */
 209  
    private List<Parameter> union(Annotation label, int ordinal) throws Exception {
 210  13
       Signature signature = new Signature(constructor);
 211  13
       Annotation[] list = extract(label);
 212  
 
 213  46
       for(Annotation value : list) {
 214  34
          Parameter parameter = factory.getInstance(constructor, label, value, ordinal);
 215  34
          String path = parameter.getPath(); 
 216  
          
 217  34
          if(signature.contains(path)) {
 218  1
             throw new UnionException("Annotation name '%s' used more than once in %s for %s", path, label, type);
 219  
          } else {
 220  33
             signature.set(path, parameter);
 221  
          }
 222  33
          register(parameter);
 223  
       }
 224  12
       return signature.getAll();
 225  
    }
 226  
    
 227  
    /**
 228  
     * This is used to create a <code>Parameter</code> object which is
 229  
     * used to represent a parameter to a constructor. Each parameter
 230  
     * contains an annotation an the index it appears in.
 231  
     * 
 232  
     * @param label this is the annotation used for the parameter
 233  
     * @param ordinal this is the position the parameter appears at
 234  
     * 
 235  
     * @return this returns the parameter for the constructor
 236  
     */
 237  
    private List<Parameter> create(Annotation label, int ordinal) throws Exception {
 238  561
       Parameter parameter = factory.getInstance(constructor, label, ordinal);
 239  
       
 240  561
       if(parameter != null) {
 241  561
          register(parameter);
 242  
       }
 243  559
       return singletonList(parameter);
 244  
    }
 245  
    
 246  
    /**
 247  
     * This is used to extract the individual annotations associated
 248  
     * with the union annotation provided. If the annotation does
 249  
     * not represent a union then this will return null.
 250  
     * 
 251  
     * @param label this is the annotation to extract from
 252  
     * 
 253  
     * @return this returns an array of annotations from the union
 254  
     */
 255  
    private Annotation[] extract(Annotation label) throws Exception {
 256  13
       Class union = label.annotationType();
 257  13
       Method[] list = union.getDeclaredMethods();
 258  
       
 259  13
       if(list.length != 1) {
 260  0
          throw new UnionException("Annotation '%s' is not a valid union for %s", label, type);
 261  
       }
 262  13
       Method method = list[0];
 263  13
       Object value = method.invoke(label);
 264  
       
 265  13
       return (Annotation[])value;
 266  
    }
 267  
    
 268  
    /**
 269  
     * This is used to register the provided parameter using the
 270  
     * given path. If this parameter has already existed then this
 271  
     * will validate the parameter against the existing one.  All
 272  
     * registered parameters are registered in to a single table.
 273  
     * 
 274  
     * @param parameter this is the parameter to be registered
 275  
     */
 276  
    private void register(Parameter parameter) throws Exception {
 277  594
       String path = parameter.getPath();
 278  594
       Object key = parameter.getKey();
 279  
       
 280  594
       if(registry.containsKey(key)) {
 281  80
          validate(parameter, key);
 282  
       }
 283  593
       if(registry.containsKey(path)) {
 284  66
          validate(parameter, path);
 285  
       }
 286  592
       registry.put(path, parameter);
 287  592
       registry.put(key, parameter);
 288  592
    }
 289  
    
 290  
    /**
 291  
     * This is used to validate the parameter against all the other
 292  
     * parameters for the class. Validating each of the parameters
 293  
     * ensures that the annotations for the parameters remain
 294  
     * consistent throughout the class.
 295  
     * 
 296  
     * @param parameter this is the parameter to be validated
 297  
     * @param key this is the key of the parameter to validate
 298  
     */
 299  
    private void validate(Parameter parameter, Object key) throws Exception {
 300  146
       Parameter other = registry.get(key);
 301  
       
 302  146
       if(parameter.isText() != other.isText()) {
 303  2
          Annotation expect = parameter.getAnnotation();
 304  2
          Annotation actual = other.getAnnotation();
 305  2
          String path = parameter.getPath();
 306  
          
 307  2
          if(!expect.equals(actual)) {
 308  2
             throw new ConstructorException("Annotations do not match for '%s' in %s", path, type);
 309  
          }
 310  0
          Class real = other.getType();
 311  
       
 312  0
          if(real != parameter.getType()) {
 313  0
             throw new ConstructorException("Parameter types do not match for '%s' in %s", path, type);
 314  
          }
 315  
       }
 316  144
    }
 317  
 }