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.io.IOException;
25  import java.lang.reflect.Method;
26  import java.net.URL;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.Iterator;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.Set;
33  
34  import org.apache.commons.beanutils.DynaBean;
35  import org.apache.commons.beanutils.DynaClass;
36  import org.apache.commons.beanutils.DynaProperty;
37  import org.apache.commons.betwixt.digester.MultiMappingBeanInfoDigester;
38  import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
39  import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
40  import org.apache.commons.betwixt.expression.EmptyExpression;
41  import org.apache.commons.betwixt.expression.IteratorExpression;
42  import org.apache.commons.betwixt.expression.MapEntryAdder;
43  import org.apache.commons.betwixt.expression.MethodUpdater;
44  import org.apache.commons.betwixt.expression.StringExpression;
45  import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
46  import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver;
47  import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
48  import org.apache.commons.betwixt.strategy.ClassNormalizer;
49  import org.apache.commons.betwixt.strategy.DefaultNameMapper;
50  import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
51  import org.apache.commons.betwixt.strategy.NameMapper;
52  import org.apache.commons.betwixt.strategy.PluralStemmer;
53  import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
54  import org.apache.commons.logging.Log;
55  import org.apache.commons.logging.LogFactory;
56  import org.xml.sax.InputSource;
57  import org.xml.sax.SAXException;
58  
59  /*** 
60    * <p><code>XMLIntrospector</code> an introspector of beans to create a 
61    * XMLBeanInfo instance.</p>
62    *
63    * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
64    * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
65    * for a particular class, the <code>XMLBeanInfo</code> is cached.
66    * Later requests for the same class will return the cached value.</p>
67    * 
68    * <p>Note :</p>
69    * <p>This class makes use of the <code>java.bean.Introspector</code>
70    * class, which contains a BeanInfoSearchPath. To make sure betwixt can
71    * do his work correctly, this searchpath is completely ignored during 
72    * processing. The original values will be restored after processing finished
73    * </p>
74    * 
75    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
76    * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
77    */
78  public class XMLIntrospector {
79      /*** 
80       * Log used for logging (Doh!) 
81       * @deprecated 0.6 use the {@link #getLog()} property instead
82       */    
83      protected Log log = LogFactory.getLog( XMLIntrospector.class );
84      
85      /*** Maps classes to <code>XMLBeanInfo</code>'s */
86      private XMLBeanInfoRegistry registry;
87      
88      /*** Digester used to parse the XML descriptor files */
89      private XMLBeanInfoDigester digester;
90  
91      /*** Digester used to parse the multi-mapping XML descriptor files */
92      private MultiMappingBeanInfoDigester multiMappingdigester;
93      
94      /*** Configuration to be used for introspection*/
95      private IntrospectionConfiguration configuration;
96      
97      /***
98       * Resolves polymorphic references.
99       * Though this is used only at bind time,
100      * it is typically tightly couple to the xml registry. 
101      * It is therefore convenient to keep both references together.
102      */
103     private PolymorphicReferenceResolver polymorphicReferenceResolver;
104     
105     /*** Base constructor */
106     public XMLIntrospector() {
107         this(new IntrospectionConfiguration());
108     }
109     
110     /***
111      * Construct allows a custom configuration to be set on construction.
112      * This allows <code>IntrospectionConfiguration</code> subclasses
113      * to be easily used.
114      * @param configuration IntrospectionConfiguration, not null
115      */
116     public XMLIntrospector(IntrospectionConfiguration configuration) {
117         setConfiguration(configuration);
118         DefaultXMLBeanInfoRegistry defaultRegistry 
119             = new DefaultXMLBeanInfoRegistry();
120         setRegistry(defaultRegistry);
121         setPolymorphicReferenceResolver(defaultRegistry);
122     }
123     
124     
125     // Properties
126     //-------------------------------------------------------------------------   
127     
128     /***
129      * <p>Gets the current logging implementation. </p>
130      * @return the Log implementation which this class logs to
131      */ 
132     public Log getLog() {
133         return getConfiguration().getIntrospectionLog();
134     }
135 
136     /***
137      * <p>Sets the current logging implementation.</p>
138      * @param log the Log implementation to use for logging
139      */ 
140     public void setLog(Log log) {
141         getConfiguration().setIntrospectionLog(log);
142     }
143     
144     /*** 
145      * <p>Gets the current registry implementation.
146      * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
147      * before introspecting. 
148      * After standard introspection is complete, the instance will be passed to the registry.</p>
149      *
150      * <p>This allows finely grained control over the caching strategy.
151      * It also allows the standard introspection mechanism 
152      * to be overridden on a per class basis.</p>
153      *
154      * @return the XMLBeanInfoRegistry currently used 
155      */
156     public XMLBeanInfoRegistry getRegistry() {
157         return registry;
158     }
159     
160     /*** 
161      * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
162      * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
163      * before introspecting. 
164      * After standard introspection is complete, the instance will be passed to the registry.</p>
165      *
166      * <p>This allows finely grained control over the caching strategy.
167      * It also allows the standard introspection mechanism 
168      * to be overridden on a per class basis.</p>
169      *
170      * <p><strong>Note</strong> when using polymophic mapping with a custom
171      * registry, a call to 
172      * {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
173      * may be necessary.
174      * </p>
175      * @param registry the XMLBeanInfoRegistry to use
176      */
177     public void setRegistry(XMLBeanInfoRegistry registry) {
178         this.registry = registry;
179     }
180     
181     /***
182      * Gets the configuration to be used for introspection.
183      * The various introspection-time strategies 
184      * and configuration variables have been consolidated as properties
185      * of this bean.
186      * This allows the configuration to be more easily shared.
187      * @return IntrospectionConfiguration, not null
188      */
189     public IntrospectionConfiguration getConfiguration() {
190         return configuration;
191     }
192 
193     /***
194      * Sets the configuration to be used for introspection.
195      * The various introspection-time strategies 
196      * and configuration variables have been consolidated as properties
197      * of this bean.
198      * This allows the configuration to be more easily shared.
199      * @param configuration IntrospectionConfiguration, not null
200      */
201     public void setConfiguration(IntrospectionConfiguration configuration) {
202         this.configuration = configuration;
203     }
204     
205     
206     /***
207       * Gets the <code>ClassNormalizer</code> strategy.
208       * This is used to determine the Class to be introspected
209       * (the normalized Class). 
210       *
211       * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
212       * for a given Object.
213       * @deprecated 0.6 use getConfiguration().getClassNormalizer
214       * @since 0.5
215       */
216     public ClassNormalizer getClassNormalizer() {
217         return getConfiguration().getClassNormalizer();
218     }
219     
220     /***
221       * Sets the <code>ClassNormalizer</code> strategy.
222       * This is used to determine the Class to be introspected
223       * (the normalized Class). 
224       *
225       * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine 
226       * the Class to be introspected for a given Object.
227       * @deprecated 0.6 use getConfiguration().setClassNormalizer
228       * @since 0.5
229       *
230       */    
231     public void setClassNormalizer(ClassNormalizer classNormalizer) {
232         getConfiguration().setClassNormalizer(classNormalizer);
233     }
234     
235     
236     
237     /***
238      * <p>Gets the resolver for polymorphic references.</p>
239      * <p>
240      * Though this is used only at bind time,
241      * it is typically tightly couple to the xml registry. 
242      * It is therefore convenient to keep both references together.
243      * </p>
244      * <p><strong>Note:</strong> though the implementation is
245      * set initially to the default registry,
246      * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)}
247      * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
248      * with the instance may be necessary. 
249      * </p>
250      * @since 0.7
251      * @return <code>PolymorphicReferenceResolver</code>, not null
252      */
253     public PolymorphicReferenceResolver getPolymorphicReferenceResolver() {
254         return polymorphicReferenceResolver;
255     }
256     
257     /***
258      * <p>Sets the resolver for polymorphic references.</p>
259      * <p>
260      * Though this is used only at bind time,
261      * it is typically tightly couple to the xml registry. 
262      * It is therefore convenient to keep both references together.
263      * </p>
264      * <p><strong>Note:</strong> though the implementation is
265      * set initially to the default registry,
266      * this reference is not updated when {@link #setRegistry(XMLBeanInfoRegistry)}
267      * is called. Therefore, a call to {@link #setPolymorphicReferenceResolver(PolymorphicReferenceResolver)}
268      * with the instance may be necessary. 
269      * </p>
270      * @since 0.7
271      * @param polymorphicReferenceResolver The polymorphicReferenceResolver to set.
272      */
273     public void setPolymorphicReferenceResolver(
274             PolymorphicReferenceResolver polymorphicReferenceResolver) {
275         this.polymorphicReferenceResolver = polymorphicReferenceResolver;
276     }
277     
278     /*** 
279      * Is <code>XMLBeanInfo</code> caching enabled? 
280      *
281      * @deprecated 0.5 replaced by XMlBeanInfoRegistry
282      * @return true if caching is enabled
283      */
284     public boolean isCachingEnabled() {
285         return true;
286     }
287 
288     /***
289      * Set whether <code>XMLBeanInfo</code> caching should be enabled.
290      *
291      * @deprecated 0.5 replaced by XMlBeanInfoRegistry
292      * @param cachingEnabled ignored
293      */    
294     public void setCachingEnabled(boolean cachingEnabled) {
295         //
296     }
297      
298     
299     /*** 
300       * Should attributes (or elements) be used for primitive types.
301       * @return true if primitive types will be mapped to attributes in the introspection
302       * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives
303       */
304     public boolean isAttributesForPrimitives() {
305         return getConfiguration().isAttributesForPrimitives();
306     }
307 
308     /*** 
309       * Set whether attributes (or elements) should be used for primitive types. 
310       * @param attributesForPrimitives pass trus to map primitives to attributes,
311       *        pass false to map primitives to elements
312       * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives
313       */
314     public void setAttributesForPrimitives(boolean attributesForPrimitives) {
315         getConfiguration().setAttributesForPrimitives(attributesForPrimitives);
316     }
317 
318     /***
319      * Should collections be wrapped in an extra element?
320      * 
321      * @return whether we should we wrap collections in an extra element? 
322      * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement
323      */
324     public boolean isWrapCollectionsInElement() {
325         return getConfiguration().isWrapCollectionsInElement();
326     }
327 
328     /*** 
329      * Sets whether we should we wrap collections in an extra element.
330      *
331      * @param wrapCollectionsInElement pass true if collections should be wrapped in a
332      *        parent element
333      * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement
334      */
335     public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
336         getConfiguration().setWrapCollectionsInElement(wrapCollectionsInElement);
337     }
338 
339     /*** 
340      * Get singular and plural matching strategy.
341      *
342      * @return the strategy used to detect matching singular and plural properties 
343      * @deprecated 0.6 use getConfiguration().getPluralStemmer
344      */
345     public PluralStemmer getPluralStemmer() {
346         return getConfiguration().getPluralStemmer();
347     }
348     
349     /*** 
350      * Sets the strategy used to detect matching singular and plural properties 
351      *
352      * @param pluralStemmer the PluralStemmer used to match singular and plural
353      * @deprecated 0.6 use getConfiguration().setPluralStemmer 
354      */
355     public void setPluralStemmer(PluralStemmer pluralStemmer) {
356         getConfiguration().setPluralStemmer(pluralStemmer);
357     }
358 
359     /*** 
360      * Gets the name mapper strategy.
361      * 
362      * @return the strategy used to convert bean type names into element names
363      * @deprecated 0.5 getNameMapper is split up in 
364      * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
365      */
366     public NameMapper getNameMapper() {
367         return getElementNameMapper();
368     }
369     
370     /*** 
371      * Sets the strategy used to convert bean type names into element names
372      * @param nameMapper the NameMapper strategy to be used
373      * @deprecated 0.5 setNameMapper is split up in 
374      * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
375      */
376     public void setNameMapper(NameMapper nameMapper) {
377         setElementNameMapper(nameMapper);
378     }
379 
380 
381     /***
382      * Gets the name mapping strategy used to convert bean names into elements.
383      *
384      * @return the strategy used to convert bean type names into element 
385      * names. If no element mapper is currently defined then a default one is created.
386      * @deprecated 0.6 use getConfiguration().getElementNameMapper
387      */ 
388     public NameMapper getElementNameMapper() {
389         return getConfiguration().getElementNameMapper();
390     }
391      
392     /***
393      * Sets the strategy used to convert bean type names into element names
394      * @param nameMapper the NameMapper to use for the conversion
395      * @deprecated 0.6 use getConfiguration().setElementNameMapper
396      */
397     public void setElementNameMapper(NameMapper nameMapper) {
398         getConfiguration().setElementNameMapper( nameMapper );
399     }
400     
401 
402     /***
403      * Gets the name mapping strategy used to convert bean names into attributes.
404      *
405      * @return the strategy used to convert bean type names into attribute
406      * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
407      * @deprecated 0.6 getConfiguration().getAttributeNameMapper
408      */
409     public NameMapper getAttributeNameMapper() {
410         return getConfiguration().getAttributeNameMapper();
411      }
412 
413 
414     /***
415      * Sets the strategy used to convert bean type names into attribute names
416      * @param nameMapper the NameMapper to use for the convertion
417      * @deprecated 0.6 use getConfiguration().setAttributeNameMapper
418      */
419     public void setAttributeNameMapper(NameMapper nameMapper) {
420         getConfiguration().setAttributeNameMapper( nameMapper );
421     }
422     
423     /***
424      * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
425      * By default it will be false.
426      * 
427      * @return boolean if the beanInfoSearchPath should be used.
428      * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath
429      */
430     public boolean useBeanInfoSearchPath() {
431         return getConfiguration().useBeanInfoSearchPath();
432     }
433 
434     /***
435      * Specifies if you want to use the beanInfoSearchPath 
436      * @see java.beans.Introspector for more details
437      * @param useBeanInfoSearchPath 
438      * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath
439      */
440     public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
441         getConfiguration().setUseBeanInfoSearchPath( useBeanInfoSearchPath );
442     }
443     
444     // Methods
445     //------------------------------------------------------------------------- 
446     
447     /***
448      * Flush existing cached <code>XMLBeanInfo</code>'s.
449      *
450      * @deprecated 0.5 use flushable registry instead
451      */
452     public void flushCache() {}
453     
454     
455     /*** Create a standard <code>XMLBeanInfo</code> by introspection
456       * The actual introspection depends only on the <code>BeanInfo</code>
457       * associated with the bean.
458       * 
459       * @param bean introspect this bean
460       * @return XMLBeanInfo describing bean-xml mapping
461       * @throws IntrospectionException when the bean introspection fails
462       */
463     public XMLBeanInfo introspect(Object bean) throws IntrospectionException {
464         if (getLog().isDebugEnabled()) {
465             getLog().debug( "Introspecting..." );
466             getLog().debug(bean);
467         }
468         
469         if ( bean instanceof DynaBean ) {
470             // allow DynaBean implementations to be overridden by .betwixt files
471             XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
472             if (xmlBeanInfo != null) {
473                 return xmlBeanInfo;
474             }
475             // this is DynaBean use the DynaClass for introspection
476             return introspect( ((DynaBean) bean).getDynaClass() );
477             
478         } else {
479             // normal bean so normal introspection
480             Class normalClass = getClassNormalizer().getNormalizedClass( bean );
481             return introspect( normalClass );
482         }
483     }
484     
485     /***
486      * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
487      * Customizing DynaBeans using betwixt is not supported.
488      * 
489      * @param dynaClass the DynaBean to introspect
490      * 
491      * @return XMLBeanInfo for the DynaClass
492      */
493     public XMLBeanInfo introspect(DynaClass dynaClass) {
494 
495         // for now this method does not do much, since XMLBeanInfoRegistry cannot
496         // use a DynaClass as a key
497         // TODO: add caching for DynaClass XMLBeanInfo
498         // need to work out if this is possible
499         
500         // this line allows subclasses to change creation strategy
501         XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
502         
503         // populate the created info with 
504         DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
505         populate( xmlInfo, beanClass );
506         
507         return xmlInfo;  
508     }
509 
510     
511     /***
512      * <p>Introspects the given <code>Class</code> using the dot betwixt 
513      * document in the given <code>InputSource</code>.
514      * </p>
515      * <p>
516      * <strong>Note:</strong> that the given mapping will <em>not</em>
517      * be registered by this method. Use {@link #register(Class, InputSource)}
518      * instead.
519      * </p>
520      * @since 0.7
521      * @param aClass <code>Class</code>, not null
522      * @param source <code>InputSource</code>, not null
523      * @return <code>XMLBeanInfo</code> describing the mapping.
524      * @throws SAXException when the input source cannot be parsed
525      * @throws IOException 	
526      */
527     public synchronized XMLBeanInfo introspect(Class aClass, InputSource source) throws IOException, SAXException  {
528         // need to synchronize since we only use one instance and SAX is essentially one thread only
529         configureDigester(aClass);
530         XMLBeanInfo result = (XMLBeanInfo) digester.parse(source);
531         return result;
532     }
533     
534     
535     /*** Create a standard <code>XMLBeanInfo</code> by introspection.
536       * The actual introspection depends only on the <code>BeanInfo</code>
537       * associated with the bean.    
538       *    
539       * @param aClass introspect this class
540       * @return XMLBeanInfo describing bean-xml mapping
541       * @throws IntrospectionException when the bean introspection fails
542       */
543     public XMLBeanInfo introspect(Class aClass) throws IntrospectionException {
544         // we first reset the beaninfo searchpath.
545         String[] searchPath = null;
546         if ( !getConfiguration().useBeanInfoSearchPath() ) {
547             searchPath = Introspector.getBeanInfoSearchPath();
548             Introspector.setBeanInfoSearchPath(new String[] { });
549         }
550         
551         XMLBeanInfo xmlInfo = registry.get( aClass );
552         
553         if ( xmlInfo == null ) {
554             // lets see if we can find an XML descriptor first
555             if ( getLog().isDebugEnabled() ) {
556                 getLog().debug( "Attempting to lookup an XML descriptor for class: " + aClass );
557             }
558             
559             xmlInfo = findByXMLDescriptor( aClass );
560             if ( xmlInfo == null ) {
561                 BeanInfo info = Introspector.getBeanInfo( aClass );
562                 xmlInfo = introspect( info );
563             }
564             
565             if ( xmlInfo != null ) {
566                 registry.put( aClass, xmlInfo );
567             }
568         } else {
569             getLog().trace( "Used cached XMLBeanInfo." );
570         }
571         
572         if ( getLog().isTraceEnabled() ) {
573             getLog().trace( xmlInfo );
574         }
575         if ( !getConfiguration().useBeanInfoSearchPath() ) {
576             // we restore the beaninfo searchpath.
577             Introspector.setBeanInfoSearchPath( searchPath );
578         }
579         
580         return xmlInfo;
581     }
582     
583     /*** Create a standard <code>XMLBeanInfo</code> by introspection. 
584       * The actual introspection depends only on the <code>BeanInfo</code>
585       * associated with the bean.
586       *
587       * @param beanInfo the BeanInfo the xml-bean mapping is based on
588       * @return XMLBeanInfo describing bean-xml mapping
589       * @throws IntrospectionException when the bean introspection fails
590       */
591     public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException {    
592         XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
593         populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
594         return xmlBeanInfo;
595     }
596     
597     
598     /***
599      * <p>Registers the class mappings specified in the multi-class document
600      * given by the <code>InputSource</code>.
601      * </p>
602      * <p>
603      * <strong>Note:</strong> that this method will override any existing mapping
604      * for the speficied classes.
605      * </p>
606      * @since 0.7
607      * @param source <code>InputSource</code>, not null
608      * @return <code>Class</code> array containing all mapped classes
609      * @throws IntrospectionException
610      * @throws SAXException
611      * @throws IOException
612      */
613     public synchronized Class[] register(InputSource source) throws IntrospectionException, IOException, SAXException {
614         Map xmlBeanInfoByClass = loadMultiMapping(source);	
615         Set keySet = xmlBeanInfoByClass.keySet();
616         Class mappedClasses[] = new Class[keySet.size()];
617         int i=0;
618         for (Iterator it=keySet.iterator(); it.hasNext(); ) {
619             Class clazz = (Class) it.next();
620             mappedClasses[i++] = clazz;
621             XMLBeanInfo xmlBeanInfo = (XMLBeanInfo) xmlBeanInfoByClass.get(clazz);
622             if (xmlBeanInfo != null) {
623                 getRegistry().put(clazz, xmlBeanInfo);
624             }   
625         }
626         return mappedClasses;
627     }
628     
629     /***
630      * Loads the multi-mapping from the given <code>InputSource</code>.
631      * @param mapping <code>InputSource</code>, not null
632      * @return <code>Map</code> containing <code>XMLBeanInfo</code>'s
633      * indexes by the <code>Class</code> they describe
634      * @throws IOException
635      * @throws SAXException
636      */
637     private synchronized Map loadMultiMapping(InputSource mapping) throws IOException, SAXException {
638         // synchronized method so this digester is only used by
639         // one thread at once
640         if (multiMappingdigester == null) {
641             multiMappingdigester = new MultiMappingBeanInfoDigester();
642             multiMappingdigester.setXMLIntrospector(this);
643         }
644         Map multiBeanInfoMap = (Map) multiMappingdigester.parse(mapping);
645         return multiBeanInfoMap;
646     }
647     
648     /***
649      * <p>Registers the class mapping specified in the standard dot-betwixt file.
650      * Subsequent introspections will use this registered mapping for the class.
651      * </p>
652      * <p>
653      * <strong>Note:</strong> that this method will override any existing mapping
654      * for this class.
655      * </p>
656      * @since 0.7
657      * @param aClass <code>Class</code>, not null
658      * @param source <code>InputSource</code>, not null
659      * @throws SAXException when the source cannot be parsed
660      * @throws IOException 
661      */
662     public void register(Class aClass, InputSource source) throws IOException, SAXException  {
663         XMLBeanInfo xmlBeanInfo = introspect(aClass, source);
664         getRegistry().put(aClass, xmlBeanInfo);
665     }
666     
667     /***
668      * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
669      *
670      * @param xmlBeanInfo populate this, not null
671      * @param bean the type definition for the bean, not null
672      */
673     private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {    
674         String name = bean.getBeanName();
675         
676         ElementDescriptor elementDescriptor = new ElementDescriptor();
677         elementDescriptor.setLocalName( 
678             getElementNameMapper().mapTypeToElementName( name ) );
679         elementDescriptor.setPropertyType( bean.getElementType() );
680         
681         if (getLog().isTraceEnabled()) {
682             getLog().trace("Populating:" + bean);
683         }
684 
685         // add default string value for primitive types
686         if ( bean.isPrimitiveType() ) {
687             getLog().trace("Bean is primitive");
688             elementDescriptor.setTextExpression( StringExpression.getInstance() );
689             
690         } else {
691             
692             getLog().trace("Bean is standard type");
693             
694             boolean isLoopType = bean.isLoopType();
695             
696             List elements = new ArrayList();
697             List attributes = new ArrayList();
698             List contents = new ArrayList();
699 
700             // add bean properties for all collection which are not basic
701             if ( !( isLoopType && isBasicCollection( bean.getClass() ) ) )
702             {
703                 addProperties( bean.getProperties(), elements, attributes, contents );    
704             }
705             
706             // add iterator for collections
707             if ( isLoopType ) {
708                 getLog().trace("Bean is loop");
709                 ElementDescriptor loopDescriptor = new ElementDescriptor();
710                 loopDescriptor.setCollective(true);
711                 loopDescriptor.setContextExpression(
712                     new IteratorExpression( EmptyExpression.getInstance() )
713                 );
714                 if ( bean.isMapType() ) {
715                     loopDescriptor.setQualifiedName( "entry" );
716                 }
717                 elements.add( loopDescriptor );
718             }
719             
720             int size = elements.size();
721             if ( size > 0 ) {
722                 ElementDescriptor[] descriptors = new ElementDescriptor[size];
723                 elements.toArray( descriptors );
724                 elementDescriptor.setElementDescriptors( descriptors );
725             }
726             size = attributes.size();
727             if ( size > 0 ) {
728                 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
729                 attributes.toArray( descriptors );
730                 elementDescriptor.setAttributeDescriptors( descriptors );
731             }
732             size = contents.size();
733             if ( size > 0 ) {
734                 if ( size > 0 ) {
735                     Descriptor[] descriptors = new Descriptor[size];
736                     contents.toArray( descriptors );
737                     elementDescriptor.setContentDescriptors( descriptors );
738                 }
739             }
740         }
741         
742         xmlBeanInfo.setElementDescriptor( elementDescriptor );        
743         
744         // default any addProperty() methods
745         defaultAddMethods( elementDescriptor, bean.getElementType() );
746         
747         if (getLog().isTraceEnabled()) {
748             getLog().trace("Populated descriptor:");
749             getLog().trace(elementDescriptor);
750         }
751     }
752     
753     /***
754      * <p>Is the given type a basic collection?
755      * </p><p>
756      * This is used to determine whether a collective type
757      * should be introspected as a bean (in addition to a collection).
758      * </p>
759      * @param type <code>Class</code>, not null
760      * @return
761      */
762     private boolean isBasicCollection( Class type )
763     {
764         return type.getName().startsWith( "java.util" );
765     }
766     
767     /***
768      * Creates XMLBeanInfo for the given DynaClass.
769      * 
770      * @param dynaClass the class describing a DynaBean
771      * 
772      * @return XMLBeanInfo that describes the properties of the given 
773      * DynaClass
774      */
775     protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
776         // XXX is the chosen class right?
777         XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
778         return beanInfo;
779     }
780 
781 
782 
783 
784     /*** 
785      * Create a XML descriptor from a bean one. 
786      * Go through and work out whether it's a loop property, a primitive or a standard.
787      * The class property is ignored.
788      *
789      * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
790      * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
791      * @return a correctly configured <code>NodeDescriptor</code> for the property
792      * @throws IntrospectionException when bean introspection fails
793      * @deprecated 0.5 use {@link #createXMLDescriptor}.
794      */
795     public Descriptor createDescriptor(
796         PropertyDescriptor propertyDescriptor, 
797         boolean useAttributesForPrimitives
798     ) throws IntrospectionException {
799         return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
800     }
801  
802     /*** 
803      * Create a XML descriptor from a bean one. 
804      * Go through and work out whether it's a loop property, a primitive or a standard.
805      * The class property is ignored.
806      *
807      * @param beanProperty the BeanProperty specifying the property
808      * @return a correctly configured <code>NodeDescriptor</code> for the property
809      * @since 0.5
810      */
811     public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
812         return beanProperty.createXMLDescriptor( configuration );
813     }
814 
815 
816     /*** 
817      * Add any addPropety(PropertyType) methods as Updaters 
818      * which are often used for 1-N relationships in beans.
819      * <br>
820      * The tricky part here is finding which ElementDescriptor corresponds
821      * to the method. e.g. a property 'items' might have an Element descriptor
822      * which the method addItem() should match to. 
823      * <br>
824      * So the algorithm we'll use 
825      * by default is to take the decapitalized name of the property being added
826      * and find the first ElementDescriptor that matches the property starting with
827      * the string. This should work for most use cases. 
828      * e.g. addChild() would match the children property.
829      * <br>
830      * TODO this probably needs refactoring. It probably belongs in the bean wrapper
831      * (so that it'll work properly with dyna-beans) and so that the operations can 
832      * be optimized by caching. Multiple hash maps are created and getMethods is
833      * called multiple times. This is relatively expensive and so it'd be better
834      * to push into a proper class and cache.
835      * <br>
836      * 
837      * @param rootDescriptor add defaults to this descriptor
838      * @param beanClass the <code>Class</code> to which descriptor corresponds
839      */
840     public void defaultAddMethods( 
841                                             ElementDescriptor rootDescriptor, 
842                                             Class beanClass ) {
843         // TODO: this probably does work properly with DynaBeans: need to push
844         // implementation into an class and expose it on BeanType.  
845         
846         // lets iterate over all methods looking for one of the form
847         // add*(PropertyType)
848         if ( beanClass != null ) {
849             ArrayList singleParameterAdders = new ArrayList();
850             ArrayList twinParameterAdders = new ArrayList();
851             
852             Method[] methods = beanClass.getMethods();
853             for ( int i = 0, size = methods.length; i < size; i++ ) {
854                 Method method = methods[i];
855                 String name = method.getName();
856                 if ( name.startsWith( "add" )) {
857                     // TODO: should we filter out non-void returning methods?
858                     // some beans will return something as a helper
859                     Class[] types = method.getParameterTypes();
860                     if ( types != null) {
861                         if ( getLog().isTraceEnabled() ) {
862                             getLog().trace("Searching for match for " + method);
863                         }
864                         
865                         switch (types.length)
866                         {
867                             case 1:
868                                 singleParameterAdders.add(method);
869                                 break;
870                             case 2:
871                                 twinParameterAdders.add(method);
872                                 break;
873                             default:
874                                 // ignore
875                                 break;
876                         }
877                     }
878                 }
879             }
880             
881             Map elementsByPropertyName = makeElementDescriptorMap( rootDescriptor );
882             
883             for (Iterator it=singleParameterAdders.iterator();it.hasNext();) {
884                 Method singleParameterAdder = (Method) it.next();
885                 setIteratorAdder(elementsByPropertyName, singleParameterAdder);
886             }
887             
888             for (Iterator it=twinParameterAdders.iterator();it.hasNext();) {
889                 Method twinParameterAdder = (Method) it.next();
890                 setMapAdder(elementsByPropertyName, twinParameterAdder);
891             }
892             
893             // need to call this once all the defaults have been added
894             // so that all the singular types have been set correctly
895             configureMappingDerivation( rootDescriptor );
896         }
897     }
898     
899     /***
900      * Configures the mapping derivation according to the current
901      * <code>MappingDerivationStrategy</code> implementation.
902      * This method acts recursively.
903      * @param rootDescriptor <code>ElementDescriptor</code>, not null
904      */
905     private void configureMappingDerivation(ElementDescriptor descriptor) {
906         boolean useBindTime = getConfiguration().getMappingDerivationStrategy()
907         		.useBindTimeTypeForMapping(descriptor.getPropertyType(), descriptor.getSingularPropertyType());
908         descriptor.setUseBindTimeTypeForMapping(useBindTime);
909         ElementDescriptor[] childDescriptors = descriptor.getElementDescriptors();
910         for (int i=0, size=childDescriptors.length; i<size; i++) {
911             configureMappingDerivation(childDescriptors[i]);
912         }
913     }
914     
915     /***
916      * Sets the adder method where the corresponding property is an iterator
917      * @param rootDescriptor
918      * @param singleParameterAdder
919      */
920     private void setIteratorAdder(
921         Map elementsByPropertyName,
922         Method singleParameterAdderMethod) {
923         
924         String adderName = singleParameterAdderMethod.getName();
925         String propertyName = Introspector.decapitalize(adderName.substring(3));
926         ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
927         if (matchingDescriptor != null) {
928             //TODO defensive code: probably should check descriptor type
929             
930             Class singularType = singleParameterAdderMethod.getParameterTypes()[0];
931             if (getLog().isTraceEnabled()) {
932                 getLog().trace(adderName + "->" + propertyName);
933             }
934             // this may match a standard collection or iteration
935             getLog().trace("Matching collection or iteration");
936                                     
937             matchingDescriptor.setUpdater( new MethodUpdater( singleParameterAdderMethod ) );
938             matchingDescriptor.setSingularPropertyType( singularType );
939             matchingDescriptor.setHollow(!isPrimitiveType(singularType));
940             String localName = matchingDescriptor.getLocalName();
941             if ( localName == null || localName.length() == 0 ) {
942                 matchingDescriptor.setLocalName( 
943                     getElementNameMapper()
944                         .mapTypeToElementName( propertyName ) );
945             }
946                                     
947             if ( getLog().isDebugEnabled() ) {
948                 getLog().debug( "!! " + singleParameterAdderMethod);
949                 getLog().debug( "!! " + singularType);
950             }
951         }
952     }
953     
954     /***
955      * Sets the adder where the corresponding property type is an map
956      * @param rootDescriptor
957      * @param singleParameterAdder
958      */
959     private void setMapAdder(
960         Map elementsByPropertyName,
961         Method twinParameterAdderMethod) {
962         String adderName = twinParameterAdderMethod.getName();
963         String propertyName = Introspector.decapitalize(adderName.substring(3));
964         ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
965         if ( matchingDescriptor != null 
966             && Map.class.isAssignableFrom( matchingDescriptor.getPropertyType() )) {
967             // this may match a map
968             getLog().trace("Matching map");
969             ElementDescriptor[] children 
970                 = matchingDescriptor.getElementDescriptors();
971             // see if the descriptor's been set up properly
972             if ( children.length == 0 ) {                                        
973                 getLog().info(
974                     "'entry' descriptor is missing for map. "
975                     + "Updaters cannot be set");
976                                         
977             } else {
978                 Class[] types = twinParameterAdderMethod.getParameterTypes();
979                 Class keyType = types[0];
980                 Class valueType = types[1];
981                 
982                 // loop through children 
983                 // adding updaters for key and value
984                 MapEntryAdder adder = new MapEntryAdder(twinParameterAdderMethod);
985                 for ( 
986                     int n=0, 
987                         noOfGrandChildren = children.length;
988                     n < noOfGrandChildren;
989                     n++ ) {
990                     if ( "key".equals( children[n].getLocalName() ) ) {
991                                       
992                         children[n].setUpdater( adder.getKeyUpdater() );
993                         children[n].setSingularPropertyType(  keyType );
994                         if (children[n].getPropertyType() == null) {
995                             children[n].setPropertyType( valueType );
996                         }
997                         if ( isPrimitiveType(keyType) ) {
998                             children[n].setHollow(false);
999                         }
1000                         if ( getLog().isTraceEnabled() ) {
1001                             getLog().trace( "Key descriptor: " + children[n]);
1002                         }                                               
1003                                                 
1004                     } else if ( "value".equals( children[n].getLocalName() ) ) {
1005 
1006                         children[n].setUpdater( adder.getValueUpdater() );
1007                         children[n].setSingularPropertyType( valueType );
1008                         if (children[n].getPropertyType() == null) {
1009                             children[n].setPropertyType( valueType );
1010                         }
1011                         if ( isPrimitiveType( valueType) ) {
1012                             children[n].setHollow(false);
1013                         }
1014                         if ( isLoopType( valueType )) {
1015                             // need to attach a hollow descriptor
1016                             // don't know the element name
1017                             // so use null name (to match anything)
1018                             ElementDescriptor loopDescriptor = new ElementDescriptor();
1019                             loopDescriptor.setHollow(true);
1020                             loopDescriptor.setSingularPropertyType( valueType );
1021                             loopDescriptor.setPropertyType( valueType );
1022                             children[n].addElementDescriptor(loopDescriptor);
1023                             loopDescriptor.setCollective(true);
1024                         }
1025                         if ( getLog().isTraceEnabled() ) { 
1026                             getLog().trace( "Value descriptor: " + children[n]);
1027                         }
1028                     }
1029                 }
1030             }       
1031         }
1032     }
1033         
1034     /***
1035      * Gets an ElementDescriptor for the property matching the adder
1036      * @param adderName
1037      * @param rootDescriptor
1038      * @return
1039      */
1040     private ElementDescriptor getMatchForAdder(
1041                                                 String propertyName, 
1042                                                 Map elementsByPropertyName) {
1043         ElementDescriptor matchingDescriptor = null;
1044         if (propertyName.length() > 0) {
1045             if ( getLog().isTraceEnabled() ) {
1046                 getLog().trace( "findPluralDescriptor( " + propertyName 
1047                     + " ):root property name=" + propertyName );
1048             }
1049         
1050             PluralStemmer stemmer = getPluralStemmer();
1051             matchingDescriptor = stemmer.findPluralDescriptor( propertyName, elementsByPropertyName );
1052         
1053             if ( getLog().isTraceEnabled() ) {
1054                 getLog().trace( 
1055                     "findPluralDescriptor( " + propertyName 
1056                         + " ):ElementDescriptor=" + matchingDescriptor );
1057             }
1058         }
1059         return matchingDescriptor;
1060     }
1061     
1062     // Implementation methods
1063     //------------------------------------------------------------------------- 
1064          
1065 
1066     /***
1067      * Creates a map where the keys are the property names and the values are the ElementDescriptors
1068      */
1069     private Map makeElementDescriptorMap( ElementDescriptor rootDescriptor ) {
1070         Map result = new HashMap();
1071         String rootPropertyName = rootDescriptor.getPropertyName();
1072         if (rootPropertyName != null) {
1073             result.put(rootPropertyName, rootDescriptor);
1074         }
1075         makeElementDescriptorMap( rootDescriptor, result );
1076         return result;
1077     }
1078     
1079     /***
1080      * Creates a map where the keys are the property names and the values are the ElementDescriptors
1081      * 
1082      * @param rootDescriptor the values of the maps are the children of this 
1083      * <code>ElementDescriptor</code> index by their property names
1084      * @param map the map to which the elements will be added
1085      */
1086     private void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
1087         ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
1088         if ( children != null ) {
1089             for ( int i = 0, size = children.length; i < size; i++ ) {
1090                 ElementDescriptor child = children[i];                
1091                 String propertyName = child.getPropertyName();                
1092                 if ( propertyName != null ) {
1093                     map.put( propertyName, child );
1094                 }
1095                 makeElementDescriptorMap( child, map );
1096             }
1097         }
1098     }
1099     
1100     /*** 
1101      * A Factory method to lazily create a new strategy 
1102      * to detect matching singular and plural properties.
1103      *
1104      * @return new defualt PluralStemmer implementation
1105      * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
1106      * Those who need to vary this should subclass that class instead
1107      */
1108     protected PluralStemmer createPluralStemmer() {
1109         return new DefaultPluralStemmer();
1110     }
1111     
1112     /*** 
1113      * A Factory method to lazily create a strategy 
1114      * used to convert bean type names into element names.
1115      *
1116      * @return new default NameMapper implementation
1117      * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
1118      * Those who need to vary this should subclass that class instead
1119      */
1120     protected NameMapper createNameMapper() {
1121         return new DefaultNameMapper();
1122     }
1123     
1124     /*** 
1125      * Attempt to lookup the XML descriptor for the given class using the
1126      * classname + ".betwixt" using the same ClassLoader used to load the class
1127      * or return null if it could not be loaded
1128      * 
1129      * @param aClass digester .betwixt file for this class
1130      * @return XMLBeanInfo digested from the .betwixt file if one can be found.
1131      *         Otherwise null.
1132      */
1133     protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
1134         // trim the package name
1135         String name = aClass.getName();
1136         int idx = name.lastIndexOf( '.' );
1137         if ( idx >= 0 ) {
1138             name = name.substring( idx + 1 );
1139         }
1140         name += ".betwixt";
1141         
1142         URL url = aClass.getResource( name );
1143         if ( url != null ) {
1144             try {
1145                 String urlText = url.toString();
1146                 if ( getLog().isDebugEnabled( )) {
1147                     getLog().debug( "Parsing Betwixt XML descriptor: " + urlText );
1148                 }
1149                 // synchronized method so this digester is only used by
1150                 // one thread at once
1151                 configureDigester(aClass);
1152                 return (XMLBeanInfo) digester.parse( urlText );
1153             } catch (Exception e) {
1154                 getLog().warn( "Caught exception trying to parse: " + name, e );
1155             }
1156         }
1157         
1158         if ( getLog().isTraceEnabled() ) {
1159             getLog().trace( "Could not find betwixt file " + name );
1160         }
1161         return null;
1162     }
1163             
1164     /***
1165      * Configures the single <code>Digester</code> instance used by this introspector.
1166      * @param aClass <code>Class</code>, not null
1167      */
1168     private synchronized void configureDigester(Class aClass) {
1169         if ( digester == null ) {
1170             digester = new XMLBeanInfoDigester();
1171             digester.setXMLIntrospector( this );
1172         }
1173         digester.setBeanClass( aClass );
1174     }
1175 
1176     /*** 
1177      * Loop through properties and process each one 
1178      *
1179      * @param beanInfo the BeanInfo whose properties will be processed
1180      * @param elements ElementDescriptor list to which elements will be added
1181      * @param attributes AttributeDescriptor list to which attributes will be added
1182      * @param contents Descriptor list to which mixed content will be added
1183      * @throws IntrospectionException if the bean introspection fails
1184      * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
1185      */
1186     protected void addProperties(
1187                                     BeanInfo beanInfo, 
1188                                     List elements, 
1189                                     List attributes,
1190                                     List contents)
1191                                         throws 
1192                                             IntrospectionException {
1193         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1194         if ( descriptors != null ) {
1195             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1196                 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
1197             }
1198         }
1199         if (getLog().isTraceEnabled()) {
1200             getLog().trace(elements);
1201             getLog().trace(attributes);
1202             getLog().trace(contents);
1203         }
1204     }
1205     /*** 
1206      * Loop through properties and process each one 
1207      *
1208      * @param beanProperties the properties to be processed
1209      * @param elements ElementDescriptor list to which elements will be added
1210      * @param attributes AttributeDescriptor list to which attributes will be added
1211      * @param contents Descriptor list to which mixed content will be added
1212      * @since 0.5
1213      */
1214     protected void addProperties(
1215                                     BeanProperty[] beanProperties, 
1216                                     List elements, 
1217                                     List attributes,
1218                                     List contents) {
1219         if ( beanProperties != null ) {
1220             if (getLog().isTraceEnabled()) {
1221                 getLog().trace(beanProperties.length + " properties to be added");
1222             }
1223             for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
1224                 addProperty(beanProperties[i], elements, attributes, contents);
1225             }
1226         }
1227         if (getLog().isTraceEnabled()) {
1228             getLog().trace("After properties have been added (elements, attributes, contents):");
1229             getLog().trace(elements);
1230             getLog().trace(attributes);
1231             getLog().trace(contents);
1232         }
1233     }    
1234 
1235     
1236     /*** 
1237      * Process a property. 
1238      * Go through and work out whether it's a loop property, a primitive or a standard.
1239      * The class property is ignored.
1240      *
1241      * @param beanInfo the BeanInfo whose property is being processed
1242      * @param propertyDescriptor the PropertyDescriptor to process
1243      * @param elements ElementDescriptor list to which elements will be added
1244      * @param attributes AttributeDescriptor list to which attributes will be added
1245      * @param contents Descriptor list to which mixed content will be added
1246      * @throws IntrospectionException if the bean introspection fails
1247      * @deprecated 0.5 BeanInfo is no longer required. 
1248      * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
1249      */
1250     protected void addProperty(
1251                                 BeanInfo beanInfo, 
1252                                 PropertyDescriptor propertyDescriptor, 
1253                                 List elements, 
1254                                 List attributes,
1255                                 List contents)
1256                                     throws 
1257                                         IntrospectionException {
1258        addProperty( propertyDescriptor, elements, attributes, contents);
1259     }
1260     
1261     /*** 
1262      * Process a property. 
1263      * Go through and work out whether it's a loop property, a primitive or a standard.
1264      * The class property is ignored.
1265      *
1266      * @param propertyDescriptor the PropertyDescriptor to process
1267      * @param elements ElementDescriptor list to which elements will be added
1268      * @param attributes AttributeDescriptor list to which attributes will be added
1269      * @param contents Descriptor list to which mixed content will be added
1270      * @throws IntrospectionException if the bean introspection fails
1271      * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
1272      */
1273     protected void addProperty(
1274                                 PropertyDescriptor propertyDescriptor, 
1275                                 List elements, 
1276                                 List attributes,
1277                                 List contents)
1278                                     throws 
1279                                         IntrospectionException {
1280         addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
1281     }
1282     
1283     /*** 
1284      * Process a property. 
1285      * Go through and work out whether it's a loop property, a primitive or a standard.
1286      * The class property is ignored.
1287      *
1288      * @param beanProperty the bean property to process
1289      * @param elements ElementDescriptor list to which elements will be added
1290      * @param attributes AttributeDescriptor list to which attributes will be added
1291      * @param contents Descriptor list to which mixed content will be added
1292      * @since 0.5
1293      */
1294     protected void addProperty(
1295                                 BeanProperty beanProperty, 
1296                                 List elements, 
1297                                 List attributes,
1298                                 List contents) {
1299         Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
1300         if (nodeDescriptor == null) {
1301            return;
1302         }
1303         if (nodeDescriptor instanceof ElementDescriptor) {
1304            elements.add(nodeDescriptor);
1305         } else if (nodeDescriptor instanceof AttributeDescriptor) {
1306            attributes.add(nodeDescriptor);
1307         } else {
1308            contents.add(nodeDescriptor);
1309         }                                 
1310     }
1311     
1312     /*** 
1313      * Loop through properties and process each one 
1314      *
1315      * @param beanInfo the BeanInfo whose properties will be processed
1316      * @param elements ElementDescriptor list to which elements will be added
1317      * @param attributes AttributeDescriptor list to which attributes will be added
1318      * @throws IntrospectionException if the bean introspection fails
1319      * @deprecated 0.5 this method does not support mixed content. 
1320      * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
1321      */
1322     protected void addProperties(
1323                                     BeanInfo beanInfo, 
1324                                     List elements, 
1325                                     List attributes) 
1326                                         throws 
1327                                             IntrospectionException {
1328         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1329         if ( descriptors != null ) {
1330             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1331                 addProperty(beanInfo, descriptors[i], elements, attributes);
1332             }
1333         }
1334         if (getLog().isTraceEnabled()) {
1335             getLog().trace(elements);
1336             getLog().trace(attributes);
1337         }
1338     }
1339     
1340     /*** 
1341      * Process a property. 
1342      * Go through and work out whether it's a loop property, a primitive or a standard.
1343      * The class property is ignored.
1344      *
1345      * @param beanInfo the BeanInfo whose property is being processed
1346      * @param propertyDescriptor the PropertyDescriptor to process
1347      * @param elements ElementDescriptor list to which elements will be added
1348      * @param attributes AttributeDescriptor list to which attributes will be added
1349      * @throws IntrospectionException if the bean introspection fails
1350      * @deprecated 0.5 this method does not support mixed content. 
1351      * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
1352      */
1353     protected void addProperty(
1354                                 BeanInfo beanInfo, 
1355                                 PropertyDescriptor propertyDescriptor, 
1356                                 List elements, 
1357                                 List attributes) 
1358                                     throws 
1359                                         IntrospectionException {
1360         NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
1361             .createDescriptor(propertyDescriptor,
1362                                  isAttributesForPrimitives(),
1363                                  this);
1364         if (nodeDescriptor == null) {
1365            return;
1366         }
1367         if (nodeDescriptor instanceof ElementDescriptor) {
1368            elements.add(nodeDescriptor);
1369         } else {
1370            attributes.add(nodeDescriptor);
1371         }
1372     }
1373 
1374     
1375     /*** 
1376      * Factory method to create XMLBeanInfo instances 
1377      *
1378      * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
1379      * @return XMLBeanInfo describing the bean-xml mapping
1380      */
1381     protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
1382         XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
1383         return xmlBeanInfo;
1384     }
1385 
1386     /*** 
1387      * Is this class a loop?
1388      *
1389      * @param type the Class to test
1390      * @return true if the type is a loop type 
1391      */
1392     public boolean isLoopType(Class type) {
1393         return getConfiguration().isLoopType(type);
1394     }
1395     
1396     
1397     /*** 
1398      * Is this class a primitive?
1399      * 
1400      * @param type the Class to test
1401      * @return true for primitive types 
1402      */
1403     public boolean isPrimitiveType(Class type) {
1404         // TODO: this method will probably be deprecated when primitive types
1405         // are subsumed into the simple type concept 
1406         TypeBindingStrategy.BindingType bindingType 
1407 			= configuration.getTypeBindingStrategy().bindingType( type ) ;
1408         boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
1409         return result;
1410     }
1411 
1412     
1413     /*** Some type of pseudo-bean */
1414     private abstract class BeanType {
1415         /*** 
1416          * Gets the name for this bean type 
1417          * @return the bean type name, not null
1418          */
1419         public abstract String getBeanName();
1420         
1421         /*** 
1422          * Gets the type to be used by the associated element
1423          * @return a Class that is the type not null
1424          */
1425         public abstract Class getElementType();
1426 
1427         /***
1428          * Is this type a primitive?
1429          * @return true if this type should be treated by betwixt as a primitive
1430          */
1431         public abstract boolean isPrimitiveType();
1432         
1433         /***
1434          * is this type a map?
1435          * @return true this should be treated as a map.
1436          */
1437         public abstract boolean isMapType();
1438         
1439         /*** 
1440          * Is this type a loop?
1441          * @return true if this should be treated as a loop
1442          */
1443         public abstract boolean isLoopType();
1444         
1445         /***
1446          * Gets the properties associated with this bean.
1447          * @return the BeanProperty's, not null
1448          */
1449         public abstract BeanProperty[] getProperties();
1450         
1451         /***
1452          * Create string representation
1453          * @return something useful for logging
1454          */
1455         public String toString() {
1456             return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1457         }
1458     }
1459     
1460     /*** Supports standard Java Beans */
1461     private class JavaBeanType extends BeanType {
1462         /*** Introspected bean */
1463         private BeanInfo beanInfo;
1464         /*** Bean class */
1465         private Class beanClass;
1466         /*** Bean name */
1467         private String name;
1468         /*** Bean properties */
1469         private BeanProperty[] properties;
1470         
1471         /***
1472          * Constructs a BeanType for a standard Java Bean
1473          * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1474          */
1475         public JavaBeanType(BeanInfo beanInfo) {
1476             this.beanInfo = beanInfo;
1477             BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1478             beanClass = beanDescriptor.getBeanClass();
1479             name = beanDescriptor.getName();
1480             // Array's contain a bad character
1481             if (beanClass.isArray()) {
1482                 // called all array's Array
1483                 name = "Array";
1484             }
1485             
1486         }
1487         
1488         /*** @see BeanType #getElementType */
1489         public Class getElementType() {
1490             return beanClass;
1491         }
1492         
1493         /*** @see BeanType#getBeanName */
1494         public String getBeanName() {
1495             return name;
1496         }
1497         
1498         /*** @see BeanType#isPrimitiveType */
1499         public boolean isPrimitiveType() {
1500             return XMLIntrospector.this.isPrimitiveType( beanClass );
1501         }
1502         
1503         /*** @see BeanType#isLoopType */
1504         public boolean isLoopType() {
1505             return getConfiguration().isLoopType( beanClass );
1506         }
1507         
1508         /*** @see BeanType#isMapType */
1509         public boolean isMapType() {
1510             return Map.class.isAssignableFrom( beanClass );
1511         }
1512         
1513         /*** @see BeanType#getProperties */
1514         public BeanProperty[] getProperties() {
1515             // lazy creation
1516             if ( properties == null ) {
1517                 ArrayList propertyDescriptors = new ArrayList();
1518                 // add base bean info
1519                 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1520                 if ( descriptors != null ) {
1521                     for (int i=0, size=descriptors.length; i<size; i++) {
1522                         if (!getConfiguration().getPropertySuppressionStrategy()
1523                                 	.suppressProperty( 
1524                                             beanClass,
1525                                             descriptors[i].getPropertyType(),
1526                                             descriptors[i].getName())) {
1527                             propertyDescriptors.add( descriptors[i] );
1528                         }
1529                     }
1530                 }
1531                 
1532                 // add properties from additional bean infos
1533                 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1534                 if ( additionals != null ) {
1535                     for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1536                         BeanInfo additionalInfo = additionals[i];
1537                         descriptors = beanInfo.getPropertyDescriptors();
1538                         if ( descriptors != null ) {
1539                             for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1540                                 if (!getConfiguration().getPropertySuppressionStrategy()
1541                                     	.suppressProperty(
1542                                     	          beanClass,
1543                                                 descriptors[j].getPropertyType(),
1544                                                 descriptors[j].getName())) {
1545                                     propertyDescriptors.add( descriptors[j] );
1546                                 }
1547                             }
1548                         }
1549                     }            
1550                 }
1551                 
1552                 addAllSuperinterfaces(beanClass, propertyDescriptors);
1553                 
1554                 // what happens when size is zero?
1555                 properties = new BeanProperty[ propertyDescriptors.size() ];
1556                 int count = 0;
1557                 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1558                     PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1559                     properties[count] = new BeanProperty( propertyDescriptor );
1560                 }
1561             }
1562             return properties;
1563         }
1564         
1565         /***
1566          * Adds all super interfaces.
1567          * Super interface methods are not returned within the usual 
1568          * bean info for an interface.
1569          * @param clazz <code>Class</code>, not null
1570          * @param propertyDescriptors <code>ArrayList</code> of <code>PropertyDescriptor</code>s', not null
1571          */
1572         private void addAllSuperinterfaces(Class clazz, ArrayList propertyDescriptors) {
1573             if (clazz.isInterface()) {
1574                 Class[] superinterfaces = clazz.getInterfaces();
1575                 for (int i=0, size=superinterfaces.length; i<size; i++) {
1576                     try {
1577                         
1578                         BeanInfo beanInfo = Introspector.getBeanInfo(superinterfaces[i]);
1579                         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1580                         for (int j=0, descriptorLength=descriptors.length; j<descriptorLength ; j++) {
1581                             if (!getConfiguration().getPropertySuppressionStrategy()
1582                                 	.suppressProperty(
1583                                 	          beanClass,
1584                                             descriptors[j].getPropertyType(),
1585                                             descriptors[j].getName())) {
1586                                 propertyDescriptors.add( descriptors[j] );
1587                             }
1588                         }
1589                         addAllSuperinterfaces(superinterfaces[i], propertyDescriptors);
1590                         
1591                     } catch (IntrospectionException ex) {
1592                         log.info("Introspection on superinterface failed.", ex);
1593                     }
1594                 }
1595             }
1596         }
1597         
1598     }
1599     
1600     /*** Implementation for DynaClasses */
1601     private class DynaClassBeanType extends BeanType {
1602         /*** BeanType for this DynaClass */
1603         private DynaClass dynaClass;
1604         /*** Properties extracted in constuctor */
1605         private BeanProperty[] properties;
1606         
1607         /*** 
1608          * Constructs a BeanType for a DynaClass
1609          * @param dynaClass not null
1610          */
1611         public DynaClassBeanType(DynaClass dynaClass) {
1612             this.dynaClass = dynaClass;
1613             DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1614             properties = new BeanProperty[dynaProperties.length];
1615             for (int i=0, size=dynaProperties.length; i<size; i++) {
1616                 properties[i] = new BeanProperty(dynaProperties[i]);
1617             }
1618         }
1619         
1620         /*** @see BeanType#getBeanName */
1621         public String getBeanName() {
1622             return dynaClass.getName();
1623         }
1624         /*** @see BeanType#getElementType */
1625         public Class getElementType() {
1626             return DynaClass.class;
1627         }
1628         /*** @see BeanType#isPrimitiveType */
1629         public boolean isPrimitiveType() {
1630             return false;
1631         }
1632         /*** @see BeanType#isMapType */
1633         public boolean isMapType() {
1634             return false;
1635         }
1636         /*** @see BeanType#isLoopType */
1637         public boolean isLoopType() {
1638             return false;
1639         }
1640         /*** @see BeanType#getProperties */
1641         public BeanProperty[] getProperties() {
1642             return properties;
1643         }
1644     }
1645 }