1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.commons.betwixt; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 22 import org.apache.commons.betwixt.expression.Expression; 23 24 /** <p><code>ElementDescriptor</code> describes the XML elements 25 * to be created for a bean instance.</p> 26 * 27 * <p> It contains <code>AttributeDescriptor</code>'s for all it's attributes 28 * and <code>ElementDescriptor</code>'s for it's child elements. 29 * 30 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 31 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 32 */ 33 public class ElementDescriptor extends NodeDescriptor { 34 35 /** 36 * Descriptors for attributes this element contains. 37 * <strong>Note:</strong> Constructed lazily on demand from a List. 38 * {@link #getAttributeDescriptor()} should be called rather than accessing this 39 * field directly. 40 */ 41 private AttributeDescriptor[] attributeDescriptors; 42 /** 43 * Descriptors for child elements. 44 * <strong>Note:</strong> Constructed lazily on demand from a List. 45 * {@link #getElementDescriptor()} should be called rather than accessing this 46 * field directly. 47 */ 48 private ElementDescriptor[] elementDescriptors; 49 50 /** 51 * Descriptors for child content. 52 * <strong>Note:</strong> Constructed lazily on demand from a List. 53 * {@link #getContentDescriptor()} should be called rather than accessing this 54 * field directly. 55 */ 56 private Descriptor[] contentDescriptors; 57 58 /** 59 * The List used on construction. It will be GC'd 60 * after initilization and the array is lazily constructed 61 */ 62 private List attributeList; 63 64 /** 65 * The List used on construction. It will be GC'd 66 * after initilization and the array is lazily constructed 67 */ 68 private List elementList; 69 70 /** 71 * The list used o construct array. It will be GC'd after 72 * initialization when the array is lazily constructed. 73 */ 74 private List contentList; 75 76 /** the expression used to evaluate the new context of this node 77 * or null if the same context is to be used */ 78 private Expression contextExpression; 79 80 /** Whether this element refers to a primitive type (or property of a parent object) */ 81 private boolean primitiveType; 82 /** Is this a collective type? */ 83 private boolean isCollectiveType; 84 85 /** 86 * Is this element hollow? 87 * In other words, is this descriptor a place holder indicating the name 88 * and update for a root ElementDescriptor for this type obtained by introspection 89 * TODO: this would probably be better modeled as a separate subclass 90 */ 91 private boolean isHollow = false; 92 93 /** 94 * Whether this collection element can be used 95 * as a collection element. Defaults to true 96 */ 97 private boolean wrapCollectionsInElement = true; 98 99 /** specifies a separate implementation class that should be instantiated 100 * when reading beans 101 * or null if there is no separate implementation */ 102 private Class implementationClass = null; 103 104 /** 105 * Should the bind time type determine the mapping? 106 * (As opposed to the introspection time type.) 107 * Note that this attribute is write once, read many (WORM). 108 */ 109 private Boolean useBindTimeTypeForMapping = null; 110 111 /** 112 * Constructs an <code>ElementDescriptor</code> that refers to a primitive type. 113 */ 114 public ElementDescriptor() { 115 } 116 117 /** 118 * Base constructor. 119 * @param primitiveType if true, this element refers to a primitive type 120 * @deprecated 0.6 PrimitiveType property has been removed 121 */ 122 public ElementDescriptor(boolean primitiveType) { 123 this.primitiveType = primitiveType; 124 } 125 126 /** 127 * Creates a ElementDescriptor with no namespace URI or prefix. 128 * 129 * @param localName the (xml) local name of this node. 130 * This will be used to set both qualified and local name for this name. 131 */ 132 public ElementDescriptor(String localName) { 133 super( localName ); 134 } 135 136 137 138 /** 139 * Creates a <code>ElementDescriptor</code> with namespace URI and qualified name 140 * @param localName the (xml) local name of this node 141 * @param qualifiedName the (xml) qualified name of this node 142 * @param uri the (xml) namespace prefix of this node 143 */ 144 public ElementDescriptor(String localName, String qualifiedName, String uri) { 145 super(localName, qualifiedName, uri); 146 } 147 148 /** 149 * Returns true if this element has child <code>ElementDescriptors</code> 150 * @return true if this element has child elements 151 * @see #getElementDescriptors 152 */ 153 public boolean hasChildren() { 154 return getElementDescriptors().length > 0; 155 } 156 157 /** 158 * Returns true if this element has <code>AttributeDescriptors</code> 159 * @return true if this element has attributes 160 * @see #getAttributeDescriptors 161 */ 162 public boolean hasAttributes() { 163 return getAttributeDescriptors().length > 0; 164 } 165 166 /** 167 * Returns true if this element has child content. 168 * @return true if this element has either child mixed content or child elements 169 * @see #getContentDescriptors 170 * @since 0.5 171 */ 172 public boolean hasContent() { 173 return getContentDescriptors().length > 0; 174 } 175 176 /** 177 * <p>Is this a simple element?</p> 178 * <p> 179 * A simple element is one without child elements or attributes. 180 * This corresponds to the simple type concept used in XML Schema. 181 * TODO: need to consider whether it's sufficient to calculate 182 * which are simple types (and so don't get IDs assigned etc). 183 * </p> 184 * @return true if it is a <code>SimpleType</code> element 185 */ 186 public boolean isSimple() { 187 return !(hasAttributes()) && !(hasChildren()); 188 } 189 190 191 /** 192 * Sets whether <code>Collection</code> bean properties should wrap items in a parent element. 193 * In other words, should the mapping for bean properties which are <code>Collection</code>s 194 * enclosed the item elements within a parent element. 195 * Normally only used when this describes a collection bean property. 196 * 197 * @param wrapCollectionsInElement true if the elements for the items in the collection 198 * should be contained in a parent element 199 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should 200 * be done during introspection 201 */ 202 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) { 203 this.wrapCollectionsInElement = wrapCollectionsInElement; 204 } 205 206 /** 207 * Returns true if collective bean properties should wrap the items in a parent element. 208 * In other words, should the mapping for bean properties which are <code>Collection</code>s 209 * enclosed the item elements within a parent element. 210 * Normally only used when this describes a collection bean property. 211 * 212 * @return true if the elements for the items in the collection should be contained 213 * in a parent element 214 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should 215 * be done during introspection 216 */ 217 public boolean isWrapCollectionsInElement() { 218 return this.wrapCollectionsInElement; 219 } 220 221 /** 222 * Adds an attribute to the element this <code>ElementDescriptor</code> describes 223 * @param descriptor the <code>AttributeDescriptor</code> that will be added to the 224 * attributes associated with element this <code>ElementDescriptor</code> describes 225 */ 226 public void addAttributeDescriptor(AttributeDescriptor descriptor) { 227 if ( attributeList == null ) { 228 attributeList = new ArrayList(); 229 } 230 getAttributeList().add( descriptor ); 231 attributeDescriptors = null; 232 } 233 234 235 /** 236 * Removes an attribute descriptor from this element descriptor. 237 * @param descriptor the <code>AttributeDescriptor</code> to be removed, not null 238 * @since 0.8 239 */ 240 public void removeAttributeDescriptor(AttributeDescriptor descriptor) { 241 getAttributeList().remove(descriptor); 242 } 243 244 /** 245 * Returns the attribute descriptors for this element 246 * 247 * @return descriptors for the attributes of the element that this 248 * <code>ElementDescriptor</code> describes 249 */ 250 public AttributeDescriptor[] getAttributeDescriptors() { 251 if ( attributeDescriptors == null ) { 252 if ( attributeList == null ) { 253 attributeDescriptors = new AttributeDescriptor[0]; 254 } else { 255 attributeDescriptors = new AttributeDescriptor[ attributeList.size() ]; 256 attributeList.toArray( attributeDescriptors ); 257 258 // allow GC of List when initialized 259 attributeList = null; 260 } 261 } 262 return attributeDescriptors; 263 } 264 265 /** 266 * Returns an attribute descriptor with a given name or null. 267 * 268 * @param name to search for; will be checked against the attributes' qualified name. 269 * @return <code>AttributeDescriptor</code> with the given name, 270 * or null if no descriptor has that name 271 * @since 0.8 272 */ 273 public AttributeDescriptor getAttributeDescriptor(final String name) { 274 for (int i = 0, size = attributeDescriptors.length; i < size; i++) { 275 AttributeDescriptor descr = attributeDescriptors[i]; 276 if (descr.getQualifiedName().equals(name)) { 277 return descr; 278 } 279 } 280 281 return null; 282 } 283 284 /** 285 * Sets the <code>AttributesDescriptors</code> for this element. 286 * This sets descriptors for the attributes of the element describe by the 287 * <code>ElementDescriptor</code>. 288 * 289 * @param attributeDescriptors the <code>AttributeDescriptor</code> describe the attributes 290 * of the element described by this <code>ElementDescriptor</code> 291 */ 292 public void setAttributeDescriptors(AttributeDescriptor[] attributeDescriptors) { 293 this.attributeDescriptors = attributeDescriptors; 294 this.attributeList = null; 295 } 296 297 /** 298 * Adds a descriptor for a child element. 299 * 300 * @param descriptor the <code>ElementDescriptor</code> describing the child element to add 301 */ 302 public void addElementDescriptor(ElementDescriptor descriptor) { 303 if ( elementList == null ) { 304 elementList = new ArrayList(); 305 } 306 getElementList().add( descriptor ); 307 elementDescriptors = null; 308 addContentDescriptor( descriptor ); 309 } 310 311 /** 312 * Removes an element descriptor from this element descriptor. 313 * @param descriptor the <code>ElementDescriptor</code> that will be removed. 314 * @since 0.8 315 */ 316 public void removeElementDescriptor(ElementDescriptor descriptor) { 317 getElementList().remove(descriptor); 318 getContentList().remove(descriptor); 319 } 320 321 /** 322 * Returns descriptors for the child elements of the element this describes. 323 * @return the <code>ElementDescriptor</code> describing the child elements 324 * of the element that this <code>ElementDescriptor</code> describes 325 */ 326 public ElementDescriptor[] getElementDescriptors() { 327 if ( elementDescriptors == null ) { 328 if ( elementList == null ) { 329 elementDescriptors = new ElementDescriptor[0]; 330 } else { 331 elementDescriptors = new ElementDescriptor[ elementList.size() ]; 332 elementList.toArray( elementDescriptors ); 333 334 // allow GC of List when initialized 335 elementList = null; 336 } 337 } 338 return elementDescriptors; 339 } 340 341 /** 342 * Gets a child ElementDescriptor matching the given name if one exists. 343 * Note that (so long as there are no better matches), a null name 344 * acts as a wildcard. In other words, an 345 * <code>ElementDescriptor</code> the first descriptor 346 * with a null name will match any name 347 * passed in, unless some other matches the name exactly. 348 * 349 * @param name the localname to be matched, not null 350 * @return the child ElementDescriptor with the given name if one exists, 351 * otherwise null 352 */ 353 public ElementDescriptor getElementDescriptor(String name) { 354 355 ElementDescriptor elementDescriptor = null; 356 ElementDescriptor descriptorWithNullName = null; 357 ElementDescriptor firstPolymorphic = null; 358 ElementDescriptor[] elementDescriptors = getElementDescriptors(); 359 for (int i=0, size=elementDescriptors.length; i<size; i++) { 360 if (firstPolymorphic == null && elementDescriptors[i].isPolymorphic()) { 361 firstPolymorphic = elementDescriptors[i]; 362 } 363 String elementName = elementDescriptors[i].getQualifiedName(); 364 if (name.equals(elementName)) { 365 elementDescriptor = elementDescriptors[i]; 366 break; 367 } 368 if (descriptorWithNullName == null && elementName == null) { 369 descriptorWithNullName = elementDescriptors[i]; 370 } 371 } 372 if (elementDescriptor == null) { 373 elementDescriptor = firstPolymorphic; 374 } 375 if (elementDescriptor == null) { 376 elementDescriptor = descriptorWithNullName; 377 } 378 return elementDescriptor; 379 } 380 381 382 /** 383 * Sets the descriptors for the child element of the element this describes. 384 * Also sets the child content descriptors for this element 385 * 386 * @param elementDescriptors the <code>ElementDescriptor</code>s of the element 387 * that this describes 388 */ 389 public void setElementDescriptors(ElementDescriptor[] elementDescriptors) { 390 this.elementDescriptors = elementDescriptors; 391 this.elementList = null; 392 setContentDescriptors( elementDescriptors ); 393 } 394 395 /** 396 * Adds a descriptor for child content. 397 * 398 * @param descriptor the <code>Descriptor</code> describing the child content to add 399 * @since 0.5 400 */ 401 public void addContentDescriptor(Descriptor descriptor) { 402 if ( contentList == null ) { 403 contentList = new ArrayList(); 404 } 405 getContentList().add( descriptor ); 406 contentDescriptors = null; 407 } 408 409 /** 410 * Returns descriptors for the child content of the element this describes. 411 * @return the <code>Descriptor</code> describing the child elements 412 * of the element that this <code>ElementDescriptor</code> describes 413 * @since 0.5 414 */ 415 public Descriptor[] getContentDescriptors() { 416 if ( contentDescriptors == null ) { 417 if ( contentList == null ) { 418 contentDescriptors = new Descriptor[0]; 419 } else { 420 contentDescriptors = new Descriptor[ contentList.size() ]; 421 contentList.toArray( contentDescriptors ); 422 423 // allow GC of List when initialized 424 contentList = null; 425 } 426 } 427 return contentDescriptors; 428 } 429 430 /** 431 * <p>Gets the primary descriptor for body text of this element. 432 * Betwixt collects all body text for any element together. 433 * This makes it rounds tripping difficult for beans that write more than one 434 * mixed content property. 435 * </p><p> 436 * The algorithm used in the default implementation is that the first TextDescriptor 437 * found amongst the descriptors is returned. 438 * 439 * @return the primary descriptor or null if this element has no mixed body content 440 * @since 0.5 441 */ 442 public TextDescriptor getPrimaryBodyTextDescriptor() { 443 // todo: this probably isn't the most efficent algorithm 444 // but should avoid premature optimization 445 Descriptor[] descriptors = getContentDescriptors(); 446 for (int i=0, size=descriptors.length; i<size; i++) { 447 if (descriptors[i] instanceof TextDescriptor) { 448 return (TextDescriptor) descriptors[i]; 449 } 450 } 451 // if we haven't found anything, return null. 452 return null; 453 } 454 455 /** 456 * Sets the descriptors for the child content of the element this describes. 457 * @param contentDescriptors the <code>Descriptor</code>s of the element 458 * that this describes 459 * @since 0.5 460 */ 461 public void setContentDescriptors(Descriptor[] contentDescriptors) { 462 this.contentDescriptors = contentDescriptors; 463 this.contentList = null; 464 } 465 466 /** 467 * Returns the expression used to evaluate the new context of this element. 468 * @return the expression used to evaluate the new context of this element 469 */ 470 public Expression getContextExpression() { 471 return contextExpression; 472 } 473 474 /** 475 * Sets the expression used to evaluate the new context of this element 476 * @param contextExpression the expression used to evaluate the new context of this element 477 */ 478 public void setContextExpression(Expression contextExpression) { 479 this.contextExpression = contextExpression; 480 } 481 482 /** 483 * Returns true if this element refers to a primitive type property 484 * @return whether this element refers to a primitive type (or property of a parent object) 485 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should 486 * be done during introspection 487 */ 488 public boolean isPrimitiveType() { 489 return primitiveType; 490 } 491 492 /** 493 * Sets whether this element refers to a primitive type (or property of a parent object) 494 * @param primitiveType true if this element refers to a primitive type 495 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should 496 * be done during introspection 497 */ 498 public void setPrimitiveType(boolean primitiveType) { 499 this.primitiveType = primitiveType; 500 } 501 502 // Implementation methods 503 //------------------------------------------------------------------------- 504 505 /** 506 * Lazily creates the mutable List. 507 * This nullifies the attributeDescriptors array so that 508 * as items are added to the list the Array is ignored until it is 509 * explicitly asked for. 510 * 511 * @return list of <code>AttributeDescriptors</code>'s describing the attributes 512 * of the element that this <code>ElementDescriptor</code> describes 513 */ 514 protected List getAttributeList() { 515 if ( attributeList == null ) { 516 if ( attributeDescriptors != null ) { 517 int size = attributeDescriptors.length; 518 attributeList = new ArrayList( size ); 519 for ( int i = 0; i < size; i++ ) { 520 attributeList.add( attributeDescriptors[i] ); 521 } 522 // force lazy recreation later 523 attributeDescriptors = null; 524 } else { 525 attributeList = new ArrayList(); 526 } 527 } 528 return attributeList; 529 } 530 531 /** 532 * Lazily creates the mutable List of child elements. 533 * This nullifies the elementDescriptors array so that 534 * as items are added to the list the Array is ignored until it is 535 * explicitly asked for. 536 * 537 * @return list of <code>ElementDescriptor</code>'s describe the child elements of 538 * the element that this <code>ElementDescriptor</code> describes 539 */ 540 protected List getElementList() { 541 if ( elementList == null ) { 542 if ( elementDescriptors != null ) { 543 int size = elementDescriptors.length; 544 elementList = new ArrayList( size ); 545 for ( int i = 0; i < size; i++ ) { 546 elementList.add( elementDescriptors[i] ); 547 } 548 // force lazy recreation later 549 elementDescriptors = null; 550 } else { 551 elementList = new ArrayList(); 552 } 553 } 554 return elementList; 555 } 556 557 /** 558 * Lazily creates the mutable List of child content descriptors. 559 * This nullifies the contentDescriptors array so that 560 * as items are added to the list the Array is ignored until it is 561 * explicitly asked for. 562 * 563 * @return list of <code>Descriptor</code>'s describe the child content of 564 * the element that this <code>Descriptor</code> describes 565 * @since 0.5 566 */ 567 protected List getContentList() { 568 if ( contentList == null ) { 569 if ( contentDescriptors != null ) { 570 int size = contentDescriptors.length; 571 contentList = new ArrayList( size ); 572 for ( int i = 0; i < size; i++ ) { 573 contentList.add( contentDescriptors[i] ); 574 } 575 // force lazy recreation later 576 contentDescriptors = null; 577 } else { 578 contentList = new ArrayList(); 579 } 580 } 581 return contentList; 582 } 583 584 /** 585 * Gets the class which should be used for instantiation. 586 * @return the class which should be used for instantiation of beans 587 * mapped from this element, null if the standard class should be used 588 */ 589 public Class getImplementationClass() { 590 return implementationClass; 591 } 592 593 /** 594 * Sets the class which should be used for instantiation. 595 * @param implementationClass the class which should be used for instantiation 596 * or null to use the mapped type 597 * @since 0.5 598 */ 599 public void setImplementationClass(Class implementationClass) { 600 this.implementationClass = implementationClass; 601 } 602 603 /** 604 * Does this describe a collective? 605 */ 606 public boolean isCollective() { 607 // TODO is this implementation correct? 608 // maybe this method is unnecessary 609 return isCollectiveType; 610 } 611 612 /** 613 * Sets whether the element described is a collective. 614 * @since 0.7 615 * @param isCollectiveType 616 */ 617 public void setCollective(boolean isCollectiveType) { 618 this.isCollectiveType = isCollectiveType; 619 } 620 621 /** 622 * Finds the parent of the given descriptor. 623 * @param elementDescriptor <code>ElementDescriptor</code> 624 * @return <code>ElementDescriptor</code>, not null 625 */ 626 public ElementDescriptor findParent(ElementDescriptor elementDescriptor) { 627 //TODO: is this really a good design? 628 ElementDescriptor result = null; 629 ElementDescriptor[] elementDescriptors = getElementDescriptors(); 630 for (int i=0, size=elementDescriptors.length; i<size; i++) { 631 if (elementDescriptors[i].equals(elementDescriptor)) { 632 result = this; 633 break; 634 } 635 else 636 { 637 result = elementDescriptors[i].findParent(elementDescriptor); 638 if (result != null) { 639 break; 640 } 641 } 642 } 643 return result; 644 } 645 646 /** 647 * Returns something useful for logging. 648 * 649 * @return a string useful for logging 650 */ 651 public String toString() { 652 return 653 "ElementDescriptor[qname=" + getQualifiedName() + ",pname=" + getPropertyName() 654 + ",class=" + getPropertyType() + ",singular=" + getSingularPropertyType() 655 + ",updater=" + getUpdater() + ",wrap=" + isWrapCollectionsInElement() + "]"; 656 } 657 658 /** 659 * <p>Is this decriptor hollow?</p> 660 * <p> 661 * A hollow descriptor is one which gives only the class that the subgraph 662 * is mapped to rather than describing the entire subgraph. 663 * A new <code>XMLBeanInfo</code> should be introspected 664 * and that used to describe the subgraph. 665 * A hollow descriptor should not have any child descriptors. 666 * TODO: consider whether a subclass would be better 667 * </p> 668 * @return true if this is hollow 669 */ 670 public boolean isHollow() { 671 return isHollow; 672 } 673 674 /** 675 * Sets whether this descriptor is hollow. 676 * A hollow descriptor is one which gives only the class that the subgraph 677 * is mapped to rather than describing the entire subgraph. 678 * A new <code>XMLBeanInfo</code> should be introspected 679 * and that used to describe the subgraph. 680 * A hollow descriptor should not have any child descriptors. 681 * TODO: consider whether a subclass would be better 682 * @param isHollow true if this is hollow 683 */ 684 public void setHollow(boolean isHollow) { 685 this.isHollow = isHollow; 686 } 687 688 /** 689 * <p>Is the bind time type to be used to determine the mapping?</p> 690 * <p> 691 * The mapping for an object property value can either be the 692 * introspection time type (based on the logical type of the property) 693 * or the bind time type (based on the type of the actual instance). 694 * </p> 695 * @since 0.7 696 * @return true if the bind time type is to be used to determine the mapping, 697 * false if the introspection time type is to be used 698 */ 699 public boolean isUseBindTimeTypeForMapping() { 700 boolean result = true; 701 if ( this.useBindTimeTypeForMapping != null ) { 702 result = this.useBindTimeTypeForMapping.booleanValue(); 703 } 704 return result; 705 } 706 707 /** 708 * <p>Sets whether the bind time type to be used to determine the mapping. 709 * The mapping for an object property value can either be the 710 * introspection time type (based on the logical type of the property) 711 * or the bind time type (based on the type of the actual instance). 712 * </p><p> 713 * <strong>Note:</strong> this property is write once, read many. 714 * So, the first time that this method is called the value will be set 715 * but subsequent calls will be ignored. 716 * </p> 717 * @since 0.7 718 * @param useBindTimeTypeForMapping true if the bind time type is to be used to 719 * determine the mapping, false if the introspection time type is to be used 720 */ 721 public void setUseBindTimeTypeForMapping(boolean useBindTimeTypeForMapping) { 722 if ( this.useBindTimeTypeForMapping == null ) { 723 this.useBindTimeTypeForMapping = new Boolean(useBindTimeTypeForMapping); 724 } 725 } 726 727 /** 728 * <p>Is this a polymorphic element?</p> 729 * <p> 730 * A polymorphic element's name is not fixed at 731 * introspection time and it's resolution is postponed to bind time. 732 * </p> 733 * @since 0.7 734 * @return true if {@link #getQualifiedName} is null, 735 * false otherwise 736 */ 737 public boolean isPolymorphic() { 738 return (getQualifiedName() == null); 739 } 740 }