View Javadoc

1   package org.apache.commons.betwixt;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */ 
18  
19  import java.beans.BeanDescriptor;
20  import java.beans.BeanInfo;
21  import java.beans.IntrospectionException;
22  import java.beans.Introspector;
23  import java.beans.PropertyDescriptor;
24  import java.net.URL;
25  import java.util.ArrayList;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.beanutils.DynaBean;
31  import org.apache.commons.beanutils.DynaClass;
32  import org.apache.commons.beanutils.DynaProperty;
33  import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
34  import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
35  import org.apache.commons.betwixt.expression.EmptyExpression;
36  import org.apache.commons.betwixt.expression.Expression;
37  import org.apache.commons.betwixt.expression.IteratorExpression;
38  import org.apache.commons.betwixt.expression.StringExpression;
39  import org.apache.commons.betwixt.expression.Updater;
40  import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
41  import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
42  import org.apache.commons.betwixt.strategy.ClassNormalizer;
43  import org.apache.commons.betwixt.strategy.DefaultNameMapper;
44  import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
45  import org.apache.commons.betwixt.strategy.NameMapper;
46  import org.apache.commons.betwixt.strategy.PluralStemmer;
47  import org.apache.commons.logging.Log;
48  import org.apache.commons.logging.LogFactory;
49  
50  /*** 
51    * <p><code>XMLIntrospector</code> an introspector of beans to create a 
52    * XMLBeanInfo instance.</p>
53    *
54    * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
55    * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
56    * for a particular class, the <code>XMLBeanInfo</code> is cached.
57    * Later requests for the same class will return the cached value.</p>
58    * 
59    * <p>Note :</p>
60    * <p>This class makes use of the <code>java.bean.Introspector</code>
61    * class, which contains a BeanInfoSearchPath. To make sure betwixt can
62    * do his work correctly, this searchpath is completely ignored during 
63    * processing. The original values will be restored after processing finished
64    * </p>
65    * 
66    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
67    * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
68    * @version $Id: XMLIntrospector.java,v 1.28.2.1 2004/06/19 16:24:09 rdonkin Exp $
69    */
70  public class XMLIntrospector {
71  
72      /*** Log used for logging (Doh!) */    
73      protected Log log = LogFactory.getLog( XMLIntrospector.class );
74      
75      /*** should attributes or elements be used for primitive types */
76      private boolean attributesForPrimitives = false;
77      
78      /*** should we wrap collections in an extra element? */
79      private boolean wrapCollectionsInElement = true;
80      
81      /*** Maps classes to <code>XMLBeanInfo</code>'s */
82      private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry();
83      
84      /*** Digester used to parse the XML descriptor files */
85      private XMLBeanInfoDigester digester;
86  
87      // pluggable strategies
88          
89      /*** The strategy used to detect matching singular and plural properties */
90      private PluralStemmer pluralStemmer;
91      
92      /*** The strategy used to convert bean type names into element names */
93      private NameMapper elementNameMapper;
94  
95      /*** Strategy normalizes the Class of the Object before introspection */
96      private ClassNormalizer classNormalizer = new ClassNormalizer(); 
97  
98      /***
99       * The strategy used to convert bean type names into attribute names
100      * It will default to the normal nameMapper.
101      */
102     private NameMapper attributeNameMapper;
103     /*** Should the existing bean info search path for java.reflect.Introspector be used? */
104     private boolean useBeanInfoSearchPath = false;
105     
106     /*** Base constructor */
107     public XMLIntrospector() {
108     }
109     
110     /***
111      * <p>Gets the current logging implementation. </p>
112      * @return the Log implementation which this class logs to
113      */ 
114     public Log getLog() {
115         return log;
116     }
117 
118     /***
119      * <p>Sets the current logging implementation.</p>
120      * @param log the Log implementation to use for logging
121      */ 
122     public void setLog(Log log) {
123         this.log = log;
124     }
125     
126     /*** 
127      * <p>Gets the current registry implementation.
128      * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
129      * before introspecting. 
130      * After standard introspection is complete, the instance will be passed to the registry.</p>
131      *
132      * <p>This allows finely grained control over the caching strategy.
133      * It also allows the standard introspection mechanism 
134      * to be overridden on a per class basis.</p>
135      *
136      * @return the XMLBeanInfoRegistry currently used 
137      */
138     public XMLBeanInfoRegistry getRegistry() {
139         return registry;
140     }
141     
142     /*** 
143      * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
144      * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
145      * before introspecting. 
146      * After standard introspection is complete, the instance will be passed to the registry.</p>
147      *
148      * <p>This allows finely grained control over the caching strategy.
149      * It also allows the standard introspection mechanism 
150      * to be overridden on a per class basis.</p>
151      *
152      * @param registry the XMLBeanInfoRegistry to use
153      */
154     public void setRegistry(XMLBeanInfoRegistry registry) {
155         this.registry = registry;
156     }
157     
158     
159     /***
160       * Gets the <code>ClassNormalizer</code> strategy.
161       * This is used to determine the Class to be introspected
162       * (the normalized Class). 
163       *
164       * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
165       * for a given Object.
166       * @since 0.5
167       */
168     public ClassNormalizer getClassNormalizer() {
169         return classNormalizer;
170     }
171     
172     /***
173       * Sets the <code>ClassNormalizer</code> strategy.
174       * This is used to determine the Class to be introspected
175       * (the normalized Class). 
176       *
177       * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine 
178       * the Class to be introspected for a given Object.
179       * @since 0.5
180       */    
181     public void setClassNormalizer(ClassNormalizer classNormalizer) {
182         this.classNormalizer = classNormalizer;
183     }
184     
185     /*** 
186      * Is <code>XMLBeanInfo</code> caching enabled? 
187      *
188      * @deprecated 0.5 replaced by XMlBeanInfoRegistry
189      * @return true if caching is enabled
190      */
191     public boolean isCachingEnabled() {
192         return true;
193     }
194 
195     /***
196      * Set whether <code>XMLBeanInfo</code> caching should be enabled.
197      *
198      * @deprecated 0.5 replaced by XMlBeanInfoRegistry
199      * @param cachingEnabled ignored
200      */    
201     public void setCachingEnabled(boolean cachingEnabled) {
202         //
203     }
204     
205     /***
206      * Flush existing cached <code>XMLBeanInfo</code>'s.
207      *
208      * @deprecated 0.5 use flushable registry instead
209      */
210     public void flushCache() {}
211     
212     /*** Create a standard <code>XMLBeanInfo</code> by introspection
213       * The actual introspection depends only on the <code>BeanInfo</code>
214       * associated with the bean.
215       * 
216       * @param bean introspect this bean
217       * @return XMLBeanInfo describing bean-xml mapping
218       * @throws IntrospectionException when the bean introspection fails
219       */
220     public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
221         if (log.isDebugEnabled()) {
222             log.debug( "Introspecting..." );
223             log.debug(bean);
224         }
225         
226         if ( bean instanceof DynaBean ) {
227             // allow DynaBean implementations to be overridden by .betwixt files
228             XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
229             if (xmlBeanInfo != null) {
230                 return xmlBeanInfo;
231             }
232             // this is DynaBean use the DynaClass for introspection
233             return introspect( ((DynaBean) bean).getDynaClass() );
234             
235         } else {
236             // normal bean so normal introspection
237             Class normalClass = getClassNormalizer().getNormalizedClass( bean );
238             return introspect( normalClass );
239         }
240     }
241     
242     /***
243      * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
244      * Customizing DynaBeans using betwixt is not supported.
245      * 
246      * @param dynaClass the DynaBean to introspect
247      * 
248      * @return XMLBeanInfo for the DynaClass
249      */
250     public XMLBeanInfo introspect(DynaClass dynaClass) {
251 
252         // for now this method does not do much, since XMLBeanInfoRegistry cannot
253         // use a DynaClass as a key
254         // TODO: add caching for DynaClass XMLBeanInfo
255         // need to work out if this is possible
256         
257         // this line allows subclasses to change creation strategy
258         XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
259         
260         // populate the created info with 
261         DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
262         populate( xmlInfo, beanClass );
263         
264         return xmlInfo;  
265     }
266     
267     /*** Create a standard <code>XMLBeanInfo</code> by introspection.
268       * The actual introspection depends only on the <code>BeanInfo</code>
269       * associated with the bean.    
270       *    
271       * @param aClass introspect this class
272       * @return XMLBeanInfo describing bean-xml mapping
273       * @throws IntrospectionException when the bean introspection fails
274       */
275     public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
276         // we first reset the beaninfo searchpath.
277         String[] searchPath = null;
278         if ( !useBeanInfoSearchPath ) {
279             searchPath = Introspector.getBeanInfoSearchPath();
280             Introspector.setBeanInfoSearchPath(new String[] { });
281         }
282         
283         XMLBeanInfo xmlInfo = registry.get( aClass );
284         
285         if ( xmlInfo == null ) {
286             // lets see if we can find an XML descriptor first
287             if ( log.isDebugEnabled() ) {
288                 log.debug( "Attempting to lookup an XML descriptor for class: " + aClass );
289             }
290             
291             xmlInfo = findByXMLDescriptor( aClass );
292             if ( xmlInfo == null ) {
293                 BeanInfo info = Introspector.getBeanInfo( aClass );
294                 xmlInfo = introspect( info );
295             }
296             
297             if ( xmlInfo != null ) {
298                 registry.put( aClass, xmlInfo );
299             }
300         } else {
301             log.trace( "Used cached XMLBeanInfo." );
302         }
303         
304         if ( log.isTraceEnabled() ) {
305             log.trace( xmlInfo );
306         }
307         if ( !useBeanInfoSearchPath ) {
308             // we restore the beaninfo searchpath.
309             Introspector.setBeanInfoSearchPath( searchPath );
310         }
311         
312         return xmlInfo;
313     }
314     
315     /*** Create a standard <code>XMLBeanInfo</code> by introspection. 
316       * The actual introspection depends only on the <code>BeanInfo</code>
317       * associated with the bean.
318       *
319       * @param beanInfo the BeanInfo the xml-bean mapping is based on
320       * @return XMLBeanInfo describing bean-xml mapping
321       * @throws IntrospectionException when the bean introspection fails
322       */
323     public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {    
324         XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
325         populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
326         return xmlBeanInfo;
327     }
328     
329     /***
330      * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
331      *
332      * @param xmlBeanInfo populate this, not null
333      * @param bean the type definition for the bean, not null
334      */
335     private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {    
336         String name = bean.getBeanName();
337         
338         ElementDescriptor elementDescriptor = new ElementDescriptor();
339         elementDescriptor.setLocalName( 
340             getElementNameMapper().mapTypeToElementName( name ) );
341         elementDescriptor.setPropertyType( bean.getElementType() );
342         
343         if (log.isTraceEnabled()) {
344             log.trace("Populating:" + bean);
345         }
346 
347         // add default string value for primitive types
348         if ( bean.isPrimitiveType() ) {
349             log.trace("Bean is primitive");
350             elementDescriptor.setTextExpression( StringExpression.getInstance() );
351             elementDescriptor.setPrimitiveType(true);
352             
353         } else if ( bean.isLoopType() ) {
354             log.trace("Bean is loop");
355             ElementDescriptor loopDescriptor = new ElementDescriptor();
356             loopDescriptor.setContextExpression(
357                 new IteratorExpression( EmptyExpression.getInstance() )
358             );
359             if ( bean.isMapType() ) {
360                 loopDescriptor.setQualifiedName( "entry" );
361             }
362             elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
363             
364 /*            
365             elementDescriptor.setContextExpression(
366                 new IteratorExpression( EmptyExpression.getInstance() )
367             );
368 */
369         } else {
370             log.trace("Bean is standard type");
371             List elements = new ArrayList();
372             List attributes = new ArrayList();
373             List contents = new ArrayList();
374 
375             addProperties( bean.getProperties(), elements, attributes, contents );    
376 
377             int size = elements.size();
378             if ( size > 0 ) {
379                 ElementDescriptor[] descriptors = new ElementDescriptor[size];
380                 elements.toArray( descriptors );
381                 elementDescriptor.setElementDescriptors( descriptors );
382             }
383             size = attributes.size();
384             if ( size > 0 ) {
385                 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
386                 attributes.toArray( descriptors );
387                 elementDescriptor.setAttributeDescriptors( descriptors );
388             }
389             size = contents.size();
390             if ( size > 0 ) {
391                 if ( size > 0 ) {
392                     Descriptor[] descriptors = new Descriptor[size];
393                     contents.toArray( descriptors );
394                     elementDescriptor.setContentDescriptors( descriptors );
395                 }
396             }
397         }
398         
399         xmlBeanInfo.setElementDescriptor( elementDescriptor );        
400         
401         // default any addProperty() methods
402         XMLIntrospectorHelper.defaultAddMethods( this, elementDescriptor, bean.getElementType() );
403         
404         if (log.isTraceEnabled()) {
405             log.trace("Populated descriptor:");
406             log.trace(elementDescriptor);
407         }
408     }
409 
410     
411     /***
412      * Creates XMLBeanInfo for the given DynaClass.
413      * 
414      * @param dynaClass the class describing a DynaBean
415      * 
416      * @return XMLBeanInfo that describes the properties of the given 
417      * DynaClass
418      */
419     protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
420         // XXX is the chosen class right?
421         XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
422         return beanInfo;
423     }
424 
425 
426     // Properties
427     //-------------------------------------------------------------------------        
428     
429     /*** 
430       * Should attributes (or elements) be used for primitive types.
431       * @return true if primitive types will be mapped to attributes in the introspection
432       */
433     public boolean isAttributesForPrimitives() {
434         return attributesForPrimitives;
435     }
436 
437     /*** 
438       * Set whether attributes (or elements) should be used for primitive types. 
439       * @param attributesForPrimitives pass trus to map primitives to attributes,
440       *        pass false to map primitives to elements
441       */
442     public void setAttributesForPrimitives(boolean attributesForPrimitives) {
443         this.attributesForPrimitives = attributesForPrimitives;
444     }
445 
446     /***
447      * Should collections be wrapped in an extra element?
448      * 
449      * @return whether we should we wrap collections in an extra element? 
450      */
451     public boolean isWrapCollectionsInElement() {
452         return wrapCollectionsInElement;
453     }
454 
455     /*** 
456      * Sets whether we should we wrap collections in an extra element.
457      *
458      * @param wrapCollectionsInElement pass true if collections should be wrapped in a
459      *        parent element
460      */
461     public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
462         this.wrapCollectionsInElement = wrapCollectionsInElement;
463     }
464 
465     /*** 
466      * Get singular and plural matching strategy.
467      *
468      * @return the strategy used to detect matching singular and plural properties 
469      */
470     public PluralStemmer getPluralStemmer() {
471         if ( pluralStemmer == null ) {
472             pluralStemmer = createPluralStemmer();
473         }
474         return pluralStemmer;
475     }
476     
477     /*** 
478      * Sets the strategy used to detect matching singular and plural properties 
479      *
480      * @param pluralStemmer the PluralStemmer used to match singular and plural
481      */
482     public void setPluralStemmer(PluralStemmer pluralStemmer) {
483         this.pluralStemmer = pluralStemmer;
484     }
485 
486     /*** 
487      * Gets the name mapper strategy.
488      * 
489      * @return the strategy used to convert bean type names into element names
490      * @deprecated 0.5 getNameMapper is split up in 
491      * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
492      */
493     public NameMapper getNameMapper() {
494         return getElementNameMapper();
495     }
496     
497     /*** 
498      * Sets the strategy used to convert bean type names into element names
499      * @param nameMapper the NameMapper strategy to be used
500      * @deprecated 0.5 setNameMapper is split up in 
501      * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
502      */
503     public void setNameMapper(NameMapper nameMapper) {
504         setElementNameMapper(nameMapper);
505     }
506 
507 
508     /***
509      * Gets the name mapping strategy used to convert bean names into elements.
510      *
511      * @return the strategy used to convert bean type names into element 
512      * names. If no element mapper is currently defined then a default one is created.
513      */
514     public NameMapper getElementNameMapper() {
515         if ( elementNameMapper == null ) {
516             elementNameMapper = createNameMapper();
517          }
518         return elementNameMapper;
519     }
520      
521     /***
522      * Sets the strategy used to convert bean type names into element names
523      * @param nameMapper the NameMapper to use for the conversion
524      */
525     public void setElementNameMapper(NameMapper nameMapper) {
526         this.elementNameMapper = nameMapper;
527     }
528     
529 
530     /***
531      * Gets the name mapping strategy used to convert bean names into attributes.
532      *
533      * @return the strategy used to convert bean type names into attribute
534      * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
535      */
536     public NameMapper getAttributeNameMapper() {
537         if (attributeNameMapper == null) {
538             attributeNameMapper = createNameMapper();
539         }
540         return attributeNameMapper;
541      }
542 
543 
544     /***
545      * Sets the strategy used to convert bean type names into attribute names
546      * @param nameMapper the NameMapper to use for the convertion
547      */
548     public void setAttributeNameMapper(NameMapper nameMapper) {
549         this.attributeNameMapper = nameMapper;
550     }
551 
552     /*** 
553      * Create a XML descriptor from a bean one. 
554      * Go through and work out whether it's a loop property, a primitive or a standard.
555      * The class property is ignored.
556      *
557      * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
558      * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
559      * @return a correctly configured <code>NodeDescriptor</code> for the property
560      * @throws IntrospectionException when bean introspection fails
561      * @deprecated 0.5 use {@link #createXMLDescriptor}.
562      */
563     public Descriptor createDescriptor(
564         PropertyDescriptor propertyDescriptor, 
565         boolean useAttributesForPrimitives
566     ) throws IntrospectionException {
567         return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
568     }
569  
570     /*** 
571      * Create a XML descriptor from a bean one. 
572      * Go through and work out whether it's a loop property, a primitive or a standard.
573      * The class property is ignored.
574      *
575      * @param beanProperty the BeanProperty specifying the property
576      * @return a correctly configured <code>NodeDescriptor</code> for the property
577      * @since 0.5
578      */
579     public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
580         String name = beanProperty.getPropertyName();
581         Class type = beanProperty.getPropertyType();
582        
583         if (log.isTraceEnabled()) {
584             log.trace("Creating descriptor for property: name="
585                 + name + " type=" + type);
586         }
587         
588         Descriptor descriptor = null;
589         Expression propertyExpression = beanProperty.getPropertyExpression();
590         Updater propertyUpdater = beanProperty.getPropertyUpdater();
591         
592         if ( propertyExpression == null ) {
593             if (log.isTraceEnabled()) {
594                 log.trace( "No read method for property: name="
595                     + name + " type=" + type);
596             }
597             return null;
598         }
599         
600         if ( log.isTraceEnabled() ) {
601             log.trace( "Property expression=" + propertyExpression );
602         }
603         
604         // choose response from property type
605         
606         // XXX: ignore class property ??
607         if ( Class.class.equals( type ) && "class".equals( name ) ) {
608             log.trace( "Ignoring class property" );
609             return null;
610             
611         }
612         
613         if ( isPrimitiveType( type ) ) {
614             if (log.isTraceEnabled()) {
615                 log.trace( "Primitive type: " + name);
616             }
617             if ( isAttributesForPrimitives() ) {
618                 if (log.isTraceEnabled()) {
619                     log.trace( "Adding property as attribute: " + name );
620                 }
621                 descriptor = new AttributeDescriptor();
622             } else {
623                 if (log.isTraceEnabled()) {
624                     log.trace( "Adding property as element: " + name );
625                 }
626                 descriptor = new ElementDescriptor(true);
627             }
628             descriptor.setTextExpression( propertyExpression );
629             if ( propertyUpdater != null ) {
630                 descriptor.setUpdater( propertyUpdater );
631             }
632             
633         } else if ( isLoopType( type ) ) {
634             if (log.isTraceEnabled()) {
635                 log.trace("Loop type: " + name);
636                 log.trace("Wrap in collections? " + isWrapCollectionsInElement());
637             }
638             ElementDescriptor loopDescriptor = new ElementDescriptor();
639             loopDescriptor.setContextExpression(
640                 new IteratorExpression( propertyExpression )
641             );
642             loopDescriptor.setWrapCollectionsInElement( isWrapCollectionsInElement() );
643             // XXX: need to support some kind of 'add' or handle arrays, Lists or indexed properties
644             //loopDescriptor.setUpdater( new MethodUpdater( writeMethod ) );
645             if ( Map.class.isAssignableFrom( type ) ) {
646                 loopDescriptor.setQualifiedName( "entry" );
647                 // add elements for reading
648                 loopDescriptor.addElementDescriptor( new ElementDescriptor( "key" ) );
649                 loopDescriptor.addElementDescriptor( new ElementDescriptor( "value" ) );
650             }
651 
652             ElementDescriptor elementDescriptor = new ElementDescriptor();
653             elementDescriptor.setWrapCollectionsInElement( isWrapCollectionsInElement() );
654             elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
655             
656             descriptor = elementDescriptor;
657             
658         } else {
659             if (log.isTraceEnabled()) {
660                 log.trace( "Standard property: " + name);
661             }
662             ElementDescriptor elementDescriptor = new ElementDescriptor();
663             elementDescriptor.setContextExpression( propertyExpression );
664             if ( propertyUpdater != null ) {
665                 elementDescriptor.setUpdater( propertyUpdater );
666             }
667             
668             descriptor = elementDescriptor;
669         }
670 
671         if (descriptor instanceof NodeDescriptor) {
672             NodeDescriptor nodeDescriptor = (NodeDescriptor) descriptor;
673             if (descriptor instanceof AttributeDescriptor) {
674                 // we want to use the attributemapper only when it is an attribute.. 
675                 nodeDescriptor.setLocalName( 
676                     getAttributeNameMapper().mapTypeToElementName( name ) );
677                 
678             } else {
679                 nodeDescriptor.setLocalName( 
680                     getElementNameMapper().mapTypeToElementName( name ) );
681             }        
682         }
683   
684         descriptor.setPropertyName( name );
685         descriptor.setPropertyType( type );
686         
687         // XXX: associate more bean information with the descriptor?
688         //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() );
689         //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() );
690         
691         if (log.isTraceEnabled()) {
692             log.trace( "Created descriptor:" );
693             log.trace( descriptor );
694         }
695         return descriptor;
696     }
697 
698 
699 
700     
701     // Implementation methods
702     //-------------------------------------------------------------------------        
703     
704     /*** 
705      * A Factory method to lazily create a new strategy 
706      * to detect matching singular and plural properties.
707      *
708      * @return new defualt PluralStemmer implementation
709      */
710     protected PluralStemmer createPluralStemmer() {
711         return new DefaultPluralStemmer();
712     }
713     
714     /*** 
715      * A Factory method to lazily create a strategy 
716      * used to convert bean type names into element names.
717      *
718      * @return new default NameMapper implementation
719      */
720     protected NameMapper createNameMapper() {
721         return new DefaultNameMapper();
722     }
723     
724     /*** 
725      * Attempt to lookup the XML descriptor for the given class using the
726      * classname + ".betwixt" using the same ClassLoader used to load the class
727      * or return null if it could not be loaded
728      * 
729      * @param aClass digester .betwixt file for this class
730      * @return XMLBeanInfo digested from the .betwixt file if one can be found.
731      *         Otherwise null.
732      */
733     protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
734         // trim the package name
735         String name = aClass.getName();
736         int idx = name.lastIndexOf( '.' );
737         if ( idx >= 0 ) {
738             name = name.substring( idx + 1 );
739         }
740         name += ".betwixt";
741         
742         URL url = aClass.getResource( name );
743         if ( url != null ) {
744             try {
745                 String urlText = url.toString();
746                 if ( log.isDebugEnabled( )) {
747                     log.debug( "Parsing Betwixt XML descriptor: " + urlText );
748                 }
749                 // synchronized method so this digester is only used by
750                 // one thread at once
751                 if ( digester == null ) {
752                     digester = new XMLBeanInfoDigester();
753                     digester.setXMLIntrospector( this );
754                 }
755                 digester.setBeanClass( aClass );
756                 return (XMLBeanInfo) digester.parse( urlText );
757             } catch (Exception e) {
758                 log.warn( "Caught exception trying to parse: " + name, e );
759             }
760         }
761         
762         if ( log.isTraceEnabled() ) {
763             log.trace( "Could not find betwixt file " + name );
764         }
765         return null;
766     }
767             
768     /*** 
769      * Loop through properties and process each one 
770      *
771      * @param beanInfo the BeanInfo whose properties will be processed
772      * @param elements ElementDescriptor list to which elements will be added
773      * @param attributes AttributeDescriptor list to which attributes will be added
774      * @param contents Descriptor list to which mixed content will be added
775      * @throws IntrospectionException if the bean introspection fails
776      * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
777      */
778     protected void addProperties(
779                                     BeanInfo beanInfo, 
780                                     List elements, 
781                                     List attributes,
782                                     List contents)
783                                         throws 
784                                             IntrospectionException {
785         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
786         if ( descriptors != null ) {
787             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
788                 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
789             }
790         }
791         if (log.isTraceEnabled()) {
792             log.trace(elements);
793             log.trace(attributes);
794             log.trace(contents);
795         }
796     }
797     /*** 
798      * Loop through properties and process each one 
799      *
800      * @param beanProperties the properties to be processed
801      * @param elements ElementDescriptor list to which elements will be added
802      * @param attributes AttributeDescriptor list to which attributes will be added
803      * @param contents Descriptor list to which mixed content will be added
804      * @since 0.5
805      */
806     protected void addProperties(
807                                     BeanProperty[] beanProperties, 
808                                     List elements, 
809                                     List attributes,
810                                     List contents) {
811         if ( beanProperties != null ) {
812             if (log.isTraceEnabled()) {
813                 log.trace(beanProperties.length + " properties to be added");
814             }
815             for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
816                 addProperty(beanProperties[i], elements, attributes, contents);
817             }
818         }
819         if (log.isTraceEnabled()) {
820             log.trace("After properties have been added (elements, attributes, contents):");
821             log.trace(elements);
822             log.trace(attributes);
823             log.trace(contents);
824         }
825     }    
826 
827     
828     /*** 
829      * Process a property. 
830      * Go through and work out whether it's a loop property, a primitive or a standard.
831      * The class property is ignored.
832      *
833      * @param beanInfo the BeanInfo whose property is being processed
834      * @param propertyDescriptor the PropertyDescriptor to process
835      * @param elements ElementDescriptor list to which elements will be added
836      * @param attributes AttributeDescriptor list to which attributes will be added
837      * @param contents Descriptor list to which mixed content will be added
838      * @throws IntrospectionException if the bean introspection fails
839      * @deprecated 0.5 BeanInfo is no longer required. 
840      * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
841      */
842     protected void addProperty(
843                                 BeanInfo beanInfo, 
844                                 PropertyDescriptor propertyDescriptor, 
845                                 List elements, 
846                                 List attributes,
847                                 List contents)
848                                     throws 
849                                         IntrospectionException {
850        addProperty( propertyDescriptor, elements, attributes, contents);
851     }
852     
853     /*** 
854      * Process a property. 
855      * Go through and work out whether it's a loop property, a primitive or a standard.
856      * The class property is ignored.
857      *
858      * @param propertyDescriptor the PropertyDescriptor to process
859      * @param elements ElementDescriptor list to which elements will be added
860      * @param attributes AttributeDescriptor list to which attributes will be added
861      * @param contents Descriptor list to which mixed content will be added
862      * @throws IntrospectionException if the bean introspection fails
863      * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
864      */
865     protected void addProperty(
866                                 PropertyDescriptor propertyDescriptor, 
867                                 List elements, 
868                                 List attributes,
869                                 List contents)
870                                     throws 
871                                         IntrospectionException {
872         addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
873     }
874     
875     /*** 
876      * Process a property. 
877      * Go through and work out whether it's a loop property, a primitive or a standard.
878      * The class property is ignored.
879      *
880      * @param beanProperty the bean property to process
881      * @param elements ElementDescriptor list to which elements will be added
882      * @param attributes AttributeDescriptor list to which attributes will be added
883      * @param contents Descriptor list to which mixed content will be added
884      * @since 0.5
885      */
886     protected void addProperty(
887                                 BeanProperty beanProperty, 
888                                 List elements, 
889                                 List attributes,
890                                 List contents) {
891         Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
892         if (nodeDescriptor == null) {
893            return;
894         }
895         if (nodeDescriptor instanceof ElementDescriptor) {
896            elements.add(nodeDescriptor);
897         } else if (nodeDescriptor instanceof AttributeDescriptor) {
898            attributes.add(nodeDescriptor);
899         } else {
900            contents.add(nodeDescriptor);
901         }                                 
902     }
903     
904     /*** 
905      * Loop through properties and process each one 
906      *
907      * @param beanInfo the BeanInfo whose properties will be processed
908      * @param elements ElementDescriptor list to which elements will be added
909      * @param attributes AttributeDescriptor list to which attributes will be added
910      * @throws IntrospectionException if the bean introspection fails
911      * @deprecated 0.5 this method does not support mixed content. 
912      * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
913      */
914     protected void addProperties(
915                                     BeanInfo beanInfo, 
916                                     List elements, 
917                                     List attributes) 
918                                         throws 
919                                             IntrospectionException {
920         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
921         if ( descriptors != null ) {
922             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
923                 addProperty(beanInfo, descriptors[i], elements, attributes);
924             }
925         }
926         if (log.isTraceEnabled()) {
927             log.trace(elements);
928             log.trace(attributes);
929         }
930     }
931     
932     /*** 
933      * Process a property. 
934      * Go through and work out whether it's a loop property, a primitive or a standard.
935      * The class property is ignored.
936      *
937      * @param beanInfo the BeanInfo whose property is being processed
938      * @param propertyDescriptor the PropertyDescriptor to process
939      * @param elements ElementDescriptor list to which elements will be added
940      * @param attributes AttributeDescriptor list to which attributes will be added
941      * @throws IntrospectionException if the bean introspection fails
942      * @deprecated 0.5 this method does not support mixed content. 
943      * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
944      */
945     protected void addProperty(
946                                 BeanInfo beanInfo, 
947                                 PropertyDescriptor propertyDescriptor, 
948                                 List elements, 
949                                 List attributes) 
950                                     throws 
951                                         IntrospectionException {
952         NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
953             .createDescriptor(propertyDescriptor,
954                                  isAttributesForPrimitives(),
955                                  this);
956         if (nodeDescriptor == null) {
957            return;
958         }
959         if (nodeDescriptor instanceof ElementDescriptor) {
960            elements.add(nodeDescriptor);
961         } else {
962            attributes.add(nodeDescriptor);
963         }
964     }
965 
966     
967     /*** 
968      * Factory method to create XMLBeanInfo instances 
969      *
970      * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
971      * @return XMLBeanInfo describing the bean-xml mapping
972      */
973     protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
974         XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
975         return xmlBeanInfo;
976     }
977 
978     /*** 
979      * Is this class a loop?
980      *
981      * @param type the Class to test
982      * @return true if the type is a loop type 
983      */
984     public boolean isLoopType(Class type) {
985         return XMLIntrospectorHelper.isLoopType(type);
986     }
987     
988     
989     /*** 
990      * Is this class a primitive?
991      * @param type the Class to test
992      * @return true for primitive types 
993      */
994     public boolean isPrimitiveType(Class type) {
995         return XMLIntrospectorHelper.isPrimitiveType(type);
996     }
997     /***
998      * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
999      * By default it will be false.
1000      * 
1001      * @return boolean if the beanInfoSearchPath should be used.
1002      */
1003     public boolean useBeanInfoSearchPath() {
1004         return useBeanInfoSearchPath;
1005     }
1006 
1007     /***
1008      * Specifies if you want to use the beanInfoSearchPath 
1009      * @see java.beans.Introspector for more details
1010      * @param useBeanInfoSearchPath 
1011      */
1012     public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
1013         this.useBeanInfoSearchPath = useBeanInfoSearchPath;
1014     }
1015     
1016     /*** Some type of pseudo-bean */
1017     private abstract class BeanType {
1018         /*** 
1019          * Gets the name for this bean type 
1020          * @return the bean type name, not null
1021          */
1022         public abstract String getBeanName();
1023         
1024         /*** 
1025          * Gets the type to be used by the associated element
1026          * @return a Class that is the type not null
1027          */
1028         public abstract Class getElementType();
1029 
1030         /***
1031          * Is this type a primitive?
1032          * @return true if this type should be treated by betwixt as a primitive
1033          */
1034         public abstract boolean isPrimitiveType();
1035         
1036         /***
1037          * is this type a map?
1038          * @return true this should be treated as a map.
1039          */
1040         public abstract boolean isMapType();
1041         
1042         /*** 
1043          * Is this type a loop?
1044          * @return true if this should be treated as a loop
1045          */
1046         public abstract boolean isLoopType();
1047         
1048         /***
1049          * Gets the properties associated with this bean.
1050          * @return the BeanProperty's, not null
1051          */
1052         public abstract BeanProperty[] getProperties();
1053         
1054         /***
1055          * Create string representation
1056          * @return something useful for logging
1057          */
1058         public String toString() {
1059             return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1060         }
1061     }
1062     
1063     /*** Supports standard Java Beans */
1064     private class JavaBeanType extends BeanType {
1065         /*** Introspected bean */
1066         private BeanInfo beanInfo;
1067         /*** Bean class */
1068         private Class beanClass;
1069         /*** Bean name */
1070         private String name;
1071         /*** Bean properties */
1072         private BeanProperty[] properties;
1073         
1074         /***
1075          * Constructs a BeanType for a standard Java Bean
1076          * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1077          */
1078         public JavaBeanType(BeanInfo beanInfo) {
1079             this.beanInfo = beanInfo;
1080             BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1081             beanClass = beanDescriptor.getBeanClass();
1082             name = beanDescriptor.getName();
1083             // Array's contain a bad character
1084             if (beanClass.isArray()) {
1085                 // called all array's Array
1086                 name = "Array";
1087             }
1088             
1089         }
1090         
1091         /*** @see BeanType #getElementType */
1092         public Class getElementType() {
1093             return beanClass;
1094         }
1095         
1096         /*** @see BeanType#getBeanName */
1097         public String getBeanName() {
1098             return name;
1099         }
1100         
1101         /*** @see BeanType#isPrimitiveType */
1102         public boolean isPrimitiveType() {
1103             return XMLIntrospectorHelper.isPrimitiveType( beanClass );
1104         }
1105         
1106         /*** @see BeanType#isLoopType */
1107         public boolean isLoopType() {
1108             return XMLIntrospectorHelper.isLoopType( beanClass );
1109         }
1110         
1111         /*** @see BeanType#isMapType */
1112         public boolean isMapType() {
1113             return Map.class.isAssignableFrom( beanClass );
1114         }
1115         
1116         /*** @see BeanType#getProperties */
1117         public BeanProperty[] getProperties() {
1118             // lazy creation
1119             if ( properties == null ) {
1120                 ArrayList propertyDescriptors = new ArrayList();
1121                 // add base bean info
1122                 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1123                 if ( descriptors != null ) {
1124                     for (int i=0, size=descriptors.length; i<size; i++) {
1125                         propertyDescriptors.add( descriptors[i] );
1126                     }
1127                 }
1128                 
1129                 // add properties from additional bean infos
1130                 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1131                 if ( additionals != null ) {
1132                     for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1133                         BeanInfo additionalInfo = additionals[i];
1134                         descriptors = beanInfo.getPropertyDescriptors();
1135                         if ( descriptors != null ) {
1136                             for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1137                                 propertyDescriptors.add( descriptors[j] );
1138                             }
1139                         }
1140                     }            
1141                 }
1142                 // what happens when size is zero?
1143                 properties = new BeanProperty[ propertyDescriptors.size() ];
1144                 int count = 0;
1145                 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1146                     PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1147                     properties[count] = new BeanProperty( propertyDescriptor );
1148                 }
1149             }
1150             return properties;
1151         }
1152     }
1153     
1154     /*** Implementation for DynaClasses */
1155     private class DynaClassBeanType extends BeanType {
1156         /*** BeanType for this DynaClass */
1157         private DynaClass dynaClass;
1158         /*** Properties extracted in constuctor */
1159         private BeanProperty[] properties;
1160         
1161         /*** 
1162          * Constructs a BeanType for a DynaClass
1163          * @param dynaClass not null
1164          */
1165         public DynaClassBeanType(DynaClass dynaClass) {
1166             this.dynaClass = dynaClass;
1167             DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1168             properties = new BeanProperty[dynaProperties.length];
1169             for (int i=0, size=dynaProperties.length; i<size; i++) {
1170                 properties[i] = new BeanProperty(dynaProperties[i]);
1171             }
1172         }
1173         
1174         /*** @see BeanType#getBeanName */
1175         public String getBeanName() {
1176             return dynaClass.getName();
1177         }
1178         /*** @see BeanType#getElementType */
1179         public Class getElementType() {
1180             return DynaClass.class;
1181         }
1182         /*** @see BeanType#isPrimitiveType */
1183         public boolean isPrimitiveType() {
1184             return false;
1185         }
1186         /*** @see BeanType#isMapType */
1187         public boolean isMapType() {
1188             return false;
1189         }
1190         /*** @see BeanType#isLoopType */
1191         public boolean isLoopType() {
1192             return false;
1193         }
1194         /*** @see BeanType#getProperties */
1195         public BeanProperty[] getProperties() {
1196             return properties;
1197         }
1198     }
1199 }