Coverage Report - org.simpleframework.xml.core.StructureBuilder
 
Classes in this File Line Coverage Branch Coverage Complexity
StructureBuilder
95%
177/185
88%
94/106
4.217
 
 1  
 /*
 2  
  * StructureBuilder.java November 2010
 3  
  *
 4  
  * Copyright (C) 2010, 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.List;
 23  
 
 24  
 import org.simpleframework.xml.Attribute;
 25  
 import org.simpleframework.xml.Element;
 26  
 import org.simpleframework.xml.ElementArray;
 27  
 import org.simpleframework.xml.ElementList;
 28  
 import org.simpleframework.xml.ElementListUnion;
 29  
 import org.simpleframework.xml.ElementMap;
 30  
 import org.simpleframework.xml.ElementMapUnion;
 31  
 import org.simpleframework.xml.ElementUnion;
 32  
 import org.simpleframework.xml.Order;
 33  
 import org.simpleframework.xml.Text;
 34  
 import org.simpleframework.xml.Version;
 35  
 import org.simpleframework.xml.strategy.Type;
 36  
 
 37  
 /**
 38  
  * The <code>StructureBuilder</code> object is used to build the XML
 39  
  * structure of an annotated class. Once all the information scanned
 40  
  * from the class has been collected a <code>Structure</code> object
 41  
  * can be built using this object. The structure instance will 
 42  
  * contain relevant information regarding the class schema.
 43  
  * <p>
 44  
  * This builder exposes several methods, which are invoked in a
 45  
  * sequence by the <code>Scanner</code> object. In particular there
 46  
  * is a <code>process</code> method which is used to consume the
 47  
  * annotated fields and methods. With the annotations it then builds
 48  
  * the underlying structure representing the class schema.
 49  
  * 
 50  
  * @author Niall Gallagher
 51  
  * 
 52  
  * @see org.simpleframework.xml.core.Scanner
 53  
  */
 54  
 class StructureBuilder {
 55  
 
 56  
    /**
 57  
     * This is used to build an instantiator for creating objects.
 58  
     */
 59  
    private InstantiatorBuilder resolver;
 60  
 
 61  
    /**
 62  
     * This is used to build XPath expressions from annotations.
 63  
     */
 64  
    private ExpressionBuilder builder;
 65  
    
 66  
    /**
 67  
     * This is used to perform the initial ordered registrations. 
 68  
     */
 69  
    private ModelAssembler assembler;
 70  
    
 71  
    /**
 72  
     * This is the instantiator that is used to create instances.
 73  
     */
 74  
    private Instantiator factory;
 75  
    
 76  
    /**
 77  
     * For validation all attributes must be stored in the builder.
 78  
     */
 79  
    private LabelMap attributes;
 80  
    
 81  
    /**
 82  
     * For validation all elements must be stored in the builder.
 83  
     */
 84  
    private LabelMap elements;
 85  
  
 86  
    /**
 87  
     * This is used to maintain the text labels for the class.
 88  
     */
 89  
    private LabelMap texts;
 90  
    
 91  
    /**
 92  
     * This is the source scanner that is used to scan the class.
 93  
     */
 94  
    private Scanner scanner;
 95  
    
 96  
    /**
 97  
     * This object contains various support functions for the class.
 98  
     */
 99  
    private Support support;
 100  
    
 101  
    /**
 102  
     * This is the version annotation extracted from the class.
 103  
     */
 104  
    private Label version;
 105  
    
 106  
    /**
 107  
     * This represents a text annotation extracted from the class.
 108  
     */
 109  
    private Label text;
 110  
    
 111  
    /**
 112  
     * This the core model used to represent the XML structure.
 113  
     */
 114  
    private Model root;
 115  
    
 116  
    /**
 117  
     * This is used to determine if the scanned class is primitive.
 118  
     */
 119  
    private boolean primitive;
 120  
 
 121  
    /**
 122  
     * Constructor for the <code>StructureBuilder</code> object. This
 123  
     * is used to process all the annotations for a class schema and
 124  
     * build a hierarchical model representing the required structure.
 125  
     * Once the structure has been built then it is validated to
 126  
     * ensure that all elements and attributes exist.
 127  
     * 
 128  
     * @param scanner this is the scanner used to scan annotations
 129  
     * @param type this is the type that is being scanned
 130  
     * @param format this is the format used to style the XML
 131  
     */
 132  2379
    public StructureBuilder(Scanner scanner, Detail detail, Support support) throws Exception {
 133  2379
       this.builder = new ExpressionBuilder(detail, support);
 134  2379
       this.assembler = new ModelAssembler(builder, detail, support);
 135  2379
       this.resolver = new InstantiatorBuilder(scanner, detail);
 136  2379
       this.root = new TreeModel(scanner, detail);
 137  2379
       this.attributes = new LabelMap(scanner);
 138  2379
       this.elements = new LabelMap(scanner);
 139  2379
       this.texts = new LabelMap(scanner);
 140  2379
       this.scanner = scanner;
 141  2379
       this.support = support;
 142  2379
    }   
 143  
    
 144  
    /**
 145  
     * This is used to acquire the optional order annotation to provide
 146  
     * order to the elements and attributes for the generated XML. This
 147  
     * acts as an override to the order provided by the declaration of
 148  
     * the types within the object.  
 149  
     * 
 150  
     * @param type this is the type to be scanned for the order
 151  
     */
 152  
    public void assemble(Class type) throws Exception {
 153  2379
       Order order = scanner.getOrder();
 154  
       
 155  2379
       if(order != null) {
 156  26
          assembler.assemble(root, order);
 157  
       }
 158  2376
    }
 159  
    
 160  
    /**
 161  
     * This reflectively checks the annotation to determine the type 
 162  
     * of annotation it represents. If it represents an XML schema
 163  
     * annotation it is used to create a <code>Label</code> which can
 164  
     * be used to represent the field within the context object.
 165  
     * 
 166  
     * @param field the field that the annotation comes from
 167  
     * @param label the annotation used to model the XML schema
 168  
     * 
 169  
     * @throws Exception if there is more than one text annotation
 170  
     */   
 171  
    public void process(Contact field, Annotation label) throws Exception {
 172  3633
       if(label instanceof Attribute) {
 173  883
          process(field, label, attributes);
 174  
       }
 175  3632
       if(label instanceof ElementUnion) {
 176  76
          union(field, label, elements);
 177  
       }
 178  3632
       if(label instanceof ElementListUnion) {
 179  68
          union(field, label, elements);
 180  
       }
 181  3632
       if(label instanceof ElementMapUnion) {
 182  21
          union(field, label, elements);
 183  
       }
 184  3632
       if(label instanceof ElementList) {
 185  343
          process(field, label, elements);
 186  
       }
 187  3632
       if(label instanceof ElementArray) {
 188  101
          process(field, label, elements);
 189  
       }
 190  3632
       if(label instanceof ElementMap) {
 191  160
          process(field, label, elements);
 192  
       }
 193  3632
       if(label instanceof Element) {
 194  1854
          process(field, label, elements);
 195  
       }    
 196  3632
       if(label instanceof Version) {
 197  4
          version(field, label);
 198  
       }
 199  3632
       if(label instanceof Text) {
 200  123
          text(field, label);
 201  
       }
 202  3631
    }   
 203  
    
 204  
    /**
 205  
     * This is used when all details from a field have been gathered 
 206  
     * and a <code>Label</code> implementation needs to be created. 
 207  
     * This will build a label instance based on the field annotation.
 208  
     * If a label with the same name was already inserted then it is
 209  
     * ignored and the value for that field will not be serialized. 
 210  
     * 
 211  
     * @param field the field the annotation was extracted from
 212  
     * @param type the annotation extracted from the field
 213  
     * @param map this is used to collect the label instance created
 214  
     * 
 215  
     * @throws Exception thrown if the label can not be created
 216  
     */   
 217  
    private void union(Contact field, Annotation type, LabelMap map) throws Exception {
 218  165
       List<Label> list = support.getLabels(field, type);
 219  
       
 220  165
       for(Label label : list) {
 221  493
          String path = label.getPath();
 222  493
          String name = label.getName();
 223  
          
 224  493
          if(map.get(path) != null) {
 225  0
             throw new PersistenceException("Duplicate annotation of name '%s' on %s", name, label);
 226  
          }
 227  493
          process(field, label, map);
 228  493
       }
 229  165
    }
 230  
    
 231  
    /**
 232  
     * This is used when all details from a field have been gathered 
 233  
     * and a <code>Label</code> implementation needs to be created. 
 234  
     * This will build a label instance based on the field annotation.
 235  
     * If a label with the same name was already inserted then it is
 236  
     * ignored and the value for that field will not be serialized. 
 237  
     * 
 238  
     * @param field the field the annotation was extracted from
 239  
     * @param type the annotation extracted from the field
 240  
     * @param map this is used to collect the label instance created
 241  
     * 
 242  
     * @throws Exception thrown if the label can not be created
 243  
     */   
 244  
    private void process(Contact field, Annotation type, LabelMap map) throws Exception {
 245  3341
       Label label = support.getLabel(field, type);
 246  3341
       String path = label.getPath();
 247  3341
       String name = label.getName();
 248  
       
 249  3341
       if(map.get(path) != null) {
 250  1
          throw new PersistenceException("Duplicate annotation of name '%s' on %s", name, field);
 251  
       }
 252  3340
       process(field, label, map);
 253  3340
    }
 254  
    
 255  
    /**
 256  
     * This is used when all details from a field have been gathered 
 257  
     * and a <code>Label</code> implementation needs to be created. 
 258  
     * This will build a label instance based on the field annotation.
 259  
     * If a label with the same name was already inserted then it is
 260  
     * ignored and the value for that field will not be serialized. 
 261  
     * 
 262  
     * @param field the field the annotation was extracted from
 263  
     * @param label this is the label representing a field or method
 264  
     * @param map this is used to collect the label instance created
 265  
     * 
 266  
     * @throws Exception thrown if the label can not be created
 267  
     */
 268  
    private void process(Contact field, Label label, LabelMap map) throws Exception {
 269  3833
       Expression expression = label.getExpression();
 270  3833
       String path = label.getPath();
 271  3833
       Model model = root;
 272  
       
 273  3833
       if(!expression.isEmpty()) {
 274  404
          model = register(expression);
 275  
       }
 276  3833
       resolver.register(label);
 277  3833
       model.register(label);      
 278  3833
       map.put(path, label);
 279  3833
    }   
 280  
    
 281  
    /**
 282  
     * This is used to process the <code>Text</code> annotations that
 283  
     * are present in the scanned class. This will set the text label
 284  
     * for the class and an ensure that if there is more than one
 285  
     * text label within the class an exception is thrown.
 286  
     * 
 287  
     * @param field the field the annotation was extracted from
 288  
     * @param type the annotation extracted from the field
 289  
     * 
 290  
     * @throws Exception if there is more than one text annotation
 291  
     */   
 292  
    private void text(Contact field, Annotation type) throws Exception {
 293  123
       Label label = support.getLabel(field, type);
 294  123
       Expression expression = label.getExpression();
 295  123
       String path = label.getPath();
 296  123
       Model model = root;
 297  
       
 298  123
       if(!expression.isEmpty()) {
 299  15
          model = register(expression);
 300  
       }
 301  123
       if(texts.get(path) != null) {
 302  1
          throw new TextException("Multiple text annotations in %s", type);
 303  
       }
 304  122
       resolver.register(label);
 305  122
       model.register(label);
 306  122
       texts.put(path, label);
 307  122
    }
 308  
    
 309  
    /**
 310  
     * This is used to process the <code>Text</code> annotations that
 311  
     * are present in the scanned class. This will set the text label
 312  
     * for the class and an ensure that if there is more than one
 313  
     * text label within the class an exception is thrown.
 314  
     * 
 315  
     * @param field the field the annotation was extracted from
 316  
     * @param type the annotation extracted from the field
 317  
     * 
 318  
     * @throws Exception if there is more than one text annotation
 319  
     */   
 320  
    private void version(Contact field, Annotation type) throws Exception {
 321  4
       Label label = support.getLabel(field, type);
 322  
       
 323  4
       if(version != null) {
 324  0
          throw new AttributeException("Multiple version annotations in %s", type);
 325  
       }
 326  4
       version = label;
 327  4
    }
 328  
 
 329  
    /**
 330  
     * This is used to build the <code>Structure</code> that has been
 331  
     * built. The structure will contain all the details required to
 332  
     * serialize and deserialize the type. Once created the structure
 333  
     * is immutable, and can be used to create <code>Section</code>
 334  
     * objects, which contains the element and attribute details.
 335  
     * 
 336  
     * @param type this is the type that represents the schema class
 337  
     * 
 338  
     * @return this returns the structure that has been built
 339  
     */
 340  
    public Structure build(Class type) throws Exception {
 341  2347
       return new Structure(factory, root, version, text, primitive);
 342  
    }
 343  
    
 344  
    /**
 345  
     * This is used to determine if the specified XPath expression
 346  
     * represents an element within the root model. This will return
 347  
     * true if the specified path exists as either an element or
 348  
     * as a valid path to an existing model.
 349  
     * <p>
 350  
     * If the path references a <code>Model</code> then that is an
 351  
     * element only if it is not empty. If the model is empty this
 352  
     * means that it was used in the <code>Order</code> annotation
 353  
     * only and this does not refer to a value XML element.
 354  
     * 
 355  
     * @param path this is the path to search for the element
 356  
     * 
 357  
     * @return this returns true if an element or model exists
 358  
     */
 359  
    private boolean isElement(String path)throws Exception {
 360  87
       Expression target = builder.build(path);
 361  87
       Model model = lookup(target);
 362  
       
 363  87
       if(model != null) {
 364  87
          String name = target.getLast();
 365  87
          int index = target.getIndex();
 366  
          
 367  87
          if(model.isElement(name)) {
 368  74
             return true;
 369  
          }
 370  13
          if(model.isModel(name)) {
 371  13
             Model element = model.lookup(name, index);
 372  
             
 373  13
             if(element.isEmpty()) {
 374  3
                return false;
 375  
             }
 376  10
             return true;
 377  
          }
 378  
       }
 379  0
       return false;
 380  
    }
 381  
    
 382  
    /**
 383  
     * This is used to determine if the specified XPath expression
 384  
     * represents an attribute within the root model. This returns
 385  
     * true if the specified path exists as either an attribute.
 386  
     * 
 387  
     * @param path this is the path to search for the attribute
 388  
     * 
 389  
     * @return this returns true if the attribute exists
 390  
     */
 391  
    private boolean isAttribute(String path) throws Exception {
 392  67
       Expression target = builder.build(path);
 393  67
       Model model = lookup(target);
 394  
       
 395  67
       if(model != null) { 
 396  67
          String name = target.getLast();
 397  
          
 398  67
          if(!target.isPath()) {
 399  28
             return model.isAttribute(path);
 400  
          }
 401  39
          return model.isAttribute(name);
 402  
       }
 403  0
       return false;
 404  
    } 
 405  
    
 406  
    /**
 407  
     * This method is used to look for a <code>Model</code> that
 408  
     * matches the specified expression. If no such model exists
 409  
     * then this will return null. Using an XPath expression allows
 410  
     * a tree like structure to be navigated with ease.
 411  
     * 
 412  
     * @param path an XPath expression used to locate a model
 413  
     * 
 414  
     * @return this returns the model located by the expression
 415  
     */
 416  
    private Model lookup(Expression path) throws Exception {
 417  154
       Expression target = path.getPath(0, 1);
 418  
       
 419  154
       if(path.isPath()) {
 420  87
          return root.lookup(target);
 421  
       }
 422  67
       return root;
 423  
    }   
 424  
 
 425  
    /**
 426  
     * This is used to register a <code>Model</code> for this builder.
 427  
     * Registration of a model creates a tree of models that is used 
 428  
     * to represent an XML structure. Each model can contain elements 
 429  
     * and attributes associated with a type.
 430  
     * 
 431  
     * @param path this is the path of the model to be resolved
 432  
     * 
 433  
     * @return this returns the model that was registered
 434  
     */
 435  
    private Model register(Expression path) throws Exception {   
 436  419
       Model model = root.lookup(path);
 437  
       
 438  419
       if (model != null) {
 439  272
          return model;
 440  
       }      
 441  147
       return create(path);
 442  
    }
 443  
    
 444  
    /**
 445  
     * This is used to register a <code>Model</code> for this builder.
 446  
     * Registration of a model creates a tree of models that is used 
 447  
     * to represent an XML structure. Each model can contain elements 
 448  
     * and attributes associated with a type.
 449  
     * 
 450  
     * @param path this is the path of the model to be resolved
 451  
     * 
 452  
     * @return this returns the model that was registered
 453  
     */
 454  
    private Model create(Expression path) throws Exception {
 455  147
       Model model = root;
 456  
    
 457  261
       while(model != null) {
 458  261
          String prefix = path.getPrefix();
 459  261
          String name = path.getFirst();
 460  261
          int index = path.getIndex();
 461  
 
 462  261
          if(name != null) {
 463  261
             model = model.register(name, prefix, index);
 464  
          }
 465  261
          if(!path.isPath()) {
 466  147
             break;
 467  
          }
 468  114
          path = path.getPath(1);
 469  114
       }
 470  147
       return model;
 471  
    }
 472  
    
 473  
    /**
 474  
     * This is used to commit the structure of the type. This will
 475  
     * build an <code>Instantiator</code> that can be used to create
 476  
     * instances using annotated constructors. If no constructors have
 477  
     * be annotated then instances can use the default constructor.
 478  
     * 
 479  
     * @param type this is the type that this builder creates
 480  
     */
 481  
    public void commit(Class type) throws Exception {
 482  2370
       if(factory == null) {
 483  2370
          factory = resolver.build();
 484  
       }
 485  2364
    }
 486  
    
 487  
    /**
 488  
     * This is used to validate the configuration of the scanned class.
 489  
     * If a <code>Text</code> annotation has been used with elements
 490  
     * then validation will fail and an exception will be thrown. 
 491  
     * 
 492  
     * @param type this is the object type that is being scanned
 493  
     */
 494  
    public void validate(Class type) throws Exception {
 495  2364
       Order order = scanner.getOrder();
 496  
       
 497  2364
       validateUnions(type);
 498  2361
       validateElements(type, order);
 499  2358
       validateAttributes(type, order);
 500  2358
       validateModel(type);
 501  2350
       validateText(type);  
 502  2350
       validateTextList(type);
 503  2347
    }
 504  
    
 505  
    /**
 506  
     * This is used to validate the model to ensure all elements and
 507  
     * attributes are valid. Validation also ensures that any order
 508  
     * specified by an annotated class did not contain invalid XPath
 509  
     * values, or redundant elements and attributes.
 510  
     * 
 511  
     * @param type this is the object type representing the schema
 512  
     */
 513  
    private void validateModel(Class type) throws Exception {
 514  2358
       if(!root.isEmpty()) {
 515  1477
          root.validate(type);
 516  
       }
 517  2350
    }
 518  
 
 519  
    /**
 520  
     * This is used to validate the configuration of the scanned class.
 521  
     * If a <code>Text</code> annotation has been used with elements
 522  
     * then validation will fail and an exception will be thrown. 
 523  
     * 
 524  
     * @param type this is the object type that is being scanned
 525  
     */
 526  
    private void validateText(Class type) throws Exception {
 527  2350
            Label label = root.getText();
 528  
            
 529  2350
       if(label != null) {
 530  114
          if(!label.isTextList()) {
 531  102
             if(!elements.isEmpty()) {
 532  0
                throw new TextException("Elements used with %s in %s", label, type);
 533  
             }
 534  102
             if(root.isComposite()) {
 535  0
                throw new TextException("Paths used with %s in %s", label, type);
 536  
             }
 537  
          }
 538  
       }  else {
 539  2236
          if(scanner.isEmpty()) {
 540  929
             primitive = isEmpty();
 541  
          }
 542  
       }
 543  2350
    }
 544  
    
 545  
    /**
 546  
     * This is used to validate the configuration of the scanned class.
 547  
     * If an <code>ElementListUnion</code> annotation has been used with 
 548  
     * a <code>Text</code> annotation this validates to ensure there are
 549  
     * no other elements declared and no <code>Path</code> annotations 
 550  
     * have been used, which ensures free text can be processed.
 551  
     * 
 552  
     * @param type this is the object type that is being scanned
 553  
     */
 554  
    private void validateTextList(Class type) throws Exception {
 555  2350
       Label label = root.getText();
 556  
       
 557  2350
       if(label != null) {
 558  114
          if(label.isTextList()) {
 559  12
             Object key = label.getKey();
 560  
             
 561  12
             for(Label element : elements) {
 562  27
                Object identity = element.getKey();
 563  
                
 564  27
                if(!identity.equals(key)) {
 565  1
                   throw new TextException("Elements used with %s in %s", label, type);
 566  
                }
 567  26
                Type dependent = element.getDependent();
 568  26
                Class actual = dependent.getType();
 569  
                
 570  26
                if(actual == String.class) {
 571  1
                   throw new TextException("Illegal entry of %s with text annotations on %s in %s", actual, label, type);
 572  
                }
 573  25
             }
 574  10
             if(root.isComposite()) {
 575  1
                throw new TextException("Paths used with %s in %s", label, type);
 576  
             }
 577  
          }
 578  
       } 
 579  2347
    }
 580  
    
 581  
    /**
 582  
     * This is used to validate the unions that have been defined
 583  
     * within the type. Union validation is done by determining if 
 584  
     * the union has consistent inline values. If one annotation in
 585  
     * the union declaration is inline, then all must be inline.
 586  
     * 
 587  
     * @param type this is the type to validate the unions for
 588  
     */
 589  
    private void validateUnions(Class type) throws Exception {
 590  2364
       for(Label label : elements) {
 591  2923
          String[] options = label.getPaths();
 592  2923
          Contact contact = label.getContact();
 593  
          
 594  6838
          for(String option : options) {
 595  3918
             Annotation union = contact.getAnnotation();
 596  3918
             Label other = elements.get(option);
 597  
             
 598  3918
             if(label.isInline() != other.isInline()) {
 599  1
                throw new UnionException("Inline must be consistent in %s for %s", union, contact);
 600  
             }
 601  3917
             if(label.isRequired() != other.isRequired()) {
 602  2
                throw new UnionException("Required must be consistent in %s for %s", union, contact);
 603  
             }
 604  
          }    
 605  2920
       }
 606  2361
    }
 607  
    
 608  
    /**
 609  
     * This is used to validate the configuration of the scanned class.
 610  
     * If an ordered element is specified but does not refer to an
 611  
     * existing element then this will throw an exception.
 612  
     * 
 613  
     * @param type this is the object type that is being scanned
 614  
     * @param order this is the order that is to be validated
 615  
     */
 616  
    private void validateElements(Class type, Order order) throws Exception {
 617  2361
       if(order != null) {
 618  107
          for(String name : order.elements()) {
 619  87
             if(!isElement(name)) {
 620  3
                throw new ElementException("Ordered element '%s' missing for %s", name, type);
 621  
             }
 622  
          }
 623  
       }
 624  2358
    }
 625  
    
 626  
    /**
 627  
     * This is used to validate the configuration of the scanned class.
 628  
     * If an ordered attribute is specified but does not refer to an
 629  
     * existing attribute then this will throw an exception.
 630  
     * 
 631  
     * @param type this is the object type that is being scanned
 632  
     * @param order this is the order that is to be validated
 633  
     */
 634  
    private void validateAttributes(Class type, Order order) throws Exception {
 635  2358
       if(order != null) {
 636  87
          for(String name : order.attributes()) {
 637  67
             if(!isAttribute(name)) {
 638  0
                throw new AttributeException("Ordered attribute '%s' missing in %s", name, type);
 639  
             }
 640  
          }
 641  
       }
 642  2358
    } 
 643  
    
 644  
    /**
 645  
     * This is used to determine if the structure is empty. To check
 646  
     * to see if the structure is empty all models within the tree
 647  
     * must be examined. Also, if there is a text annotation then it
 648  
     * is not considered to be empty.
 649  
     * 
 650  
     * @return true if the structure represents an empty schema
 651  
     */
 652  
    private boolean isEmpty() {
 653  929
       if(text != null) {
 654  0
          return false;
 655  
       }
 656  929
       return root.isEmpty();
 657  
    } 
 658  
 }