View Javadoc

1   package org.apache.commons.betwixt;
2   
3   /*
4    * Copyright 2001-2004 The Apache Software Foundation.
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */ 
18  
19  import java.beans.BeanDescriptor;
20  import java.beans.BeanInfo;
21  import java.beans.IntrospectionException;
22  import java.beans.Introspector;
23  import java.beans.PropertyDescriptor;
24  import java.lang.reflect.Method;
25  import java.net.URL;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.Iterator;
29  import java.util.List;
30  import java.util.Map;
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 bean) throws IntrospectionException {
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 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 aClass) throws IntrospectionException {
450         // we first reset the beaninfo searchpath.
451         String[] searchPath = null;
452         if ( !getConfiguration().useBeanInfoSearchPath() ) {
453             searchPath = Introspector.getBeanInfoSearchPath();
454             Introspector.setBeanInfoSearchPath(new String[] { });
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 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 beanInfo) throws IntrospectionException {    
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 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 elements = new ArrayList();
540             List attributes = new ArrayList();
541             List contents = new ArrayList();
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 propertyDescriptor, 
609         boolean useAttributesForPrimitives
610     ) throws IntrospectionException {
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 beanClass ) {
658                                               
659         // lets iterate over all methods looking for one of the form
660         // add*(PropertyType)
661         if ( beanClass != null ) {
662             ArrayList singleParameterAdders = new ArrayList();
663             ArrayList twinParameterAdders = new ArrayList();
664             
665             Method[] methods = beanClass.getMethods();
666             for ( int i = 0, size = methods.length; i < size; i++ ) {
667                 Method method = methods[i];
668                 String 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[] 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 elementsByPropertyName = makeElementDescriptorMap( rootDescriptor );
695             
696             for (Iterator it=singleParameterAdders.iterator();it.hasNext();) {
697                 Method singleParameterAdder = (Method) it.next();
698                 setIteratorAdder(elementsByPropertyName, singleParameterAdder);
699             }
700             
701             for (Iterator it=twinParameterAdders.iterator();it.hasNext();) {
702                 Method twinParameterAdder = (Method) 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 elementsByPropertyName,
715         Method singleParameterAdderMethod) {
716         
717         String adderName = singleParameterAdderMethod.getName();
718         String 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 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 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 elementsByPropertyName,
754         Method twinParameterAdderMethod) {
755         String adderName = twinParameterAdderMethod.getName();
756         String propertyName = Introspector.decapitalize(adderName.substring(3));
757         ElementDescriptor matchingDescriptor = getMatchForAdder(propertyName, elementsByPropertyName);
758         if ( matchingDescriptor != null 
759             && Map.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[] types = twinParameterAdderMethod.getParameterTypes();
772                 Class keyType = types[0];
773                 Class 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                             children[n].addElementDescriptor(loopDescriptor);
816                             
817                         }
818                         if ( getLog().isTraceEnabled() ) { 
819                             getLog().trace( "Value descriptor: " + children[n]);
820                         }
821                     }
822                 }
823             }       
824         }
825     }
826         
827     /***
828      * Gets an ElementDescriptor for the property matching the adder
829      * @param adderName
830      * @param rootDescriptor
831      * @return
832      */
833     private ElementDescriptor getMatchForAdder(
834                                                 String propertyName, 
835                                                 Map elementsByPropertyName) {
836         ElementDescriptor matchingDescriptor = null;
837         if (propertyName.length() > 0) {
838             if ( getLog().isTraceEnabled() ) {
839                 getLog().trace( "findPluralDescriptor( " + propertyName 
840                     + " ):root property name=" + propertyName );
841             }
842         
843             PluralStemmer stemmer = getPluralStemmer();
844             matchingDescriptor = stemmer.findPluralDescriptor( propertyName, elementsByPropertyName );
845         
846             if ( getLog().isTraceEnabled() ) {
847                 getLog().trace( 
848                     "findPluralDescriptor( " + propertyName 
849                         + " ):ElementDescriptor=" + matchingDescriptor );
850             }
851         }
852         return matchingDescriptor;
853     }
854     
855     // Implementation methods
856     //------------------------------------------------------------------------- 
857          
858 
859     /***
860      * Creates a map where the keys are the property names and the values are the ElementDescriptors
861      */
862     private Map makeElementDescriptorMap( ElementDescriptor rootDescriptor ) {
863         Map result = new HashMap();
864         String rootPropertyName = rootDescriptor.getPropertyName();
865         if (rootPropertyName != null) {
866             result.put(rootPropertyName, rootDescriptor);
867         }
868         makeElementDescriptorMap( rootDescriptor, result );
869         return result;
870     }
871     
872     /***
873      * Creates a map where the keys are the property names and the values are the ElementDescriptors
874      * 
875      * @param rootDescriptor the values of the maps are the children of this 
876      * <code>ElementDescriptor</code> index by their property names
877      * @param map the map to which the elements will be added
878      */
879     private void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) {
880         ElementDescriptor[] children = rootDescriptor.getElementDescriptors();
881         if ( children != null ) {
882             for ( int i = 0, size = children.length; i < size; i++ ) {
883                 ElementDescriptor child = children[i];                
884                 String propertyName = child.getPropertyName();                
885                 if ( propertyName != null ) {
886                     map.put( propertyName, child );
887                 }
888                 makeElementDescriptorMap( child, map );
889             }
890         }
891     }
892     
893     /*** 
894      * A Factory method to lazily create a new strategy 
895      * to detect matching singular and plural properties.
896      *
897      * @return new defualt PluralStemmer implementation
898      * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
899      * Those who need to vary this should subclass that class instead
900      */
901     protected PluralStemmer createPluralStemmer() {
902         return new DefaultPluralStemmer();
903     }
904     
905     /*** 
906      * A Factory method to lazily create a strategy 
907      * used to convert bean type names into element names.
908      *
909      * @return new default NameMapper implementation
910      * @deprecated 0.6 this method has been moved into IntrospectionConfiguration.
911      * Those who need to vary this should subclass that class instead
912      */
913     protected NameMapper createNameMapper() {
914         return new DefaultNameMapper();
915     }
916     
917     /*** 
918      * Attempt to lookup the XML descriptor for the given class using the
919      * classname + ".betwixt" using the same ClassLoader used to load the class
920      * or return null if it could not be loaded
921      * 
922      * @param aClass digester .betwixt file for this class
923      * @return XMLBeanInfo digested from the .betwixt file if one can be found.
924      *         Otherwise null.
925      */
926     protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) {
927         // trim the package name
928         String name = aClass.getName();
929         int idx = name.lastIndexOf( '.' );
930         if ( idx >= 0 ) {
931             name = name.substring( idx + 1 );
932         }
933         name += ".betwixt";
934         
935         URL url = aClass.getResource( name );
936         if ( url != null ) {
937             try {
938                 String urlText = url.toString();
939                 if ( getLog().isDebugEnabled( )) {
940                     getLog().debug( "Parsing Betwixt XML descriptor: " + urlText );
941                 }
942                 // synchronized method so this digester is only used by
943                 // one thread at once
944                 if ( digester == null ) {
945                     digester = new XMLBeanInfoDigester();
946                     digester.setXMLIntrospector( this );
947                 }
948                 digester.setBeanClass( aClass );
949                 return (XMLBeanInfo) digester.parse( urlText );
950             } catch (Exception e) {
951                 getLog().warn( "Caught exception trying to parse: " + name, e );
952             }
953         }
954         
955         if ( getLog().isTraceEnabled() ) {
956             getLog().trace( "Could not find betwixt file " + name );
957         }
958         return null;
959     }
960             
961     /*** 
962      * Loop through properties and process each one 
963      *
964      * @param beanInfo the BeanInfo whose properties will be processed
965      * @param elements ElementDescriptor list to which elements will be added
966      * @param attributes AttributeDescriptor list to which attributes will be added
967      * @param contents Descriptor list to which mixed content will be added
968      * @throws IntrospectionException if the bean introspection fails
969      * @deprecated 0.5 use {@link #addProperties(BeanProperty[], List, List,List)}
970      */
971     protected void addProperties(
972                                     BeanInfo beanInfo, 
973                                     List elements, 
974                                     List attributes,
975                                     List contents)
976                                         throws 
977                                             IntrospectionException {
978         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
979         if ( descriptors != null ) {
980             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
981                 addProperty(beanInfo, descriptors[i], elements, attributes, contents);
982             }
983         }
984         if (getLog().isTraceEnabled()) {
985             getLog().trace(elements);
986             getLog().trace(attributes);
987             getLog().trace(contents);
988         }
989     }
990     /*** 
991      * Loop through properties and process each one 
992      *
993      * @param beanProperties the properties to be processed
994      * @param elements ElementDescriptor list to which elements will be added
995      * @param attributes AttributeDescriptor list to which attributes will be added
996      * @param contents Descriptor list to which mixed content will be added
997      * @since 0.5
998      */
999     protected void addProperties(
1000                                     BeanProperty[] beanProperties, 
1001                                     List elements, 
1002                                     List attributes,
1003                                     List contents) {
1004         if ( beanProperties != null ) {
1005             if (getLog().isTraceEnabled()) {
1006                 getLog().trace(beanProperties.length + " properties to be added");
1007             }
1008             for ( int i = 0, size = beanProperties.length; i < size; i++ ) {
1009                 addProperty(beanProperties[i], elements, attributes, contents);
1010             }
1011         }
1012         if (getLog().isTraceEnabled()) {
1013             getLog().trace("After properties have been added (elements, attributes, contents):");
1014             getLog().trace(elements);
1015             getLog().trace(attributes);
1016             getLog().trace(contents);
1017         }
1018     }    
1019 
1020     
1021     /*** 
1022      * Process a property. 
1023      * Go through and work out whether it's a loop property, a primitive or a standard.
1024      * The class property is ignored.
1025      *
1026      * @param beanInfo the BeanInfo whose property is being processed
1027      * @param propertyDescriptor the PropertyDescriptor to process
1028      * @param elements ElementDescriptor list to which elements will be added
1029      * @param attributes AttributeDescriptor list to which attributes will be added
1030      * @param contents Descriptor list to which mixed content will be added
1031      * @throws IntrospectionException if the bean introspection fails
1032      * @deprecated 0.5 BeanInfo is no longer required. 
1033      * Use {@link #addProperty(PropertyDescriptor, List, List, List)} instead.
1034      */
1035     protected void addProperty(
1036                                 BeanInfo beanInfo, 
1037                                 PropertyDescriptor propertyDescriptor, 
1038                                 List elements, 
1039                                 List attributes,
1040                                 List contents)
1041                                     throws 
1042                                         IntrospectionException {
1043        addProperty( propertyDescriptor, elements, attributes, contents);
1044     }
1045     
1046     /*** 
1047      * Process a property. 
1048      * Go through and work out whether it's a loop property, a primitive or a standard.
1049      * The class property is ignored.
1050      *
1051      * @param propertyDescriptor the PropertyDescriptor to process
1052      * @param elements ElementDescriptor list to which elements will be added
1053      * @param attributes AttributeDescriptor list to which attributes will be added
1054      * @param contents Descriptor list to which mixed content will be added
1055      * @throws IntrospectionException if the bean introspection fails
1056      * @deprecated 0.5 use {@link #addProperty(BeanProperty, List, List, List)} instead
1057      */
1058     protected void addProperty(
1059                                 PropertyDescriptor propertyDescriptor, 
1060                                 List elements, 
1061                                 List attributes,
1062                                 List contents)
1063                                     throws 
1064                                         IntrospectionException {
1065         addProperty(new BeanProperty( propertyDescriptor ), elements, attributes, contents);
1066     }
1067     
1068     /*** 
1069      * Process a property. 
1070      * Go through and work out whether it's a loop property, a primitive or a standard.
1071      * The class property is ignored.
1072      *
1073      * @param beanProperty the bean property to process
1074      * @param elements ElementDescriptor list to which elements will be added
1075      * @param attributes AttributeDescriptor list to which attributes will be added
1076      * @param contents Descriptor list to which mixed content will be added
1077      * @since 0.5
1078      */
1079     protected void addProperty(
1080                                 BeanProperty beanProperty, 
1081                                 List elements, 
1082                                 List attributes,
1083                                 List contents) {
1084         Descriptor nodeDescriptor = createXMLDescriptor(beanProperty);
1085         if (nodeDescriptor == null) {
1086            return;
1087         }
1088         if (nodeDescriptor instanceof ElementDescriptor) {
1089            elements.add(nodeDescriptor);
1090         } else if (nodeDescriptor instanceof AttributeDescriptor) {
1091            attributes.add(nodeDescriptor);
1092         } else {
1093            contents.add(nodeDescriptor);
1094         }                                 
1095     }
1096     
1097     /*** 
1098      * Loop through properties and process each one 
1099      *
1100      * @param beanInfo the BeanInfo whose properties will be processed
1101      * @param elements ElementDescriptor list to which elements will be added
1102      * @param attributes AttributeDescriptor list to which attributes will be added
1103      * @throws IntrospectionException if the bean introspection fails
1104      * @deprecated 0.5 this method does not support mixed content. 
1105      * Use {@link #addProperties(BeanInfo, List, List, List)} instead.
1106      */
1107     protected void addProperties(
1108                                     BeanInfo beanInfo, 
1109                                     List elements, 
1110                                     List attributes) 
1111                                         throws 
1112                                             IntrospectionException {
1113         PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1114         if ( descriptors != null ) {
1115             for ( int i = 0, size = descriptors.length; i < size; i++ ) {
1116                 addProperty(beanInfo, descriptors[i], elements, attributes);
1117             }
1118         }
1119         if (getLog().isTraceEnabled()) {
1120             getLog().trace(elements);
1121             getLog().trace(attributes);
1122         }
1123     }
1124     
1125     /*** 
1126      * Process a property. 
1127      * Go through and work out whether it's a loop property, a primitive or a standard.
1128      * The class property is ignored.
1129      *
1130      * @param beanInfo the BeanInfo whose property is being processed
1131      * @param propertyDescriptor the PropertyDescriptor to process
1132      * @param elements ElementDescriptor list to which elements will be added
1133      * @param attributes AttributeDescriptor list to which attributes will be added
1134      * @throws IntrospectionException if the bean introspection fails
1135      * @deprecated 0.5 this method does not support mixed content. 
1136      * Use {@link #addProperty(BeanInfo, PropertyDescriptor, List, List, List)} instead.
1137      */
1138     protected void addProperty(
1139                                 BeanInfo beanInfo, 
1140                                 PropertyDescriptor propertyDescriptor, 
1141                                 List elements, 
1142                                 List attributes) 
1143                                     throws 
1144                                         IntrospectionException {
1145         NodeDescriptor nodeDescriptor = XMLIntrospectorHelper
1146             .createDescriptor(propertyDescriptor,
1147                                  isAttributesForPrimitives(),
1148                                  this);
1149         if (nodeDescriptor == null) {
1150            return;
1151         }
1152         if (nodeDescriptor instanceof ElementDescriptor) {
1153            elements.add(nodeDescriptor);
1154         } else {
1155            attributes.add(nodeDescriptor);
1156         }
1157     }
1158 
1159     
1160     /*** 
1161      * Factory method to create XMLBeanInfo instances 
1162      *
1163      * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created
1164      * @return XMLBeanInfo describing the bean-xml mapping
1165      */
1166     protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) {
1167         XMLBeanInfo xmlBeanInfo = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() );
1168         return xmlBeanInfo;
1169     }
1170 
1171     /*** 
1172      * Is this class a loop?
1173      *
1174      * @param type the Class to test
1175      * @return true if the type is a loop type 
1176      */
1177     public boolean isLoopType(Class type) {
1178         return XMLIntrospectorHelper.isLoopType(type);
1179     }
1180     
1181     
1182     /*** 
1183      * Is this class a primitive?
1184      * TODO: this method will probably be deprecated when primitive types
1185      * are subsumed into the simple type concept 
1186      * @param type the Class to test
1187      * @return true for primitive types 
1188      */
1189     public boolean isPrimitiveType(Class type) {
1190         TypeBindingStrategy.BindingType bindingType 
1191 			= configuration.getTypeBindingStrategy().bindingType( type ) ;
1192         boolean result = (bindingType.equals(TypeBindingStrategy.BindingType.PRIMITIVE));
1193         return result;
1194     }
1195 
1196     
1197     /*** Some type of pseudo-bean */
1198     private abstract class BeanType {
1199         /*** 
1200          * Gets the name for this bean type 
1201          * @return the bean type name, not null
1202          */
1203         public abstract String getBeanName();
1204         
1205         /*** 
1206          * Gets the type to be used by the associated element
1207          * @return a Class that is the type not null
1208          */
1209         public abstract Class getElementType();
1210 
1211         /***
1212          * Is this type a primitive?
1213          * @return true if this type should be treated by betwixt as a primitive
1214          */
1215         public abstract boolean isPrimitiveType();
1216         
1217         /***
1218          * is this type a map?
1219          * @return true this should be treated as a map.
1220          */
1221         public abstract boolean isMapType();
1222         
1223         /*** 
1224          * Is this type a loop?
1225          * @return true if this should be treated as a loop
1226          */
1227         public abstract boolean isLoopType();
1228         
1229         /***
1230          * Gets the properties associated with this bean.
1231          * @return the BeanProperty's, not null
1232          */
1233         public abstract BeanProperty[] getProperties();
1234         
1235         /***
1236          * Create string representation
1237          * @return something useful for logging
1238          */
1239         public String toString() {
1240             return "Bean[name=" + getBeanName() + ", type=" + getElementType();
1241         }
1242     }
1243     
1244     /*** Supports standard Java Beans */
1245     private class JavaBeanType extends BeanType {
1246         /*** Introspected bean */
1247         private BeanInfo beanInfo;
1248         /*** Bean class */
1249         private Class beanClass;
1250         /*** Bean name */
1251         private String name;
1252         /*** Bean properties */
1253         private BeanProperty[] properties;
1254         
1255         /***
1256          * Constructs a BeanType for a standard Java Bean
1257          * @param beanInfo the BeanInfo describing the standard Java Bean, not null
1258          */
1259         public JavaBeanType(BeanInfo beanInfo) {
1260             this.beanInfo = beanInfo;
1261             BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor();
1262             beanClass = beanDescriptor.getBeanClass();
1263             name = beanDescriptor.getName();
1264             // Array's contain a bad character
1265             if (beanClass.isArray()) {
1266                 // called all array's Array
1267                 name = "Array";
1268             }
1269             
1270         }
1271         
1272         /*** @see BeanType #getElementType */
1273         public Class getElementType() {
1274             return beanClass;
1275         }
1276         
1277         /*** @see BeanType#getBeanName */
1278         public String getBeanName() {
1279             return name;
1280         }
1281         
1282         /*** @see BeanType#isPrimitiveType */
1283         public boolean isPrimitiveType() {
1284             return XMLIntrospector.this.isPrimitiveType( beanClass );
1285         }
1286         
1287         /*** @see BeanType#isLoopType */
1288         public boolean isLoopType() {
1289             return XMLIntrospectorHelper.isLoopType( beanClass );
1290         }
1291         
1292         /*** @see BeanType#isMapType */
1293         public boolean isMapType() {
1294             return Map.class.isAssignableFrom( beanClass );
1295         }
1296         
1297         /*** @see BeanType#getProperties */
1298         public BeanProperty[] getProperties() {
1299             // lazy creation
1300             if ( properties == null ) {
1301                 ArrayList propertyDescriptors = new ArrayList();
1302                 // add base bean info
1303                 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
1304                 if ( descriptors != null ) {
1305                     for (int i=0, size=descriptors.length; i<size; i++) {
1306                         propertyDescriptors.add( descriptors[i] );
1307                     }
1308                 }
1309                 
1310                 // add properties from additional bean infos
1311                 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo();
1312                 if ( additionals != null ) {
1313                     for ( int i=0, outerSize=additionals.length; i<outerSize; i++ ) {
1314                         BeanInfo additionalInfo = additionals[i];
1315                         descriptors = beanInfo.getPropertyDescriptors();
1316                         if ( descriptors != null ) {
1317                             for (int j=0, innerSize=descriptors.length; j<innerSize; j++) {
1318                                 propertyDescriptors.add( descriptors[j] );
1319                             }
1320                         }
1321                     }            
1322                 }
1323                 // what happens when size is zero?
1324                 properties = new BeanProperty[ propertyDescriptors.size() ];
1325                 int count = 0;
1326                 for ( Iterator it = propertyDescriptors.iterator(); it.hasNext(); count++) {
1327                     PropertyDescriptor propertyDescriptor = (PropertyDescriptor) it.next();
1328                     properties[count] = new BeanProperty( propertyDescriptor );
1329                 }
1330             }
1331             return properties;
1332         }
1333     }
1334     
1335     /*** Implementation for DynaClasses */
1336     private class DynaClassBeanType extends BeanType {
1337         /*** BeanType for this DynaClass */
1338         private DynaClass dynaClass;
1339         /*** Properties extracted in constuctor */
1340         private BeanProperty[] properties;
1341         
1342         /*** 
1343          * Constructs a BeanType for a DynaClass
1344          * @param dynaClass not null
1345          */
1346         public DynaClassBeanType(DynaClass dynaClass) {
1347             this.dynaClass = dynaClass;
1348             DynaProperty[] dynaProperties = dynaClass.getDynaProperties();
1349             properties = new BeanProperty[dynaProperties.length];
1350             for (int i=0, size=dynaProperties.length; i<size; i++) {
1351                 properties[i] = new BeanProperty(dynaProperties[i]);
1352             }
1353         }
1354         
1355         /*** @see BeanType#getBeanName */
1356         public String getBeanName() {
1357             return dynaClass.getName();
1358         }
1359         /*** @see BeanType#getElementType */
1360         public Class getElementType() {
1361             return DynaClass.class;
1362         }
1363         /*** @see BeanType#isPrimitiveType */
1364         public boolean isPrimitiveType() {
1365             return false;
1366         }
1367         /*** @see BeanType#isMapType */
1368         public boolean isMapType() {
1369             return false;
1370         }
1371         /*** @see BeanType#isLoopType */
1372         public boolean isLoopType() {
1373             return false;
1374         }
1375         /*** @see BeanType#getProperties */
1376         public BeanProperty[] getProperties() {
1377             return properties;
1378         }
1379     }
1380 }