KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > betwixt > XMLIntrospector


1 package org.apache.commons.betwixt;
2
3 /*
4  * Copyright 2001-2004 The Apache Software Foundation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */

18
19 import java.beans.BeanDescriptor JavaDoc;
20 import java.beans.BeanInfo JavaDoc;
21 import java.beans.IntrospectionException JavaDoc;
22 import java.beans.Introspector JavaDoc;
23 import java.beans.PropertyDescriptor JavaDoc;
24 import java.lang.reflect.Method JavaDoc;
25 import java.net.URL JavaDoc;
26 import java.util.ArrayList JavaDoc;
27 import java.util.HashMap JavaDoc;
28 import java.util.Iterator JavaDoc;
29 import java.util.List JavaDoc;
30 import java.util.Map JavaDoc;
31
32 import org.apache.commons.beanutils.DynaBean;
33 import org.apache.commons.beanutils.DynaClass;
34 import org.apache.commons.beanutils.DynaProperty;
35 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester;
36 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper;
37 import org.apache.commons.betwixt.expression.EmptyExpression;
38 import org.apache.commons.betwixt.expression.IteratorExpression;
39 import org.apache.commons.betwixt.expression.MapEntryAdder;
40 import org.apache.commons.betwixt.expression.MethodUpdater;
41 import org.apache.commons.betwixt.expression.StringExpression;
42 import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry;
43 import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry;
44 import org.apache.commons.betwixt.strategy.ClassNormalizer;
45 import org.apache.commons.betwixt.strategy.DefaultNameMapper;
46 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer;
47 import org.apache.commons.betwixt.strategy.NameMapper;
48 import org.apache.commons.betwixt.strategy.PluralStemmer;
49 import org.apache.commons.betwixt.strategy.TypeBindingStrategy;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52
53 /**
54   * <p><code>XMLIntrospector</code> an introspector of beans to create a
55   * XMLBeanInfo instance.</p>
56   *
57   * <p>By default, <code>XMLBeanInfo</code> caching is switched on.
58   * This means that the first time that a request is made for a <code>XMLBeanInfo</code>
59   * for a particular class, the <code>XMLBeanInfo</code> is cached.
60   * Later requests for the same class will return the cached value.</p>
61   *
62   * <p>Note :</p>
63   * <p>This class makes use of the <code>java.bean.Introspector</code>
64   * class, which contains a BeanInfoSearchPath. To make sure betwixt can
65   * do his work correctly, this searchpath is completely ignored during
66   * processing. The original values will be restored after processing finished
67   * </p>
68   *
69   * @author <a HREF="mailto:jstrachan@apache.org">James Strachan</a>
70   * @author <a HREF="mailto:martin@mvdb.net">Martin van den Bemt</a>
71   */

