Coverage Report - org.apache.commons.betwixt.XMLIntrospector

Classes in this File Line Coverage Branch Coverage Complexity
XMLIntrospector
77% 
88% 
2.17

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