View Javadoc

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