72 public class XMLIntrospector {
73     /**
74      * Log used for logging (Doh!)
75      * @deprecated 0.6 use the {@link #getLog()} property instead
76      */

77     protected Log log = LogFactory.getLog( XMLIntrospector.class );
78     
79     /** Maps classes to <code>XMLBeanInfo</code>'s */
80     private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry();
81     
82     /** Digester used to parse the XML descriptor files */
83     private XMLBeanInfoDigester digester;
84
85     /** Configuration to be used for introspection*/
86     private IntrospectionConfiguration configuration;
87     
88     /** Base constructor */
89     public XMLIntrospector() {
90         this(new IntrospectionConfiguration());
91     }
92     
93     /**
94      * Construct allows a custom configuration to be set on construction.
95      * This allows <code>IntrospectionConfiguration</code> subclasses
96      * to be easily used.
97      * @param configuration IntrospectionConfiguration, not null
98      */

99     public XMLIntrospector(IntrospectionConfiguration configuration) {
100         setConfiguration(configuration);
101     }
102     
103     
104     // Properties
105
//-------------------------------------------------------------------------
106

107     /**
108      * <p>Gets the current logging implementation. </p>
109      * @return the Log implementation which this class logs to
110      */

111     public Log getLog() {
112         return getConfiguration().getIntrospectionLog();
113     }
114
115     /**
116      * <p>Sets the current logging implementation.</p>
117      * @param log the Log implementation to use for logging
118      */

119     public void setLog(Log log) {
120         getConfiguration().setIntrospectionLog(log);
121     }
122     
123     /**
124      * <p>Gets the current registry implementation.
125      * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
126      * before introspecting.
127      * After standard introspection is complete, the instance will be passed to the registry.</p>
128      *
129      * <p>This allows finely grained control over the caching strategy.
130      * It also allows the standard introspection mechanism
131      * to be overridden on a per class basis.</p>
132      *
133      * @return the XMLBeanInfoRegistry currently used
134      */

135     public XMLBeanInfoRegistry getRegistry() {
136         return registry;
137     }
138     
139     /**
140      * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation.
141      * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class
142      * before introspecting.
143      * After standard introspection is complete, the instance will be passed to the registry.</p>
144      *
145      * <p>This allows finely grained control over the caching strategy.
146      * It also allows the standard introspection mechanism
147      * to be overridden on a per class basis.</p>
148      *
149      * @param registry the XMLBeanInfoRegistry to use
150      */

151     public void setRegistry(XMLBeanInfoRegistry registry) {
152         this.registry = registry;
153     }
154     
155     /**
156      * Gets the configuration to be used for introspection.
157      * The various introspection-time strategies
158      * and configuration variables have been consolidated as properties
159      * of this bean.
160      * This allows the configuration to be more easily shared.
161      * @return IntrospectionConfiguration, not null
162      */

163     public IntrospectionConfiguration getConfiguration() {
164         return configuration;
165     }
166
167     /**
168      * Sets the configuration to be used for introspection.
169      * The various introspection-time strategies
170      * and configuration variables have been consolidated as properties
171      * of this bean.
172      * This allows the configuration to be more easily shared.
173      * @param configuration IntrospectionConfiguration, not null
174      */

175     public void setConfiguration(IntrospectionConfiguration configuration) {
176         this.configuration = configuration;
177     }
178     
179     
180     /**
181       * Gets the <code>ClassNormalizer</code> strategy.
182       * This is used to determine the Class to be introspected
183       * (the normalized Class).
184       *
185       * @return the <code>ClassNormalizer</code> used to determine the Class to be introspected
186       * for a given Object.
187       * @deprecated 0.6 use getConfiguration().getClassNormalizer
188       * @since 0.5
189       */

190     public ClassNormalizer getClassNormalizer() {
191         return getConfiguration().getClassNormalizer();
192     }
193     
194     /**
195       * Sets the <code>ClassNormalizer</code> strategy.
196       * This is used to determine the Class to be introspected
197       * (the normalized Class).
198       *
199       * @param classNormalizer the <code>ClassNormalizer</code> to be used to determine
200       * the Class to be introspected for a given Object.
201       * @deprecated 0.6 use getConfiguration().setClassNormalizer
202       * @since 0.5
203       *
204       */

205     public void setClassNormalizer(ClassNormalizer classNormalizer) {
206         getConfiguration().setClassNormalizer(classNormalizer);
207     }
208     
209     /**
210      * Is <code>XMLBeanInfo</code> caching enabled?
211      *
212      * @deprecated 0.5 replaced by XMlBeanInfoRegistry
213      * @return true if caching is enabled
214      */

215     public boolean isCachingEnabled() {
216         return true;
217     }
218
219     /**
220      * Set whether <code>XMLBeanInfo</code> caching should be enabled.
221      *
222      * @deprecated 0.5 replaced by XMlBeanInfoRegistry
223      * @param cachingEnabled ignored
224      */

225     public void setCachingEnabled(boolean cachingEnabled) {
226         //
227
}
228      
229     
230     /**
231       * Should attributes (or elements) be used for primitive types.
232       * @return true if primitive types will be mapped to attributes in the introspection
233       * @deprecated 0.6 use getConfiguration().isAttributesForPrimitives
234       */

235     public boolean isAttributesForPrimitives() {
236         return getConfiguration().isAttributesForPrimitives();
237     }
238
239     /**
240       * Set whether attributes (or elements) should be used for primitive types.
241       * @param attributesForPrimitives pass trus to map primitives to attributes,
242       * pass false to map primitives to elements
243       * @deprecated 0.6 use getConfiguration().setAttributesForPrimitives
244       */

245     public void setAttributesForPrimitives(boolean attributesForPrimitives) {
246         getConfiguration().setAttributesForPrimitives(attributesForPrimitives);
247     }
248
249     /**
250      * Should collections be wrapped in an extra element?
251      *
252      * @return whether we should we wrap collections in an extra element?
253      * @deprecated 0.6 use getConfiguration().isWrapCollectionsInElement
254      */

255     public boolean isWrapCollectionsInElement() {
256         return getConfiguration().isWrapCollectionsInElement();
257     }
258
259     /**
260      * Sets whether we should we wrap collections in an extra element.
261      *
262      * @param wrapCollectionsInElement pass true if collections should be wrapped in a
263      * parent element
264      * @deprecated 0.6 use getConfiguration().setWrapCollectionsInElement
265      */

266     public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) {
267         getConfiguration().setWrapCollectionsInElement(wrapCollectionsInElement);
268     }
269
270     /**
271      * Get singular and plural matching strategy.
272      *
273      * @return the strategy used to detect matching singular and plural properties
274      * @deprecated 0.6 use getConfiguration().getPluralStemmer
275      */

