001    package org.apache.commons.betwixt;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     * 
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     * 
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */ 
019    
020    import java.beans.BeanDescriptor;
021    import java.beans.BeanInfo;
022    import java.beans.IntrospectionException;
023    import java.beans.Introspector;
024    import java.beans.PropertyDescriptor;
025    import java.io.IOException;
026    import java.lang.reflect.Method;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.HashMap;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Set;
034    
035    import org.apache.commons.beanutils.DynaBean;
036    import org.apache.commons.beanutils.DynaClass;
037    import org.apache.commons.beanutils.DynaProperty;
038    import org.apache.commons.betwixt.digester.MultiMappingBeanInfoDigester;
039    import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
040    import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
041    import org.apache.commons.betwixt.expression.CollectionUpdater;
042    import org.apache.commons.betwixt.expression.EmptyExpression;
043    import org.apache.commons.betwixt.expression.IteratorExpression;
044    import org.apache.commons.betwixt.expression.MapEntryAdder;
045    import org.apache.commons.betwixt.expression.MethodUpdater;
046    import org.apache.commons.betwixt.expression.StringExpression;
047    import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
048    import org.apache.commons.betwixt.registry.PolymorphicReferenceResolver;
049    import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
050    import org.apache.commons.betwixt.strategy.ClassNormalizer;
051    import org.apache.commons.betwixt.strategy.DefaultNameMapper;
052    import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
053    import org.apache.commons.betwixt.strategy.NameMapper;
054    import org.apache.commons.betwixt.strategy.PluralStemmer;
055    import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
056    import org.apache.commons.logging.Log;
057    import org.apache.commons.logging.LogFactory;
058    import org.xml.sax.InputSource;
059    import org.xml.sax.SAXException;
060    
061    /** 
062      * <p><code>XMLIntrospector</code> an introspector of beans to create a 
063      * XMLBeanInfo instance.</p>
064      *
065      * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
066      * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
067      * for a particular class, the <code>XMLBeanInfo</code> is cached.
068      * Later requests for the same class will return the cached value.</p>
069      * 
070      * <p>Note :</p>
071      * <p>This class makes use of the <code>java.bean.Introspector</code>
072      * class, which contains a BeanInfoSearchPath. To make sure betwixt can
073      * do his work correctly, this searchpath is completely ignored during 
074      * processing. The original values will be restored after processing finished
075      * </p>
076      * 
077      * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
078      * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a>
079      */
080    public class XMLIntrospector {
081        /** 
082         * Log used for logging (Doh!) 
083         * @deprecated 0.6 use the {@link #getLog()} property instead
084         */    
085        protected Log log = LogFactory.getLog( XMLIntrospector.class );
086        
087        /** Maps classes to <code>XMLBeanInfo</code>'s */
088        private XMLBeanInfoRegistry registry;
089        
090        /** Digester used to parse the XML descriptor files */
091        private XMLBeanInfoDigester digester;
092    
093        /** Digester used to parse the multi-mapping XML descriptor files */
094        private MultiMappingBeanInfoDigester multiMappingdigester;
095        
096        /** Configuration to be used for introspection*/
097        private IntrospectionConfiguration configuration;
098        
099        /**
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    }