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.digester.XMLIntrospectorHelper;
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 /***
83 * Is this element hollow?
84 * In other words, is this descriptor a place holder indicating the name
85 * and update for a root ElementDescriptor for this type obtained by introspection
86 * TODO: this would probably be better modeled as a separate subclass
87 */
88 private boolean isHollow = false;
89
90 /***
91 * Whether this collection element can be used
92 * as a collection element. Defaults to true
93 */
94 private boolean wrapCollectionsInElement = true;
95
96 /*** specifies a separate implementation class that should be instantiated
97 * when reading beans
98 * or null if there is no separate implementation */
99 private Class implementationClass = null;
100
101 /***
102 * Constructs an <code>ElementDescriptor</code> that refers to a primitive type.
103 */
104 public ElementDescriptor() {
105 }
106
107 /***
108 * Base constructor.
109 * @param primitiveType if true, this element refers to a primitive type
110 * @deprecated 0.6 PrimitiveType property has been removed
111 */
112 public ElementDescriptor(boolean primitiveType) {
113 this.primitiveType = primitiveType;
114 }
115
116 /***
117 * Creates a ElementDescriptor with no namespace URI or prefix.
118 *
119 * @param localName the (xml) local name of this node.
120 * This will be used to set both qualified and local name for this name.
121 */
122 public ElementDescriptor(String localName) {
123 super( localName );
124 }
125
126
127
128 /***
129 * Creates a <code>ElementDescriptor</code> with namespace URI and qualified name
130 * @param localName the (xml) local name of this node
131 * @param qualifiedName the (xml) qualified name of this node
132 * @param uri the (xml) namespace prefix of this node
133 */
134 public ElementDescriptor(String localName, String qualifiedName, String uri) {
135 super(localName, qualifiedName, uri);
136 }
137
138 /***
139 * Returns true if this element has child <code>ElementDescriptors</code>
140 * @return true if this element has child elements
141 * @see #getElementDescriptors
142 */
143 public boolean hasChildren() {
144 return getElementDescriptors().length > 0;
145 }
146
147 /***
148 * Returns true if this element has <code>AttributeDescriptors</code>
149 * @return true if this element has attributes
150 * @see #getAttributeDescriptors
151 */
152 public boolean hasAttributes() {
153 return getAttributeDescriptors().length > 0;
154 }
155
156 /***
157 * Returns true if this element has child content.
158 * @return true if this element has either child mixed content or child elements
159 * @see #getContentDescriptors
160 * @since 0.5
161 */
162 public boolean hasContent() {
163 return getContentDescriptors().length > 0;
164 }
165
166 /***
167 * Is this a simple element?
168 * A simple element is one without child elements or attributes.
169 * This corresponds to the simple type concept used in XML Schema.
170 * TODO: need to consider whether it's sufficient to calculate
171 * which are simple types (and so don't get IDs assigned etc)
172 * @return true if it is a <code>SimpleType</code> element
173 */
174 public boolean isSimple() {
175 return !(hasAttributes()) && !(hasChildren());
176 }
177
178
179 /***
180 * Sets whether <code>Collection</code> bean properties should wrap items in a parent element.
181 * In other words, should the mapping for bean properties which are <code>Collection</code>s
182 * enclosed the item elements within a parent element.
183 * Normally only used when this describes a collection bean property.
184 *
185 * @param wrapCollectionsInElement true if the elements for the items in the collection
186 * should be contained in a parent element
187 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
188 * be done during introspection
189 */
190 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
191 this.wrapCollectionsInElement = wrapCollectionsInElement;
192 }
193
194 /***
195 * Returns true if collective bean properties should wrap the items in a parent element.
196 * In other words, should the mapping for bean properties which are <code>Collection</code>s
197 * enclosed the item elements within a parent element.
198 * Normally only used when this describes a collection bean property.
199 *
200 * @return true if the elements for the items in the collection should be contained
201 * in a parent element
202 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
203 * be done during introspection
204 */
205 public boolean isWrapCollectionsInElement() {
206 return this.wrapCollectionsInElement;
207 }
208
209 /***
210 * Adds an attribute to the element this <code>ElementDescriptor</code> describes
211 * @param descriptor the <code>AttributeDescriptor</code> that will be added to the
212 * attributes associated with element this <code>ElementDescriptor</code> describes
213 */
214 public void addAttributeDescriptor(AttributeDescriptor descriptor) {
215 if ( attributeList == null ) {
216 attributeList = new ArrayList();
217 }
218 getAttributeList().add( descriptor );
219 attributeDescriptors = null;
220 }
221
222
223 /***
224 * Returns the attribute descriptors for this element
225 *
226 * @return descriptors for the attributes of the element that this
227 * <code>ElementDescriptor</code> describes
228 */
229 public AttributeDescriptor[] getAttributeDescriptors() {
230 if ( attributeDescriptors == null ) {
231 if ( attributeList == null ) {
232 attributeDescriptors = new AttributeDescriptor[0];
233 } else {
234 attributeDescriptors = new AttributeDescriptor[ attributeList.size() ];
235 attributeList.toArray( attributeDescriptors );
236
237
238 attributeList = null;
239 }
240 }
241 return attributeDescriptors;
242 }
243
244 /***
245 * Sets the <code>AttributesDescriptors</code> for this element.
246 * This sets descriptors for the attributes of the element describe by the
247 * <code>ElementDescriptor</code>.
248 *
249 * @param attributeDescriptors the <code>AttributeDescriptor</code> describe the attributes
250 * of the element described by this <code>ElementDescriptor</code>
251 */
252 public void setAttributeDescriptors(AttributeDescriptor[] attributeDescriptors) {
253 this.attributeDescriptors = attributeDescriptors;
254 this.attributeList = null;
255 }
256
257 /***
258 * Adds a descriptor for a child element.
259 *
260 * @param descriptor the <code>ElementDescriptor</code> describing the child element to add
261 */
262 public void addElementDescriptor(ElementDescriptor descriptor) {
263 if ( elementList == null ) {
264 elementList = new ArrayList();
265 }
266 getElementList().add( descriptor );
267 elementDescriptors = null;
268 addContentDescriptor( descriptor );
269 }
270
271 /***
272 * Returns descriptors for the child elements of the element this describes.
273 * @return the <code>ElementDescriptor</code> describing the child elements
274 * of the element that this <code>ElementDescriptor</code> describes
275 */
276 public ElementDescriptor[] getElementDescriptors() {
277 if ( elementDescriptors == null ) {
278 if ( elementList == null ) {
279 elementDescriptors = new ElementDescriptor[0];
280 } else {
281 elementDescriptors = new ElementDescriptor[ elementList.size() ];
282 elementList.toArray( elementDescriptors );
283
284
285 elementList = null;
286 }
287 }
288 return elementDescriptors;
289 }
290
291 /***
292 * Gets a child ElementDescriptor matching the given name if one exists.
293 * Note that (so long as there are no better matches), a null name
294 * acts as a wildcard. In other words, an
295 * <code>ElementDescriptor</code> the first descriptor
296 * with a null name will match any name
297 * passed in, unless some other matches the name exactly.
298 *
299 * @param name the localname to be matched, not null
300 * @returns the child ElementDescriptor with the given name if one exists,
301 * otherwise null
302 */
303 public ElementDescriptor getElementDescriptor(String name) {
304
305 ElementDescriptor elementDescriptor = null;
306 ElementDescriptor descriptorWithNullName = null;
307 ElementDescriptor[] elementDescriptors = getElementDescriptors();
308 for (int i=0, size=elementDescriptors.length; i<size; i++) {
309 String elementName = elementDescriptors[i].getQualifiedName();
310 if (name.equals(elementName)) {
311 elementDescriptor = elementDescriptors[i];
312 break;
313 }
314 if (descriptorWithNullName == null && elementName == null) {
315 descriptorWithNullName = elementDescriptors[i];
316 }
317 }
318 if (elementDescriptor == null) {
319 elementDescriptor = descriptorWithNullName;
320 }
321 return elementDescriptor;
322 }
323
324
325 /***
326 * Sets the descriptors for the child element of the element this describes.
327 * Also sets the child content descriptors for this element
328 *
329 * @param elementDescriptors the <code>ElementDescriptor</code>s of the element
330 * that this describes
331 */
332 public void setElementDescriptors(ElementDescriptor[] elementDescriptors) {
333 this.elementDescriptors = elementDescriptors;
334 this.elementList = null;
335 setContentDescriptors( elementDescriptors );
336 }
337
338 /***
339 * Adds a descriptor for child content.
340 *
341 * @param descriptor the <code>Descriptor</code> describing the child content to add
342 * @since 0.5
343 */
344 public void addContentDescriptor(Descriptor descriptor) {
345 if ( contentList == null ) {
346 contentList = new ArrayList();
347 }
348 getContentList().add( descriptor );
349 contentDescriptors = null;
350 }
351
352 /***
353 * Returns descriptors for the child content of the element this describes.
354 * @return the <code>Descriptor</code> describing the child elements
355 * of the element that this <code>ElementDescriptor</code> describes
356 * @since 0.5
357 */
358 public Descriptor[] getContentDescriptors() {
359 if ( contentDescriptors == null ) {
360 if ( contentList == null ) {
361 contentDescriptors = new Descriptor[0];
362 } else {
363 contentDescriptors = new Descriptor[ contentList.size() ];
364 contentList.toArray( contentDescriptors );
365
366
367 contentList = null;
368 }
369 }
370 return contentDescriptors;
371 }
372
373 /***
374 * <p>Gets the primary descriptor for body text of this element.
375 * Betwixt collects all body text for any element together.
376 * This makes it rounds tripping difficult for beans that write more than one
377 * mixed content property.
378 * </p><p>
379 * The algorithm used in the default implementation is that the first TextDescriptor
380 * found amongst the descriptors is returned.
381 *
382 * @return the primary descriptor or null if this element has no mixed body content
383 * @since 0.5
384 */
385 public TextDescriptor getPrimaryBodyTextDescriptor() {
386
387
388 Descriptor[] descriptors = getContentDescriptors();
389 for (int i=0, size=descriptors.length; i<size; i++) {
390 if (descriptors[i] instanceof TextDescriptor) {
391 return (TextDescriptor) descriptors[i];
392 }
393 }
394
395 return null;
396 }
397
398 /***
399 * Sets the descriptors for the child content of the element this describes.
400 * @param contentDescriptors the <code>Descriptor</code>s of the element
401 * that this describes
402 * @since 0.5
403 */
404 public void setContentDescriptors(Descriptor[] contentDescriptors) {
405 this.contentDescriptors = contentDescriptors;
406 this.contentList = null;
407 }
408
409 /***
410 * Returns the expression used to evaluate the new context of this element.
411 * @return the expression used to evaluate the new context of this element
412 */
413 public Expression getContextExpression() {
414 return contextExpression;
415 }
416
417 /***
418 * Sets the expression used to evaluate the new context of this element
419 * @param contextExpression the expression used to evaluate the new context of this element
420 */
421 public void setContextExpression(Expression contextExpression) {
422 this.contextExpression = contextExpression;
423 }
424
425 /***
426 * Returns true if this element refers to a primitive type property
427 * @return whether this element refers to a primitive type (or property of a parent object)
428 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
429 * be done during introspection
430 */
431 public boolean isPrimitiveType() {
432 return primitiveType;
433 }
434
435 /***
436 * Sets whether this element refers to a primitive type (or property of a parent object)
437 * @param primitiveType true if this element refers to a primitive type
438 * @deprecated 0.6 moved to a declarative style of descriptors where the alrogithmic should
439 * be done during introspection
440 */
441 public void setPrimitiveType(boolean primitiveType) {
442 this.primitiveType = primitiveType;
443 }
444
445
446
447
448 /***
449 * Lazily creates the mutable List.
450 * This nullifies the attributeDescriptors array so that
451 * as items are added to the list the Array is ignored until it is
452 * explicitly asked for.
453 *
454 * @return list of <code>AttributeDescriptors</code>'s describing the attributes
455 * of the element that this <code>ElementDescriptor</code> describes
456 */
457 protected List getAttributeList() {
458 if ( attributeList == null ) {
459 if ( attributeDescriptors != null ) {
460 int size = attributeDescriptors.length;
461 attributeList = new ArrayList( size );
462 for ( int i = 0; i < size; i++ ) {
463 attributeList.add( attributeDescriptors[i] );
464 }
465
466 attributeDescriptors = null;
467 } else {
468 attributeList = new ArrayList();
469 }
470 }
471 return attributeList;
472 }
473
474 /***
475 * Lazily creates the mutable List of child elements.
476 * This nullifies the elementDescriptors array so that
477 * as items are added to the list the Array is ignored until it is
478 * explicitly asked for.
479 *
480 * @return list of <code>ElementDescriptor</code>'s describe the child elements of
481 * the element that this <code>ElementDescriptor</code> describes
482 */
483 protected List getElementList() {
484 if ( elementList == null ) {
485 if ( elementDescriptors != null ) {
486 int size = elementDescriptors.length;
487 elementList = new ArrayList( size );
488 for ( int i = 0; i < size; i++ ) {
489 elementList.add( elementDescriptors[i] );
490 }
491
492 elementDescriptors = null;
493 } else {
494 elementList = new ArrayList();
495 }
496 }
497 return elementList;
498 }
499
500 /***
501 * Lazily creates the mutable List of child content descriptors.
502 * This nullifies the contentDescriptors array so that
503 * as items are added to the list the Array is ignored until it is
504 * explicitly asked for.
505 *
506 * @return list of <code>Descriptor</code>'s describe the child content of
507 * the element that this <code>Descriptor</code> describes
508 * @since 0.5
509 */
510 protected List getContentList() {
511 if ( contentList == null ) {
512 if ( contentDescriptors != null ) {
513 int size = contentDescriptors.length;
514 contentList = new ArrayList( size );
515 for ( int i = 0; i < size; i++ ) {
516 contentList.add( contentDescriptors[i] );
517 }
518
519 contentDescriptors = null;
520 } else {
521 contentList = new ArrayList();
522 }
523 }
524 return contentList;
525 }
526
527 /***
528 * Gets the class which should be used for instantiation.
529 * @return the class which should be used for instantiation of beans
530 * mapped from this element, null if the standard class should be used
531 */
532 public Class getImplementationClass() {
533 return implementationClass;
534 }
535
536 /***
537 * Sets the class which should be used for instantiation.
538 * @param implementationClass the class which should be used for instantiation
539 * or null to use the mapped type
540 * @since 0.5
541 */
542 public void setImplementationClass(Class implementationClass) {
543 this.implementationClass = implementationClass;
544 }
545
546 /***
547 * TODO is this implementation correct?
548 * maybe this method is unnecessary
549 */
550 public boolean isCollective() {
551 boolean result = false;
552 Class type = getPropertyType();
553 if (type != null) {
554 result = XMLIntrospectorHelper.isLoopType(type);
555 }
556 return result;
557 }
558
559 /***
560 * @todo is this really a good design?
561 */
562 public ElementDescriptor findParent(ElementDescriptor elementDescriptor) {
563 ElementDescriptor result = null;
564 ElementDescriptor[] elementDescriptors = getElementDescriptors();
565 for (int i=0, size=elementDescriptors.length; i<size; i++) {
566 if (elementDescriptors[i].equals(elementDescriptor)) {
567 result = this;
568 break;
569 }
570 else
571 {
572 result = elementDescriptors[i].findParent(elementDescriptor);
573 if (result != null) {
574 break;
575 }
576 }
577 }
578 return result;
579 }
580
581 /***
582 * Returns something useful for logging.
583 *
584 * @return a string useful for logging
585 */
586 public String toString() {
587 return
588 "ElementDescriptor[qname=" + getQualifiedName() + ",pname=" + getPropertyName()
589 + ",class=" + getPropertyType() + ",singular=" + getSingularPropertyType()
590 + ",updater=" + getUpdater() + ",wrap=" + isWrapCollectionsInElement() + "]";
591 }
592
593 /***
594 * Is this decriptor hollow?
595 * A hollow descriptor is one which gives only the class that the subgraph
596 * is mapped to rather than describing the entire subgraph.
597 * A new <code>XMLBeanInfo</code> should be introspected
598 * and that used to describe the subgraph.
599 * A hollow descriptor should not have any child descriptors.
600 * TODO: consider whether a subclass would be better
601 * @return true if this is hollow
602 */
603 public boolean isHollow() {
604 return isHollow;
605 }
606
607 /***
608 * Sets whether this descriptor is hollow.
609 * A hollow descriptor is one which gives only the class that the subgraph
610 * is mapped to rather than describing the entire subgraph.
611 * A new <code>XMLBeanInfo</code> should be introspected
612 * and that used to describe the subgraph.
613 * A hollow descriptor should not have any child descriptors.
614 * TODO: consider whether a subclass would be better
615 * @param true if this is hollow
616 */
617 public void setHollow(boolean isHollow) {
618 this.isHollow = isHollow;
619 }
620 }