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.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                 // allow GC of List when initialized
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                 // allow GC of List when initialized
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                 // allow GC of List when initialized
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         // todo: this probably isn't the most efficent algorithm
387         // but should avoid premature optimization
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         // if we haven't found anything, return null.
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     // Implementation methods
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                 // force lazy recreation later
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                 // force lazy recreation later
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                 // force lazy recreation later
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 }