View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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                 // allow GC of List when initialized
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                 // allow GC of List when initialized
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                 // allow GC of List when initialized
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         // todo: this probably isn't the most efficent algorithm
405         // but should avoid premature optimization
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         // if we haven't found anything, return null.
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     // Implementation methods
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                 // force lazy recreation later
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                 // force lazy recreation later
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                 // force lazy recreation later
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         // TODO is this implementation correct?
569         // maybe this method is unnecessary
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         //TODO: is this really a good design?
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 }