Coverage Report - org.simpleframework.xml.core.InstantiatorBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
InstantiatorBuilder
93%
140/149
78%
61/78
3.476
 
 1  
 /*
 2  
  * InstantiatorBuilder.java July 2011
 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 org.simpleframework.xml.core.Support.isAssignable;
 22  
 
 23  
 import java.lang.annotation.Annotation;
 24  
 import java.util.ArrayList;
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 
 28  
 /**
 29  
  * The <code>InstantiatorBuilder</code> object is used to resolve labels
 30  
  * based on a provided parameter. Resolution of labels is done using
 31  
  * a set of mappings based on the names of the labels. Because many
 32  
  * labels might have the same name, this will only map a label if it
 33  
  * has unique name. As well as resolving by name, this can resolve
 34  
  * using the path of the parameter.
 35  
  * 
 36  
  * @author Niall Gallagher
 37  
  * 
 38  
  * @see org.simpleframework.xml.core.StructureBuilder
 39  
  */
 40  
 class InstantiatorBuilder {
 41  
 
 42  
    /**
 43  
     * This is the list of creators representing an object constructor.
 44  
     */
 45  
    private List<Creator> options; 
 46  
    
 47  
    /**
 48  
     * This is the single instance of the instantiator to be built.
 49  
     */
 50  
    private Instantiator factory;
 51  
    
 52  
    /**
 53  
     * This is used to maintain the mappings for the attribute labels.
 54  
     */
 55  
    private LabelMap attributes;
 56  
    
 57  
    /**
 58  
     * This is used to maintain the mappings for the element labels.
 59  
     */
 60  
    private LabelMap elements;
 61  
    
 62  
    /**
 63  
     * This is used to maintain the mappings for the text labels.
 64  
     */
 65  
    private LabelMap texts;
 66  
   
 67  
    /**
 68  
     * This is used for validation to compare annotations used.
 69  
     */
 70  
    private Comparer comparer;
 71  
   
 72  
    /**
 73  
     * This is used to acquire the signatures created for the type.
 74  
     */
 75  
    private Scanner scanner;
 76  
    
 77  
    /**
 78  
     * This is the detail the instantiator uses to create objects.
 79  
     */
 80  
    private Detail detail;
 81  
    
 82  
    /**
 83  
     * Constructor for the <code>InstantiatorBuilder</code> object. This 
 84  
     * is used to create an object that can resolve a label using a
 85  
     * parameter. Resolution is performed using either the name of
 86  
     * the parameter or the path of the parameter.
 87  
     * 
 88  
     * @param scanner this is the scanner to acquire signatures from
 89  
     * @param detail contains the details instantiators are built with
 90  
     */
 91  2379
    public InstantiatorBuilder(Scanner scanner, Detail detail) {
 92  2379
       this.options = new ArrayList<Creator>();
 93  2379
       this.comparer = new Comparer();
 94  2379
       this.attributes = new LabelMap();
 95  2379
       this.elements = new LabelMap();
 96  2379
       this.texts = new LabelMap();
 97  2379
       this.scanner = scanner;
 98  2379
       this.detail = detail;
 99  2379
    }
 100  
    
 101  
    /**
 102  
     * This is used to build the <code>Instantiator</code> object that
 103  
     * will be used to instantiate objects. Validation is performed on 
 104  
     * all of the parameters as well as the <code>Creator</code> objects
 105  
     * associated with the type. This validation ensures that the labels
 106  
     * and constructor parameters match based on annotations.
 107  
     * 
 108  
     * @return this returns the instance that has been built
 109  
     */
 110  
    public Instantiator build() throws Exception {
 111  2370
       if(factory == null) {
 112  2370
          populate(detail);
 113  2370
          build(detail);
 114  2370
          validate(detail);
 115  
       }
 116  2364
       return factory;
 117  
    }
 118  
    
 119  
    /**
 120  
     * This is used to build the <code>Instantiator</code> object that
 121  
     * will be used to instantiate objects. Validation is performed on 
 122  
     * all of the parameters as well as the <code>Creator</code> objects
 123  
     * associated with the type. This validation ensures that the labels
 124  
     * and constructor parameters match based on annotations.
 125  
     * 
 126  
     * @param detail contains the details instantiators are built with
 127  
     */
 128  
    private Instantiator build(Detail detail) throws Exception {
 129  2370
       if(factory == null) {
 130  2370
          factory = create(detail);
 131  
       }
 132  2370
       return factory;
 133  
    }
 134  
    
 135  
    /**
 136  
     * This is used to create the <code>Instantiator</code> object that
 137  
     * will be used to instantiate objects. Validation is performed on 
 138  
     * all of the parameters as well as the <code>Creator</code> objects
 139  
     * associated with the type. This validation ensures that the labels
 140  
     * and constructor parameters match based on annotations.
 141  
     * 
 142  
     * @param detail contains the details instantiators are built with
 143  
     * 
 144  
     * @return this returns the instance that has been created
 145  
     */
 146  
    private Instantiator create(Detail detail) throws Exception {
 147  2370
       Signature primary = scanner.getSignature();
 148  2370
       ParameterMap registry = scanner.getParameters();
 149  2370
       Creator creator = null;
 150  
       
 151  2370
       if(primary != null) {
 152  1808
          creator = new SignatureCreator(primary);
 153  
       }
 154  2370
       return new ClassInstantiator(options, creator, registry, detail);
 155  
    }
 156  
    
 157  
    /**
 158  
     * This is used to create a new <code>Creator</code> object from 
 159  
     * the provided signature. Once created the instance is added to
 160  
     * the list of available creators which can be used to instantiate
 161  
     * an object instance.
 162  
     * 
 163  
     * @param signature this is the signature that is to be used
 164  
     *  
 165  
     * @return this returns the creator associated with this builder
 166  
     */
 167  
    private Creator create(Signature signature) {
 168  2087
       Creator creator = new SignatureCreator(signature); 
 169  
       
 170  2087
       if(signature != null) {
 171  2087
          options.add(creator);
 172  
       }
 173  2087
       return creator;
 174  
    }
 175  
    
 176  
    /**
 177  
     * This is used to create a <code>Parameter</code> based on the 
 178  
     * currently registered labels. Creating a replacement in this way
 179  
     * ensures that all parameters are keyed in the exact same way 
 180  
     * that the label is, making it easier and quicker to match them.
 181  
     * 
 182  
     * @param original this is the original parameter to replace
 183  
     * 
 184  
     * @return this returns the replacement parameter object
 185  
     */
 186  
    private Parameter create(Parameter original) throws Exception {
 187  557
       Label label = resolve(original);
 188  
       
 189  557
       if(label != null) {
 190  554
          return new CacheParameter(original, label);
 191  
       }
 192  3
       return null;
 193  
    }
 194  
    
 195  
    /**
 196  
     * This used to populate replace the parameters extracted from the
 197  
     * scanning process with ones matched with registered labels. By 
 198  
     * replacing parameters in this way the parameters can be better
 199  
     * matched with the associated labels using the label keys.
 200  
     * 
 201  
     * @param detail contains the details instantiators are built with
 202  
     */
 203  
    private void populate(Detail detail) throws Exception {
 204  2370
       List<Signature> list = scanner.getSignatures();
 205  
       
 206  2370
       for(Signature signature : list) {
 207  2087
          populate(signature);
 208  
       }
 209  2370
    }
 210  
    
 211  
    /**
 212  
     * This used to populate replace the parameters extracted from the
 213  
     * scanning process with ones matched with registered labels. By 
 214  
     * replacing parameters in this way the parameters can be better
 215  
     * matched with the associated labels using the label keys.
 216  
     * 
 217  
     * @param signature this is the signature used for a creator
 218  
     */
 219  
    private void populate(Signature signature) throws Exception {
 220  2087
       Signature substitute = new Signature(signature);   
 221  
       
 222  2087
       for(Parameter parameter : signature) {
 223  557
          Parameter replace = create(parameter);
 224  
          
 225  557
          if(replace != null) {
 226  554
             substitute.add(replace);
 227  
          }
 228  557
       }
 229  2087
       create(substitute);
 230  2087
    }
 231  
    
 232  
    /**
 233  
     * This is used to ensure that for each parameter in the builder
 234  
     * there is a matching method or field. This ensures that the
 235  
     * class schema is fully readable and writable. If not method or
 236  
     * field annotation exists for the parameter validation fails.
 237  
     * 
 238  
     * @param detail contains the details instantiators are built with
 239  
     */
 240  
    private void validate(Detail detail) throws Exception {
 241  2370
       ParameterMap registry = scanner.getParameters();
 242  2370
       List<Parameter> list = registry.getAll();
 243  
       
 244  2370
       for(Parameter parameter : list) {
 245  978
          Label label = resolve(parameter);
 246  978
          String path = parameter.getPath();
 247  
          
 248  978
          if(label == null) {
 249  3
             throw new ConstructorException("Parameter '%s' does not have a match in %s", path, detail);
 250  
          }
 251  975
          validateParameter(label, parameter);
 252  973
       }
 253  2365
       validateConstructors();
 254  2364
    }
 255  
    
 256  
    /**
 257  
     * This is used to validate the <code>Parameter</code> object that
 258  
     * exist in the constructors. Validation is performed against the
 259  
     * annotated methods and fields to ensure that they match up.
 260  
     * 
 261  
     * @param label this is the annotated method or field to validate
 262  
     * @param parameter this is the parameter to validate with
 263  
     */
 264  
    private void validateParameter(Label label, Parameter parameter) throws Exception {
 265  975
       Contact contact = label.getContact();
 266  975
       String name = parameter.getName();
 267  975
       Class expect = parameter.getType();
 268  975
       Class actual = contact.getType();
 269  
       
 270  975
       if(!isAssignable(expect, actual)) {
 271  2
          throw new ConstructorException("Type is not compatible with %s for '%s' in %s", label, name, parameter);
 272  
       }
 273  973
       validateNames(label, parameter);
 274  973
       validateAnnotations(label, parameter);
 275  973
    }
 276  
    
 277  
    /**
 278  
     * This is used to validate the names associated with the parameters.
 279  
     * Validation is performed by checking that the parameter name is
 280  
     * present in the list of names the label is known by. This is used
 281  
     * to ensure that the if the label is a union the parameter is one of
 282  
     * the names declared within the union.
 283  
     * 
 284  
     * @param label this is the label to validate the parameter against
 285  
     * @param parameter this is the parameter that is to be validated
 286  
     */
 287  
    private void validateNames(Label label, Parameter parameter) throws Exception {
 288  973
       String[] options = label.getNames();
 289  973
       String name = parameter.getName();
 290  
       
 291  973
       if(!contains(options, name)) {
 292  0
          String require = label.getName();
 293  
          
 294  0
          if(name != require) {
 295  0
             if(name == null || require == null) {
 296  0
                throw new ConstructorException("Annotation does not match %s for '%s' in %s", label, name, parameter);
 297  
             }
 298  0
             if(!name.equals(require)) {
 299  0
                throw new ConstructorException("Annotation does not match %s for '%s' in %s", label, name, parameter);              
 300  
             }
 301  
          }
 302  
       }
 303  973
    }
 304  
    
 305  
    /**
 306  
     * This is used to validate the annotations associated with a field
 307  
     * and a matching constructor parameter. Each constructor parameter
 308  
     * paired with an annotated field or method must be the same 
 309  
     * annotation type and must also contain the same name.
 310  
     * 
 311  
     * @param label this is the label associated with the parameter
 312  
     * @param parameter this is the constructor parameter to use
 313  
     */
 314  
    private void validateAnnotations(Label label, Parameter parameter) throws Exception {
 315  973
       Annotation field = label.getAnnotation();
 316  973
       Annotation argument = parameter.getAnnotation();
 317  973
       String name = parameter.getName();     
 318  
       
 319  973
       if(!comparer.equals(field, argument)) {
 320  150
          Class expect = field.annotationType();
 321  150
          Class actual = argument.annotationType();
 322  
          
 323  150
          if(!expect.equals(actual)) {
 324  0
             throw new ConstructorException("Annotation %s does not match %s for '%s' in %s", actual, expect, name, parameter);  
 325  
          } 
 326  
       }
 327  973
    }
 328  
    
 329  
    /**
 330  
     * This is used to ensure that final methods and fields have a 
 331  
     * constructor parameter that allows the value to be injected in
 332  
     * to. Validating the constructor in this manner ensures that the
 333  
     * class schema remains fully serializable and deserializable.
 334  
     */   
 335  
    private void validateConstructors() throws Exception {
 336  2365
       List<Creator> list = factory.getCreators(); 
 337  
       
 338  2365
       if(factory.isDefault()) {
 339  1807
          validateConstructors(elements);
 340  1807
          validateConstructors(attributes);
 341  
       }
 342  2365
       if(!list.isEmpty()) {
 343  2037
          validateConstructors(elements, list);
 344  2036
          validateConstructors(attributes, list);
 345  
       }
 346  2364
    }
 347  
    
 348  
    /**
 349  
     * This is used when there are only default constructors. It will
 350  
     * check to see if any of the annotated fields or methods is read
 351  
     * only. If a read only method or field is found then this will
 352  
     * throw an exception to indicate that it is not valid. 
 353  
     * 
 354  
     * @param map this is the map of values that is to be validated
 355  
     */
 356  
    private void validateConstructors(LabelMap map) throws Exception {
 357  3614
       for(Label label : map) {
 358  3357
          if(label != null) {
 359  3357
             Contact contact = label.getContact();
 360  
             
 361  3357
             if(contact.isReadOnly()) {
 362  0
                throw new ConstructorException("Default constructor can not accept read only %s in %s", label, detail);
 363  
             }
 364  3357
          }
 365  
       }
 366  3614
    }
 367  
    
 368  
    /**
 369  
     * This is used to ensure that final methods and fields have a 
 370  
     * constructor parameter that allows the value to be injected in
 371  
     * to. Validating the constructor in this manner ensures that the
 372  
     * class schema remains fully serializable and deserializable.
 373  
     * 
 374  
     * @param map this is the map that contains the labels to validate
 375  
     * @param list this is the list of builders to validate
 376  
     */
 377  
    private void validateConstructors(LabelMap map, List<Creator> list) throws Exception {      
 378  4073
       for(Label label : map) {         
 379  4103
          if(label != null) {
 380  4103
             validateConstructor(label, list);
 381  
          }
 382  
       }   
 383  4073
       if(list.isEmpty()) {
 384  1
          throw new ConstructorException("No constructor accepts all read only values in %s", detail);
 385  
       }
 386  4072
    }
 387  
    
 388  
    /**
 389  
     * This is used to ensure that final methods and fields have a 
 390  
     * constructor parameter that allows the value to be injected in
 391  
     * to. Validating the constructor in this manner ensures that the
 392  
     * class schema remains fully serializable and deserializable.
 393  
     * 
 394  
     * @param label this is the variable to check in constructors
 395  
     * @param list this is the list of builders to validate
 396  
     */
 397  
    private void validateConstructor(Label label, List<Creator> list) throws Exception {
 398  4103
       Iterator<Creator> iterator = list.iterator();
 399  
 
 400  8349
       while(iterator.hasNext()) {
 401  4246
          Creator instantiator = iterator.next();
 402  4246
          Signature signature = instantiator.getSignature();
 403  4246
          Contact contact = label.getContact();
 404  4246
          Object key = label.getKey();
 405  
 
 406  4246
          if(contact.isReadOnly()) {
 407  548
             Parameter value = signature.get(key);
 408  
 
 409  548
             if(value == null) {
 410  14
                iterator.remove();
 411  
             }
 412  
          }
 413  4246
       }
 414  4103
    }
 415  
    
 416  
    /**
 417  
     * This <code>register</code> method is used to register a label
 418  
     * based on its name and path. Registration like this is done
 419  
     * to ensure that the label can be resolved based on a parameter
 420  
     * name or path. 
 421  
     * 
 422  
     * @param label this is the label that is to be registered
 423  
     */
 424  
    public void register(Label label) throws Exception {
 425  3955
       if(label.isAttribute()) {
 426  882
          register(label, attributes);
 427  3073
       } else if(label.isText()){
 428  122
          register(label, texts);
 429  
       } else {
 430  2951
          register(label, elements);
 431  
       }
 432  3955
    }
 433  
    
 434  
    /**
 435  
     * This <code>register</code> method is used to register a label
 436  
     * based on its name and path. Registration like this is done
 437  
     * to ensure that the label can be resolved based on a parameter
 438  
     * name or path. 
 439  
     * <p>
 440  
     * Registration here ensures a parameter can be resolved on both
 441  
     * name and path. However, because we want these mappings to be
 442  
     * unique we do not allow multiple names to be mapped to the same
 443  
     * label. For example, say we have 'x/@a' and 'y/@a', these both 
 444  
     * have the same name 'a' even though the point/put( to different
 445  
     * things. Here we would not allow a mapping from 'a' and keep
 446  
     * only mappings based on the full path. This means that any
 447  
     * parameters specified must declare the full path also.
 448  
     * 
 449  
     * @param label this is the label that is to be registered
 450  
     * @param map this is the map that the label is registered with
 451  
     */
 452  
    private void register(Label label, LabelMap map) throws Exception {
 453  3955
       String name = label.getName();
 454  3955
       String path = label.getPath();
 455  
       
 456  3955
       if(map.containsKey(name)) {
 457  53
          Label current = map.get(name);
 458  53
          String key = current.getPath();
 459  
          
 460  53
          if(!key.equals(name)) {
 461  48
             map.remove(name); 
 462  
          }
 463  53
       } else {
 464  3902
          map.put(name, label);
 465  
       }
 466  3955
       map.put(path, label);
 467  3955
    }
 468  
    
 469  
    /**
 470  
     * This <code>resolve</code> method is used to find a label based
 471  
     * on the name and path of the provided parameter. If it can not
 472  
     * be found then this will return null.
 473  
     * 
 474  
     * @param parameter this is the parameter used for resolution
 475  
     * 
 476  
     * @return the label that has been resolved, or null
 477  
     */
 478  
    private Label resolve(Parameter parameter) throws Exception {
 479  1535
       if(parameter.isAttribute()) {
 480  356
          return resolve(parameter, attributes);
 481  1179
       } else if(parameter.isText()){
 482  48
          return resolve(parameter, texts);
 483  
       }
 484  1131
       return resolve(parameter, elements);
 485  
  
 486  
    }
 487  
    
 488  
    /**
 489  
     * This <code>resolve</code> method is used to find a label based
 490  
     * on the name and path of the provided parameter. If it can not
 491  
     * be found then this will return null.
 492  
     * 
 493  
     * @param parameter this is the parameter used for resolution
 494  
     * @param map this is the map that is used for resolution
 495  
     * 
 496  
     * @return the label that has been resolved, or null
 497  
     */
 498  
    private Label resolve(Parameter parameter, LabelMap map) throws Exception {
 499  1535
       String name = parameter.getName();
 500  1535
       String path = parameter.getPath();
 501  1535
       Label label = map.get(path);
 502  
       
 503  1535
       if(label == null) {
 504  6
          return map.get(name);
 505  
       }
 506  1529
       return label;
 507  
    }
 508  
    
 509  
    /**
 510  
     * This is used to determine if a value exists within an array. 
 511  
     * Searching the array like this is required when no collections 
 512  
     * are available to use on the list of attributes.
 513  
     * 
 514  
     * @param list this is the list to begin searching for a value
 515  
     * @param value this is the value to be searched for
 516  
     * 
 517  
     * @return true if the list contains the specified value
 518  
     */
 519  
    private boolean contains(String[] list, String value) throws Exception {
 520  1359
       for(String entry : list) {
 521  1359
         if(entry == value) {
 522  392
            return true;
 523  
         }
 524  967
         if(entry.equals(value)) {
 525  581
            return true;
 526  
         }
 527  
       }
 528  0
       return false;
 529  
    }
 530  
 }