276     public PluralStemmer getPluralStemmer() {
277         return getConfiguration().getPluralStemmer();
278     }
279     
280     /**
281      * Sets the strategy used to detect matching singular and plural properties
282      *
283      * @param pluralStemmer the PluralStemmer used to match singular and plural
284      * @deprecated 0.6 use getConfiguration().setPluralStemmer
285      */

286     public void setPluralStemmer(PluralStemmer pluralStemmer) {
287         getConfiguration().setPluralStemmer(pluralStemmer);
288     }
289
290     /**
291      * Gets the name mapper strategy.
292      *
293      * @return the strategy used to convert bean type names into element names
294      * @deprecated 0.5 getNameMapper is split up in
295      * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()}
296      */

297     public NameMapper getNameMapper() {
298         return getElementNameMapper();
299     }
300     
301     /**
302      * Sets the strategy used to convert bean type names into element names
303      * @param nameMapper the NameMapper strategy to be used
304      * @deprecated 0.5 setNameMapper is split up in
305      * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)}
306      */

307     public void setNameMapper(NameMapper nameMapper) {
308         setElementNameMapper(nameMapper);
309     }
310
311
312     /**
313      * Gets the name mapping strategy used to convert bean names into elements.
314      *
315      * @return the strategy used to convert bean type names into element
316      * names. If no element mapper is currently defined then a default one is created.
317      * @deprecated 0.6 use getConfiguration().getElementNameMapper
318      */

319     public NameMapper getElementNameMapper() {
320         return getConfiguration().getElementNameMapper();
321     }
322      
323     /**
324      * Sets the strategy used to convert bean type names into element names
325      * @param nameMapper the NameMapper to use for the conversion
326      * @deprecated 0.6 use getConfiguration().setElementNameMapper
327      */

328     public void setElementNameMapper(NameMapper nameMapper) {
329         getConfiguration().setElementNameMapper( nameMapper );
330     }
331     
332
333     /**
334      * Gets the name mapping strategy used to convert bean names into attributes.
335      *
336      * @return the strategy used to convert bean type names into attribute
337      * names. If no attributeNamemapper is known, it will default to the ElementNameMapper
338      * @deprecated 0.6 getConfiguration().getAttributeNameMapper
339      */

340     public NameMapper getAttributeNameMapper() {
341         return getConfiguration().getAttributeNameMapper();
342      }
343
344
345     /**
346      * Sets the strategy used to convert bean type names into attribute names
347      * @param nameMapper the NameMapper to use for the convertion
348      * @deprecated 0.6 use getConfiguration().setAttributeNameMapper
349      */

350     public void setAttributeNameMapper(NameMapper nameMapper) {
351         getConfiguration().setAttributeNameMapper( nameMapper );
352     }
353     
354     /**
355      * Should the original <code>java.reflect.Introspector</code> bean info search path be used?
356      * By default it will be false.
357      *
358      * @return boolean if the beanInfoSearchPath should be used.
359      * @deprecated 0.6 use getConfiguration().useBeanInfoSearchPath
360      */

361     public boolean useBeanInfoSearchPath() {
362         return getConfiguration().useBeanInfoSearchPath();
363     }
364
365     /**
366      * Specifies if you want to use the beanInfoSearchPath
367      * @see java.beans.Introspector for more details
368      * @param useBeanInfoSearchPath
369      * @deprecated 0.6 use getConfiguration().setUseBeanInfoSearchPath
370      */

371     public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) {
372         getConfiguration().setUseBeanInfoSearchPath( useBeanInfoSearchPath );
373     }
374     
375     // Methods
376
//-------------------------------------------------------------------------
377

