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