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