378     /**
379      * Flush existing cached <code>XMLBeanInfo</code>'s.
380      *
381      * @deprecated 0.5 use flushable registry instead
382      */

383     public void flushCache() {}
384     
385     
386     /** Create a standard <code>XMLBeanInfo</code> by introspection
387       * The actual introspection depends only on the <code>BeanInfo</code>
388       * associated with the bean.
389       *
390       * @param bean introspect this bean
391       * @return XMLBeanInfo describing bean-xml mapping
392       * @throws IntrospectionException when the bean introspection fails
393       */

394     public XMLBeanInfo introspect(Object JavaDoc bean) throws IntrospectionException JavaDoc {
395         if (getLog().isDebugEnabled()) {
396             getLog().debug( "Introspecting..." );
397             getLog().debug(bean);
398         }
399         
400         if ( bean instanceof DynaBean ) {
401             // allow DynaBean implementations to be overridden by .betwixt files
402
XMLBeanInfo xmlBeanInfo = findByXMLDescriptor( bean.getClass() );
403             if (xmlBeanInfo != null) {
404                 return xmlBeanInfo;
405             }
406             // this is DynaBean use the DynaClass for introspection
407
return introspect( ((DynaBean) bean).getDynaClass() );
408             
409         } else {
410             // normal bean so normal introspection
411
Class JavaDoc normalClass = getClassNormalizer().getNormalizedClass( bean );
412             return introspect( normalClass );
413         }
414     }
415     
416     /**
417      * Creates XMLBeanInfo by reading the DynaProperties of a DynaBean.
418      * Customizing DynaBeans using betwixt is not supported.
419      *
420      * @param dynaClass the DynaBean to introspect
421      *
422      * @return XMLBeanInfo for the DynaClass
423      */

424     public XMLBeanInfo introspect(DynaClass dynaClass) {
425
426         // for now this method does not do much, since XMLBeanInfoRegistry cannot
427
// use a DynaClass as a key
428
// TODO: add caching for DynaClass XMLBeanInfo
429
// need to work out if this is possible
430

431         // this line allows subclasses to change creation strategy
432
XMLBeanInfo xmlInfo = createXMLBeanInfo( dynaClass );
433         
434         // populate the created info with
435
DynaClassBeanType beanClass = new DynaClassBeanType( dynaClass );
436         populate( xmlInfo, beanClass );
437         
438         return xmlInfo;
439     }
440     
441     /** Create a standard <code>XMLBeanInfo</code> by introspection.
442       * The actual introspection depends only on the <code>BeanInfo</code>
443       * associated with the bean.
444       *
445       * @param aClass introspect this class
446       * @return XMLBeanInfo describing bean-xml mapping
447       * @throws IntrospectionException when the bean introspection fails
448       */

449     public XMLBeanInfo introspect(Class JavaDoc aClass) throws IntrospectionException JavaDoc {
450         // we first reset the beaninfo searchpath.
451
String JavaDoc[] searchPath = null;
452         if ( !getConfiguration().useBeanInfoSearchPath() ) {
453             searchPath = Introspector.getBeanInfoSearchPath();
454             Introspector.setBeanInfoSearchPath(new String JavaDoc[] { });
455         }
456         
457         XMLBeanInfo xmlInfo = registry.get( aClass );
458         
459         if ( xmlInfo == null ) {
460             // lets see if we can find an XML descriptor first
461
if ( getLog().isDebugEnabled() ) {
462                 getLog().debug( "Attempting to lookup an XML descriptor for class: " + aClass );
463             }
464             
465             xmlInfo = findByXMLDescriptor( aClass );
466             if ( xmlInfo == null ) {
467                 BeanInfo JavaDoc info = Introspector.getBeanInfo( aClass );
468                 xmlInfo = introspect( info );
469             }
470             
471             if ( xmlInfo != null ) {
472                 registry.put( aClass, xmlInfo );
473             }
474         } else {
475             getLog().trace( "Used cached XMLBeanInfo." );
476         }
477         
478         if ( getLog().isTraceEnabled() ) {
479             getLog().trace( xmlInfo );
480         }
481         if ( !getConfiguration().useBeanInfoSearchPath() ) {
482             // we restore the beaninfo searchpath.
483
Introspector.setBeanInfoSearchPath( searchPath );
484         }
485         
486         return xmlInfo;
487     }
488     
489     /** Create a standard <code>XMLBeanInfo</code> by introspection.
490       * The actual introspection depends only on the <code>BeanInfo</code>
491       * associated with the bean.
492       *
493       * @param beanInfo the BeanInfo the xml-bean mapping is based on
494       * @return XMLBeanInfo describing bean-xml mapping
495       * @throws IntrospectionException when the bean introspection fails
496       */

