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         digester.setBeanClass( aClass );
1248     }
1249 
1250     /*** 
1251      * Loop through properties and process each one 
1252      *
1253      * @param beanInfo the BeanInfo whose properties will be processed
1254      * @param elements ElementDescriptor list to which elements will be added
1255      * @param attributes AttributeDescriptor list to which attributes will be added
1256      * @param contents Descriptor list to which mixed content will be added
1257      * @throws IntrospectionException if the bean introspection fails
1258      * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
1259      */
1260     protected void addProperties(
1261                                     BeanInfo beanInfo, 
1262                                     List elements, 
1263                                     List attributes,
1264                                     List contents)
1265                                         throws 
1266                                             IntrospectionException {
1267         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1268         if ( descriptors != null ) {
1269             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1270                 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
1271             }
1272         }
1273         if (getLog().isTraceEnabled()) {
1274             getLog().trace(elements);
1275             getLog().trace(attributes);
1276             getLog().trace(contents);
1277         }
1278     }
1279     /*** 
1280      * Loop through properties and process each one 
1281      *
1282      * @param beanProperties the properties to be processed
1283      * @param elements ElementDescriptor list to which elements will be added
1284      * @param attributes AttributeDescriptor list to which attributes will be added
1285      * @param contents Descriptor list to which mixed content will be added
1286      * @since 0.5
1287      */
1288     protected void addProperties(
1289                                     BeanProperty[] beanProperties, 
1290                                     List elements, 
1291                                     List attributes,
1292                                     List contents) {
1293         if ( beanProperties != null ) {
1294             if (getLog().isTraceEnabled()) {
1295                 getLog().trace(beanProperties.length + " properties to be added");
1296             }
1297             for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
1298                 addProperty(beanProperties[i], elements, attributes, contents);
1299             }
1300         }
1301         if (getLog().isTraceEnabled()) {
1302             getLog().trace("After properties have been added (elements, attributes, contents):");
1303             getLog().trace(elements);
1304             getLog().trace(attributes);
1305             getLog().trace(contents);
1306         }
1307     }    
1308 
1309     
1310     /*** 
1311      * Process a property. 
1312      * Go through and work out whether it's a loop property, a primitive or a standard.
1313      * The class property is ignored.
1314      *
1315      * @param beanInfo the BeanInfo whose property is being processed
1316      * @param propertyDescriptor the PropertyDescriptor to process
1317      * @param elements ElementDescriptor list to which elements will be added
1318      * @param attributes AttributeDescriptor list to which attributes will be added
1319      * @param contents Descriptor list to which mixed content will be added
1320      * @throws IntrospectionException if the bean introspection fails
1321      * @deprecated 0.5 BeanInfo is no longer required. 
1322      * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
1323      */
1324     protected void addProperty(
1325                                 BeanInfo beanInfo, 
1326                                 PropertyDescriptor propertyDescriptor, 
1327                                 List elements, 
1328                                 List attributes,
1329                                 List contents)
1330                                     throws 
1331                                         IntrospectionException {
1332        addProperty( propertyDescriptor, elements, attributes, contents);
1333     }
1334     
1335     /*** 
1336      * Process a property. 
1337      * Go through and work out whether it's a loop property, a primitive or a standard.
1338      * The class property is ignored.
1339      *
1340      * @param propertyDescriptor the PropertyDescriptor to process
1341      * @param elements ElementDescriptor list to which elements will be added
1342      * @param attributes AttributeDescriptor list to which attributes will be added
1343      * @param contents Descriptor list to which mixed content will be added
1344      * @throws IntrospectionException if the bean introspection fails
1345      * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
1346      */
1347     protected void addProperty(
1348                                 PropertyDescriptor propertyDescriptor, 
1349                                 List elements, 
1350                                 List attributes,
1351                                 List contents)
1352                                     throws 
1353                                         IntrospectionException {
1354         addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
1355     }
1356     
1357     /*** 
1358      * Process a property. 
1359      * Go through and work out whether it's a loop property, a primitive or a standard.
1360      * The class property is ignored.
1361      *
1362      * @param beanProperty the bean property to process
1363      * @param elements ElementDescriptor list to which elements will be added
1364      * @param attributes AttributeDescriptor list to which attributes will be added
1365      * @param contents Descriptor list to which mixed content will be added
1366      * @since 0.5
1367      */
1368     protected void addProperty(
1369                                 BeanProperty beanProperty, 
1370                                 List elements, 
1371                                 List attributes,
1372                                 List contents) {
1373         Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
1374         if (nodeDescriptor == null) {
1375            return;
1376         }
1377         if (nodeDescriptor instanceof ElementDescriptor) {
1378            elements.add(nodeDescriptor);
1379         } else if (nodeDescriptor instanceof AttributeDescriptor) {
1380            attributes.add(nodeDescriptor);
1381         } else {
1382            contents.add(nodeDescriptor);
1383         }                                 
1384     }
1385     
1386     /*** 
1387      * Loop through properties and process each one 
1388      *
1389      * @param beanInfo the BeanInfo whose properties will be processed
1390      * @param elements ElementDescriptor list to which elements will be added
1391      * @param attributes AttributeDescriptor list to which attributes will be added
1392      * @throws IntrospectionException if the bean introspection fails
1393      * @deprecated 0.5 this method does not support mixed content. 
1394      * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
1395      */
1396     protected void addProperties(
1397                                     BeanInfo beanInfo, 
1398                                     List elements, 
1399                                     List attributes) 
1400                                         throws 
1401                                             IntrospectionException {
1402         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1403         if ( descriptors != null ) {
1404             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1405                 addProperty(beanInfo, descriptors[i], elements, attributes);
1406             }
1407         }
1408         if (getLog().isTraceEnabled()) {
1409             getLog().trace(elements);
1410             getLog().trace(attributes);
1411         }
1412     }
1413     
1414     /*** 
1415      * Process a property. 
1416      * Go through and work out whether it's a loop property, a primitive or a standard.
1417      * The class property is ignored.
1418      *
1419      * @param beanInfo the BeanInfo whose property is being processed
1420      * @param propertyDescriptor the PropertyDescriptor to process
1421      * @param elements ElementDescriptor list to which elements will be added
1422      * @param attributes AttributeDescriptor list to which attributes will be added
1423      * @throws IntrospectionException if the bean introspection fails
1424      * @deprecated 0.5 this method does not support mixed content. 
1425      * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
1426      */
1427     protected void addProperty(
1428                                 BeanInfo beanInfo, 
1429                                 PropertyDescriptor propertyDescriptor, 
1430                                 List elements, 
1431                                 List attributes) 
1432                                     throws 
1433                                         IntrospectionException {
1434         NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
1435             .createDescriptor(propertyDescriptor,
1436                                  isAttributesForPrimitives(),
1437                                  this);
1438         if (nodeDescriptor == null) {
1439            return;
1440         }
1441         if (nodeDescriptor instanceof ElementDescriptor) {
1442            elements.add(nodeDescriptor);
1443         } else {
1444            attributes.add(nodeDescriptor);
1445         }
1446     }
1447 
1448     
1449     /*** 
1450      * Factory method to create XMLBeanInfo instances 
1451      *
1452      * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
1453      * @return XMLBeanInfo describing the bean-xml mapping
1454      */
1455     protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
1456         XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
1457         return xmlBeanInfo;
1458     }
1459 
1460     /*** 
1461      * Is this class a loop?
1462      *
1463      * @param type the Class to test
1464      * @return true if the type is a loop type 
1465      */
1466     public boolean isLoopType(Class type) {
1467         return getConfiguration().isLoopType(type);
1468     }
1469     
1470     
1471     /*** 
1472      * Is this class a primitive?
1473      * 
1474      * @param type the Class to test
1475      * @return true for primitive types 
1476      */
1477     public boolean isPrimitiveType(Class type) {
1478         // TODO: this method will probably be deprecated when primitive types
1479         // are subsumed into the simple type concept 
1480         TypeBindingStrategy.BindingType bindingType 
1481 			= configuration.getTypeBindingStrategy().bindingType( type ) ;
1482         boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
1483         return result;
1484     }
1485 
1486     
1487     /*** Some type of pseudo-bean */
1488     private abstract class BeanType {
1489         /*** 
1490          * Gets the name for this bean type 
1491          * @return the bean type name, not null
1492          */
1493         public abstract String getBeanName();
1494         
1495         /*** 
1496          * Gets the type to be used by the associated element
1497          * @return a Class that is the type not null
1498          */
1499         public abstract Class getElementType();
1500 
1501         /***
1502          * Is this type a primitive?
1503          * @return true if this type should be treated by betwixt as a primitive
1504          */
1505         public abstract boolean isPrimitiveType();
1506         
1507         /***
1508          * is this type a map?
1509          * @return true this should be treated as a map.
1510          */
1511         public abstract boolean isMapType();
1512         
1513         /*** 
1514          * Is this type a loop?
1515          * @return true if this should be treated as a loop
1516          */
1517         public abstract boolean isLoopType();
1518         
1519         /***
1520          * Gets the properties associated with this bean.
1521          * @return the BeanProperty's, not null
1522          */
1523         public abstract BeanProperty[] getProperties();
1524         
1525         /***
1526          * Create string representation
1527          * @return something useful for logging
1528          */
1529         public String toString() {
1530             return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1531         }
1532     }
1533     
1534     /*** Supports standard Java Beans */
1535     private class JavaBeanType extends BeanType {
1536         /*** Introspected bean */
1537         private BeanInfo beanInfo;
1538         /*** Bean class */
1539         private Class beanClass;
1540         /*** Bean name */
1541         private String name;
1542         /*** Bean properties */
1543         private BeanProperty[] properties;
1544         
1545         /***
1546          * Constructs a BeanType for a standard Java Bean
1547          * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1548          */
1549         public JavaBeanType(BeanInfo beanInfo) {
1550             this.beanInfo = beanInfo;
1551             BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1552             beanClass = beanDescriptor.getBeanClass();
1553             name = beanDescriptor.getName();
1554             // Array's contain a bad character
1555             if (beanClass.isArray()) {
1556                 // called all array's Array
1557                 name = "Array";
1558             }
1559             
1560         }
1561         
1562         /*** @see BeanType #getElementType */
1563         public Class getElementType() {
1564             return beanClass;
1565         }
1566         
1567         /*** @see BeanType#getBeanName */
1568         public String getBeanName() {
1569             return name;
1570         }
1571         
1572         /*** @see BeanType#isPrimitiveType */
1573         public boolean isPrimitiveType() {
1574             return XMLIntrospector.this.isPrimitiveType( beanClass );
1575         }
1576         
1577         /*** @see BeanType#isLoopType */
1578         public boolean isLoopType() {
1579             return getConfiguration().isLoopType( beanClass );
1580         }
1581         
1582         /*** @see BeanType#isMapType */
1583         public boolean isMapType() {
1584             return Map.class.isAssignableFrom( beanClass );
1585         }
1586         
1587         /*** @see BeanType#getProperties */
1588         public BeanProperty[] getProperties() {
1589             // lazy creation
1590             if ( properties == null ) {
1591                 ArrayList propertyDescriptors = new ArrayList();
1592                 // add base bean info
1593                 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1594                 if ( descriptors != null ) {
1595                     for (int i=0, size=descriptors.length; i<size; i++) {
1596                         if (!getConfiguration().getPropertySuppressionStrategy()
1597                                 	.suppressProperty( 
1598                                             beanClass,
1599                                             descriptors[i].getPropertyType(),
1600                                             descriptors[i].getName())) {
1601                             propertyDescriptors.add( descriptors[i] );
1602                         }
1603                     }
1604                 }
1605                 
1606                 // add properties from additional bean infos
1607                 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1608                 if ( additionals != null ) {
1609                     for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1610                         BeanInfo additionalInfo = additionals[i];
1611                         descriptors = additionalInfo.getPropertyDescriptors();
1612                         if ( descriptors != null ) {
1613                             for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1614                                 if (!getConfiguration().getPropertySuppressionStrategy()
1615                                     	.suppressProperty(
1616                                     	          beanClass,
1617                                                 descriptors[j].getPropertyType(),
1618                                                 descriptors[j].getName())) {
1619                                     propertyDescriptors.add( descriptors[j] );
1620                                 }
1621                             }
1622                         }
1623                     }            
1624                 }
1625                 
1626                 addAllSuperinterfaces(beanClass, propertyDescriptors);
1627                 
1628                 // what happens when size is zero?
1629                 properties = new BeanProperty[ propertyDescriptors.size() ];
1630                 int count = 0;
1631                 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1632                     PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1633                     properties[count] = new BeanProperty( propertyDescriptor );
1634                 }
1635             }
1636             return properties;
1637         }
1638         
1639         /***
1640          * Adds all super interfaces.
1641          * Super interface methods are not returned within the usual 
1642          * bean info for an interface.
1643          * @param clazz <code>Class</code>, not null
1644          * @param propertyDescriptors <code>ArrayList</code> of <code>PropertyDescriptor</code>s', not null
1645          */
1646         private void addAllSuperinterfaces(Class clazz, ArrayList propertyDescriptors) {
1647             if (clazz.isInterface()) {
1648                 Class[] superinterfaces = clazz.getInterfaces();
1649                 for (int i=0, size=superinterfaces.length; i<size; i++) {
1650                     try {
1651                         
1652                         BeanInfo beanInfo;
1653                         if( getConfiguration().ignoreAllBeanInfo() ) {
1654                             beanInfo = Introspector.getBeanInfo( superinterfaces[i], Introspector.IGNORE_ALL_BEANINFO );
1655                         }
1656                         else {
1657                             beanInfo = Introspector.getBeanInfo( superinterfaces[i] );
1658                         }
1659                         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1660                         for (int j=0, descriptorLength=descriptors.length; j<descriptorLength ; j++) {
1661                             if (!getConfiguration().getPropertySuppressionStrategy()
1662                                 	.suppressProperty(
1663                                 	          beanClass,
1664                                             descriptors[j].getPropertyType(),
1665                                             descriptors[j].getName())) {
1666                                 propertyDescriptors.add( descriptors[j] );
1667                             }
1668                         }
1669                         addAllSuperinterfaces(superinterfaces[i], propertyDescriptors);
1670                         
1671                     } catch (IntrospectionException ex) {
1672                         log.info("Introspection on superinterface failed.", ex);
1673                     }
1674                 }
1675             }
1676         }
1677         
1678     }
1679     
1680     /*** Implementation for DynaClasses */
1681     private class DynaClassBeanType extends BeanType {
1682         /*** BeanType for this DynaClass */
1683         private DynaClass dynaClass;
1684         /*** Properties extracted in constuctor */
1685         private BeanProperty[] properties;
1686         
1687         /*** 
1688          * Constructs a BeanType for a DynaClass
1689          * @param dynaClass not null
1690          */
1691         public DynaClassBeanType(DynaClass dynaClass) {
1692             this.dynaClass = dynaClass;
1693             DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1694             properties = new BeanProperty[dynaProperties.length];
1695             for (int i=0, size=dynaProperties.length; i<size; i++) {
1696                 properties[i] = new BeanProperty(dynaProperties[i]);
1697             }
1698         }
1699         
1700         /*** @see BeanType#getBeanName */
1701         public String getBeanName() {
1702             return dynaClass.getName();
1703         }
1704         /*** @see BeanType#getElementType */
1705         public Class getElementType() {
1706             return DynaClass.class;
1707         }
1708         /*** @see BeanType#isPrimitiveType */
1709         public boolean isPrimitiveType() {
1710             return false;
1711         }
1712         /*** @see BeanType#isMapType */
1713         public boolean isMapType() {
1714             return false;
1715         }
1716         /*** @see BeanType#isLoopType */
1717         public boolean isLoopType() {
1718             return false;
1719         }
1720         /*** @see BeanType#getProperties */
1721         public BeanProperty[] getProperties() {
1722             return properties;
1723         }
1724     }
1725 }