1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
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
444
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
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
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
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
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
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
608
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
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 }