497     public XMLBeanInfo introspect(BeanInfo JavaDoc beanInfo) throws IntrospectionException JavaDoc {
498         XMLBeanInfo xmlBeanInfo = createXMLBeanInfo( beanInfo );
499         populate( xmlBeanInfo, new JavaBeanType( beanInfo ) );
500         return xmlBeanInfo;
501     }
502     
503     /**
504      * Populates the given <code>XMLBeanInfo</code> based on the given type of bean.
505      *
506      * @param xmlBeanInfo populate this, not null
507      * @param bean the type definition for the bean, not null
508      */

509     private void populate(XMLBeanInfo xmlBeanInfo, BeanType bean) {
510         String JavaDoc name = bean.getBeanName();
511         
512         ElementDescriptor elementDescriptor = new ElementDescriptor();
513         elementDescriptor.setLocalName(
514             getElementNameMapper().mapTypeToElementName( name ) );
515         elementDescriptor.setPropertyType( bean.getElementType() );
516         
517         if (getLog().isTraceEnabled()) {
518             getLog().trace("Populating:" + bean);
519         }
520
521         // add default string value for primitive types
522
if ( bean.isPrimitiveType() ) {
523             getLog().trace("Bean is primitive");
524             elementDescriptor.setTextExpression( StringExpression.getInstance() );
525             
526         } else if ( bean.isLoopType() ) {
527             getLog().trace("Bean is loop");
528             ElementDescriptor loopDescriptor = new ElementDescriptor();
529             loopDescriptor.setContextExpression(
530                 new IteratorExpression( EmptyExpression.getInstance() )
531             );
532             if ( bean.isMapType() ) {
533                 loopDescriptor.setQualifiedName( "entry" );
534             }
535             elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } );
536             
537         } else {
538             getLog().trace("Bean is standard type");
539             List JavaDoc elements = new ArrayList JavaDoc();
540             List JavaDoc attributes = new ArrayList JavaDoc();
541             List JavaDoc contents = new ArrayList JavaDoc();
542
543             addProperties( bean.getProperties(), elements, attributes, contents );
544
545             int size = elements.size();
546             if ( size > 0 ) {
547                 ElementDescriptor[] descriptors = new ElementDescriptor[size];
548                 elements.toArray( descriptors );
549                 elementDescriptor.setElementDescriptors( descriptors );
550             }
551             size = attributes.size();
552             if ( size > 0 ) {
553                 AttributeDescriptor[] descriptors = new AttributeDescriptor[size];
554                 attributes.toArray( descriptors );
555                 elementDescriptor.setAttributeDescriptors( descriptors );
556             }
557             size = contents.size();
558             if ( size > 0 ) {
559                 if ( size > 0 ) {
560                     Descriptor[] descriptors = new Descriptor[size];
561                     contents.toArray( descriptors );
562                     elementDescriptor.setContentDescriptors( descriptors );
563                 }
564             }
565         }
566         
567         xmlBeanInfo.setElementDescriptor( elementDescriptor );
568         
569         // default any addProperty() methods
570
defaultAddMethods( elementDescriptor, bean.getElementType() );
571         
572         if (getLog().isTraceEnabled()) {
573             getLog().trace("Populated descriptor:");
574             getLog().trace(elementDescriptor);
575         }
576     }
577
578     
579     /**
580      * Creates XMLBeanInfo for the given DynaClass.
581      *
582      * @param dynaClass the class describing a DynaBean
583      *
584      * @return XMLBeanInfo that describes the properties of the given
585      * DynaClass
586      */

587     protected XMLBeanInfo createXMLBeanInfo(DynaClass dynaClass) {
588         // XXX is the chosen class right?
589
XMLBeanInfo beanInfo = new XMLBeanInfo(dynaClass.getClass());
590         return beanInfo;
591     }
592
593
594
595
596     /**
597      * Create a XML descriptor from a bean one.
598      * Go through and work out whether it's a loop property, a primitive or a standard.
599      * The class property is ignored.
600      *
601      * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property
602      * @param useAttributesForPrimitives write primitives as attributes (rather than elements)
603      * @return a correctly configured <code>NodeDescriptor</code> for the property
604      * @throws IntrospectionException when bean introspection fails
605      * @deprecated 0.5 use {@link #createXMLDescriptor}.
606      */

607     public Descriptor createDescriptor(
608         PropertyDescriptor JavaDoc propertyDescriptor,
609         boolean useAttributesForPrimitives
610     ) throws IntrospectionException JavaDoc {
611         return createXMLDescriptor( new BeanProperty( propertyDescriptor ) );
612     }
613  
614     /**
615      * Create a XML descriptor from a bean one.
616      * Go through and work out whether it's a loop property, a primitive or a standard.
617      * The class property is ignored.
618      *
619      * @param beanProperty the BeanProperty specifying the property
620      * @return a correctly configured <code>NodeDescriptor</code> for the property
621      * @since 0.5
622      */

623     public Descriptor createXMLDescriptor( BeanProperty beanProperty ) {
624         return beanProperty.createXMLDescriptor( configuration );
625     }
626
627
628     /**
629      * Add any addPropety(PropertyType) methods as Updaters
630      * which are often used for 1-N relationships in beans.
631      * <br>
632      * The tricky part here is finding which ElementDescriptor corresponds
633      * to the method. e.g. a property 'items' might have an Element descriptor
634      * which the method addItem() should match to.
635      * <br>
636      * So the algorithm we'll use
637      * by default is to take the decapitalized name of the property being added
638      * and find the first ElementDescriptor that matches the property starting with
639      * the string. This should work for most use cases.
640      * e.g. addChild() would match the children property.
641      * <br>
642      * TODO this probably needs refactoring. It probably belongs in the bean wrapper
643      * (so that it'll work properly with dyna-beans) and so that the operations can
644      * be optimized by caching. Multiple hash maps are created and getMethods is
645      * called multiple times. This is relatively expensive and so it'd be better
646      * to push into a proper class and cache.
647      * <br>
648      * TODO this probably does work properly with DynaBeans: need to push
649      * implementation into an class and expose it on BeanType.
650      *
651      * @param introspector use this <code>XMLIntrospector</code> for introspection
652      * @param rootDescriptor add defaults to this descriptor
653      * @param beanClass the <code>Class</code> to which descriptor corresponds
654      */

655     public void defaultAddMethods(
656                                             ElementDescriptor rootDescriptor,
657                                             Class JavaDoc beanClass ) {
658                                               
659         // lets iterate over all methods looking for one of the form
660
// add*(PropertyType)
661
if ( beanClass != null ) {
662             ArrayList JavaDoc singleParameterAdders = new ArrayList JavaDoc();
663             ArrayList JavaDoc twinParameterAdders = new ArrayList JavaDoc();
664             
665             Method JavaDoc[] methods = beanClass.getMethods();
666             for ( int i = 0, size = methods.length; i < size; i++ ) {
667                 Method JavaDoc method = methods[i];
668                 String JavaDoc name = method.getName();
669                 if ( name.startsWith( "add" )) {
670                     // TODO: should we filter out non-void returning methods?
671
// some beans will return something as a helper
672
Class JavaDoc[] types = method.getParameterTypes();
673                     if ( types != null) {
674                         if ( getLog().isTraceEnabled() ) {
675                             getLog().trace("Searching for match for " + method);
676                         }
677                         
678                         switch (types.length)
679                         {
680                             case 1:
681                                 singleParameterAdders.add(method);
682                                 break;
683                             case 2:
684                                 twinParameterAdders.add(method);
685                                 break;
686                             default:
687                                 // ignore
688
break;
689                         }
690                     }
691                 }
692             }
693             
694             Map JavaDoc elementsByPropertyName = makeElementDescriptorMap( rootDescriptor );
695             
696             for (Iterator JavaDoc it=singleParameterAdders.iterator();it.hasNext();) {
697                 Method JavaDoc singleParameterAdder = (Method JavaDoc) it.next();
698                 setIteratorAdder(elementsByPropertyName, singleParameterAdder);
699             }
700             
701             for (Iterator JavaDoc it=twinParameterAdders.iterator();it.hasNext();) {
702                 Method JavaDoc twinParameterAdder = (Method JavaDoc) it.next();
703                 setMapAdder(elementsByPropertyName, twinParameterAdder);
704             }
705         }
706     }
707     
708     /**
709      * Sets the adder method where the corresponding property is an iterator
710      * @param rootDescriptor
711      * @param singleParameterAdder
712      */

713     private void setIteratorAdder(
714         Map JavaDoc elementsByPropertyName,
715         Method JavaDoc singleParameterAdderMethod) {
716         
717         String JavaDoc adderName = singleParameterAdderMethod.getName();
718         String JavaDoc propertyName = Introspector.decapitalize(adderName.substring(3));
719         ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
720         if (matchingDescriptor != null) {
721             //TODO defensive code: probably should check descriptor type
722

723             Class JavaDoc singularType = singleParameterAdderMethod.getParameterTypes()[0];
724             if (getLog().isTraceEnabled()) {
725                 getLog().trace(adderName + "->" + propertyName);
726             }
727             // this may match a standard collection or iteration
728
getLog().trace("Matching collection or iteration");
729                                     
730             matchingDescriptor.setUpdater( new MethodUpdater( singleParameterAdderMethod ) );
731             matchingDescriptor.setSingularPropertyType( singularType );
732             matchingDescriptor.setHollow(!isPrimitiveType(singularType));
733             String JavaDoc localName = matchingDescriptor.getLocalName();
734             if ( localName == null || localName.length() == 0 ) {
735                 matchingDescriptor.setLocalName(
736                     getElementNameMapper()
737                         .mapTypeToElementName( propertyName ) );
738             }
739                                     
740             if ( getLog().isDebugEnabled() ) {
741                 getLog().debug( "!! " + singleParameterAdderMethod);
742                 getLog().debug( "!! " + singularType);
743             }
744         }
745     }
746     
747     /**
748      * Sets the adder where the corresponding property type is an map
749      * @param rootDescriptor
750      * @param singleParameterAdder
751      */

752     private void setMapAdder(
753         Map JavaDoc elementsByPropertyName,
754         Method JavaDoc twinParameterAdderMethod) {
755         String JavaDoc adderName = twinParameterAdderMethod.getName();
756         String JavaDoc propertyName = Introspector.decapitalize(adderName.substring(3));
757         ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
758         if ( matchingDescriptor != null
759             && Map JavaDoc.class.isAssignableFrom( matchingDescriptor.getPropertyType() )) {
760             // this may match a map
761
getLog().trace("Matching map");
762             ElementDescriptor[] children
763                 = matchingDescriptor.getElementDescriptors();
764             // see if the descriptor's been set up properly
765
if ( children.length == 0 ) {
766                 getLog().info(
767                     "'entry' descriptor is missing for map. "
768                     + "Updaters cannot be set");
769                                         
770             } else {
771                 Class JavaDoc[] types = twinParameterAdderMethod.getParameterTypes();
772                 Class JavaDoc keyType = types[0];
773                 Class JavaDoc valueType = types[1];
774                 
775                 // loop through children
776
// adding updaters for key and value
777
MapEntryAdder adder = new MapEntryAdder(twinParameterAdderMethod);
778                 for (
779                     int n=0,
780                         noOfGrandChildren = children.length;
781                     n < noOfGrandChildren;
782                     n++ ) {
783                     if ( "key".equals( children[n].getLocalName() ) ) {
784                                       
785                         children[n].setUpdater( adder.getKeyUpdater() );
786                         children[n].setSingularPropertyType( keyType );
787                         if (children[n].getPropertyType() == null) {
788                             children[n].setPropertyType( valueType );
789                         }
790                         if ( isPrimitiveType(keyType) ) {
791                             children[n].setHollow(false);
792                         }
793                         if ( getLog().isTraceEnabled() ) {
794                             getLog().trace( "Key descriptor: " + children[n]);
795                         }
796                                                 
797                     } else if ( "value".equals( children[n].getLocalName() ) ) {
798
799                         children[n].setUpdater( adder.getValueUpdater() );
800                         children[n].setSingularPropertyType( valueType );
801                         if (children[n].getPropertyType() == null) {
802                             children[n].setPropertyType( valueType );
803                         }
804                         if ( isPrimitiveType( valueType) ) {
805                             children[n].setHollow(false);
806                         }
807                         if ( isLoopType( valueType )) {
808                             // need to attach a hollow descriptor
809
// don't know the element name
810
// so use null name (to match anything)
811
ElementDescriptor loopDescriptor = new ElementDescriptor();
812                             loopDescriptor.setHollow(true);
813                             loopDescriptor.setSingularPropertyType( valueType );
814                             loopDescriptor.setPropertyType( valueType );
815          &n