View Javadoc
1 package org.apache.commons.betwixt; 2 3 /* 4 * ==================================================================== 5 * 6 * The Apache Software License, Version 1.1 7 * 8 * Copyright (c) 1999-2002 The Apache Software Foundation. All rights 9 * reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 18 * 2. Redistributions in binary form must reproduce the above copyright 19 * notice, this list of conditions and the following disclaimer in 20 * the documentation and/or other materials provided with the 21 * distribution. 22 * 23 * 3. The end-user documentation included with the redistribution, if 24 * any, must include the following acknowlegement: 25 * "This product includes software developed by the 26 * Apache Software Foundation (http://www.apache.org/)." 27 * Alternately, this acknowlegement may appear in the software itself, 28 * if and wherever such third-party acknowlegements normally appear. 29 * 30 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 31 * Foundation" must not be used to endorse or promote products derived 32 * from this software without prior written permission. For written 33 * permission, please contact apache@apache.org. 34 * 35 * 5. Products derived from this software may not be called "Apache" 36 * nor may "Apache" appear in their names without prior written 37 * permission of the Apache Group. 38 * 39 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 40 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 41 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 42 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 43 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 44 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 45 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 46 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 47 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 48 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 49 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 50 * SUCH DAMAGE. 51 * ==================================================================== 52 * 53 * This software consists of voluntary contributions made by many 54 * individuals on behalf of the Apache Software Foundation. For more 55 * information on the Apache Software Foundation, please see 56 * <http://www.apache.org/>;. 57 */ 58 59 import java.beans.BeanDescriptor; 60 import java.beans.BeanInfo; 61 import java.beans.IntrospectionException; 62 import java.beans.Introspector; 63 import java.beans.PropertyDescriptor; 64 import java.net.URL; 65 import java.util.ArrayList; 66 import java.util.List; 67 import java.util.Map; 68 69 import org.apache.commons.betwixt.digester.XMLBeanInfoDigester; 70 import org.apache.commons.betwixt.digester.XMLIntrospectorHelper; 71 import org.apache.commons.betwixt.expression.EmptyExpression; 72 import org.apache.commons.betwixt.expression.IteratorExpression; 73 import org.apache.commons.betwixt.expression.StringExpression; 74 import org.apache.commons.betwixt.registry.DefaultXMLBeanInfoRegistry; 75 import org.apache.commons.betwixt.registry.XMLBeanInfoRegistry; 76 import org.apache.commons.betwixt.strategy.DefaultNameMapper; 77 import org.apache.commons.betwixt.strategy.DefaultPluralStemmer; 78 import org.apache.commons.betwixt.strategy.NameMapper; 79 import org.apache.commons.betwixt.strategy.PluralStemmer; 80 import org.apache.commons.logging.Log; 81 import org.apache.commons.logging.LogFactory; 82 83 /*** 84 * <p><code>XMLIntrospector</code> an introspector of beans to create a 85 * XMLBeanInfo instance.</p> 86 * 87 * <p>By default, <code>XMLBeanInfo</code> caching is switched on. 88 * This means that the first time that a request is made for a <code>XMLBeanInfo</code> 89 * for a particular class, the <code>XMLBeanInfo</code> is cached. 90 * Later requests for the same class will return the cached value.</p> 91 * 92 * <p>Note :</p> 93 * <p>This class makes use of the <code>java.bean.Introspector</code> 94 * class, which contains a BeanInfoSearchPath. To make sure betwixt can 95 * do his work correctly, this searchpath is completely ignored during 96 * processing. The original values will be restored after processing finished 97 * </p> 98 * 99 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 100 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 101 * @version $Id: XMLIntrospector.java,v 1.19 2003/01/09 22:34:07 rdonkin Exp $ 102 */ 103 public class XMLIntrospector { 104 105 /*** Log used for logging (Doh!) */ 106 protected Log log = LogFactory.getLog( XMLIntrospector.class ); 107 108 /*** should attributes or elements be used for primitive types */ 109 private boolean attributesForPrimitives = false; 110 111 /*** should we wrap collections in an extra element? */ 112 private boolean wrapCollectionsInElement = true; 113 114 /*** Maps classes to <code>XMLBeanInfo</code>'s */ 115 private XMLBeanInfoRegistry registry = new DefaultXMLBeanInfoRegistry(); 116 117 /*** Digester used to parse the XML descriptor files */ 118 private XMLBeanInfoDigester digester; 119 120 // pluggable strategies 121 122 /*** The strategy used to detect matching singular and plural properties */ 123 private PluralStemmer pluralStemmer; 124 125 /*** The strategy used to convert bean type names into element names */ 126 private NameMapper elementNameMapper; 127 128 /*** 129 * The strategy used to convert bean type names into attribute names 130 * It will default to the normal nameMapper. 131 */ 132 private NameMapper attributeNameMapper; 133 /*** Should the existing bean info search path for java.reflect.Introspector be used? */ 134 private boolean useBeanInfoSearchPath = false; 135 136 /*** Base constructor */ 137 public XMLIntrospector() { 138 } 139 140 /*** 141 * <p>Gets the current logging implementation. </p> 142 * @return the Log implementation which this class logs to 143 */ 144 public Log getLog() { 145 return log; 146 } 147 148 /*** 149 * <p>Sets the current logging implementation.</p> 150 * @param log the Log implementation to use for logging 151 */ 152 public void setLog(Log log) { 153 this.log = log; 154 } 155 156 /*** 157 * <p>Gets the current registry implementation. 158 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class 159 * before introspecting. 160 * After standard introspection is complete, the instance will be passed to the registry.</p> 161 * 162 * <p>This allows finely grained control over the caching strategy. 163 * It also allows the standard introspection mechanism 164 * to be overridden on a per class basis.</p> 165 * 166 * @return the XMLBeanInfoRegistry currently used 167 */ 168 public XMLBeanInfoRegistry getRegistry() { 169 return registry; 170 } 171 172 /*** 173 * <p>Sets the <code>XMLBeanInfoRegistry</code> implementation. 174 * The registry is checked to see if it has an <code>XMLBeanInfo</code> for a class 175 * before introspecting. 176 * After standard introspection is complete, the instance will be passed to the registry.</p> 177 * 178 * <p>This allows finely grained control over the caching strategy. 179 * It also allows the standard introspection mechanism 180 * to be overridden on a per class basis.</p> 181 * 182 * @param registry the XMLBeanInfoRegistry to use 183 */ 184 public void setRegistry(XMLBeanInfoRegistry registry) { 185 this.registry = registry; 186 } 187 188 189 /*** 190 * Is <code>XMLBeanInfo</code> caching enabled? 191 * 192 * @deprecated replaced by XMlBeanInfoRegistry 193 * @return true if caching is enabled 194 */ 195 public boolean isCachingEnabled() { 196 return true; 197 } 198 199 /*** 200 * Set whether <code>XMLBeanInfo</code> caching should be enabled. 201 * 202 * @deprecated replaced by XMlBeanInfoRegistry 203 * @param cachingEnabled ignored 204 */ 205 public void setCachingEnabled(boolean cachingEnabled) { 206 // 207 } 208 209 /*** 210 * Flush existing cached <code>XMLBeanInfo</code>'s. 211 * 212 * @deprecated use flushable registry instead 213 */ 214 public void flushCache() {} 215 216 /*** Create a standard <code>XMLBeanInfo</code> by introspection 217 * The actual introspection depends only on the <code>BeanInfo</code> 218 * associated with the bean. 219 * 220 * @param bean introspect this bean 221 * @return XMLBeanInfo describing bean-xml mapping 222 * @throws IntrospectionException when the bean introspection fails 223 */ 224 public XMLBeanInfo introspect(Object bean) throws IntrospectionException { 225 if (log.isDebugEnabled()) { 226 log.debug( "Introspecting..." ); 227 log.debug(bean); 228 } 229 return introspect( bean.getClass() ); 230 } 231 232 /*** Create a standard <code>XMLBeanInfo</code> by introspection. 233 * The actual introspection depends only on the <code>BeanInfo</code> 234 * associated with the bean. 235 * 236 * @param aClass introspect this class 237 * @return XMLBeanInfo describing bean-xml mapping 238 * @throws IntrospectionException when the bean introspection fails 239 */ 240 public XMLBeanInfo introspect(Class aClass) throws IntrospectionException { 241 // we first reset the beaninfo searchpath. 242 String[] searchPath = null; 243 if (!useBeanInfoSearchPath) { 244 searchPath = Introspector.getBeanInfoSearchPath(); 245 Introspector.setBeanInfoSearchPath(new String[] { }); 246 } 247 248 XMLBeanInfo xmlInfo = registry.get( aClass ); 249 250 if (xmlInfo == null) { 251 // lets see if we can find an XML descriptor first 252 if ( log.isDebugEnabled() ) { 253 log.debug( "Attempting to lookup an XML descriptor for class: " + aClass ); 254 } 255 256 xmlInfo = findByXMLDescriptor( aClass ); 257 if ( xmlInfo == null ) { 258 BeanInfo info = Introspector.getBeanInfo( aClass ); 259 xmlInfo = introspect( info ); 260 } 261 262 if (xmlInfo != null) { 263 registry.put( aClass, xmlInfo ); 264 } 265 } else { 266 log.trace("Used cached XMLBeanInfo."); 267 } 268 269 if (log.isTraceEnabled()) { 270 log.trace(xmlInfo); 271 } 272 if (!useBeanInfoSearchPath) { 273 // we restore the beaninfo searchpath. 274 Introspector.setBeanInfoSearchPath(searchPath); 275 } 276 277 return xmlInfo; 278 } 279 280 /*** Create a standard <code>XMLBeanInfo</code> by introspection. 281 * The actual introspection depends only on the <code>BeanInfo</code> 282 * associated with the bean. 283 * 284 * @param beanInfo the BeanInfo the xml-bean mapping is based on 285 * @return XMLBeanInfo describing bean-xml mapping 286 * @throws IntrospectionException when the bean introspection fails 287 */ 288 public XMLBeanInfo introspect(BeanInfo beanInfo) throws IntrospectionException { 289 XMLBeanInfo answer = createXMLBeanInfo( beanInfo ); 290 291 BeanDescriptor beanDescriptor = beanInfo.getBeanDescriptor(); 292 Class beanClass = beanDescriptor.getBeanClass(); 293 294 ElementDescriptor elementDescriptor = new ElementDescriptor(); 295 elementDescriptor.setLocalName( 296 getElementNameMapper().mapTypeToElementName( beanDescriptor.getName() ) ); 297 elementDescriptor.setPropertyType( beanInfo.getBeanDescriptor().getBeanClass() ); 298 299 if (log.isTraceEnabled()) { 300 log.trace(elementDescriptor); 301 } 302 303 // add default string value for primitive types 304 if ( isPrimitiveType( beanClass ) ) { 305 elementDescriptor.setTextExpression( StringExpression.getInstance() ); 306 elementDescriptor.setPrimitiveType(true); 307 } else if ( isLoopType( beanClass ) ) { 308 ElementDescriptor loopDescriptor = new ElementDescriptor(); 309 loopDescriptor.setContextExpression( 310 new IteratorExpression( EmptyExpression.getInstance() ) 311 ); 312 if ( Map.class.isAssignableFrom( beanClass ) ) { 313 loopDescriptor.setQualifiedName( "entry" ); 314 } 315 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } ); 316 317 /* 318 elementDescriptor.setContextExpression( 319 new IteratorExpression( EmptyExpression.getInstance() ) 320 ); 321 */ 322 } else { 323 List elements = new ArrayList(); 324 List attributes = new ArrayList(); 325 326 addProperties( beanInfo, elements, attributes ); 327 328 BeanInfo[] additionals = beanInfo.getAdditionalBeanInfo(); 329 if ( additionals != null ) { 330 for ( int i = 0, size = additionals.length; i < size; i++ ) { 331 BeanInfo otherInfo = additionals[i]; 332 addProperties( otherInfo, elements, attributes ); 333 } 334 } 335 336 int size = elements.size(); 337 if ( size > 0 ) { 338 ElementDescriptor[] descriptors = new ElementDescriptor[size]; 339 elements.toArray( descriptors ); 340 elementDescriptor.setElementDescriptors( descriptors ); 341 } 342 size = attributes.size(); 343 if ( size > 0 ) { 344 AttributeDescriptor[] descriptors = new AttributeDescriptor[size]; 345 attributes.toArray( descriptors ); 346 elementDescriptor.setAttributeDescriptors( descriptors ); 347 } 348 } 349 350 answer.setElementDescriptor( elementDescriptor ); 351 352 // default any addProperty() methods 353 XMLIntrospectorHelper.defaultAddMethods( this, elementDescriptor, beanClass ); 354 355 return answer; 356 } 357 358 359 // Properties 360 //------------------------------------------------------------------------- 361 362 /*** 363 * Should attributes (or elements) be used for primitive types. 364 * @return true if primitive types will be mapped to attributes in the introspection 365 */ 366 public boolean isAttributesForPrimitives() { 367 return attributesForPrimitives; 368 } 369 370 /*** 371 * Set whether attributes (or elements) should be used for primitive types. 372 * @param attributesForPrimitives pass trus to map primitives to attributes, 373 * pass false to map primitives to elements 374 */ 375 public void setAttributesForPrimitives(boolean attributesForPrimitives) { 376 this.attributesForPrimitives = attributesForPrimitives; 377 } 378 379 /*** 380 * Should collections be wrapped in an extra element? 381 * 382 * @return whether we should we wrap collections in an extra element? 383 */ 384 public boolean isWrapCollectionsInElement() { 385 return wrapCollectionsInElement; 386 } 387 388 /*** 389 * Sets whether we should we wrap collections in an extra element. 390 * 391 * @param wrapCollectionsInElement pass true if collections should be wrapped in a 392 * parent element 393 */ 394 public void setWrapCollectionsInElement(boolean wrapCollectionsInElement) { 395 this.wrapCollectionsInElement = wrapCollectionsInElement; 396 } 397 398 /*** 399 * Get singular and plural matching strategy. 400 * 401 * @return the strategy used to detect matching singular and plural properties 402 */ 403 public PluralStemmer getPluralStemmer() { 404 if ( pluralStemmer == null ) { 405 pluralStemmer = createPluralStemmer(); 406 } 407 return pluralStemmer; 408 } 409 410 /*** 411 * Sets the strategy used to detect matching singular and plural properties 412 * 413 * @param pluralStemmer the PluralStemmer used to match singular and plural 414 */ 415 public void setPluralStemmer(PluralStemmer pluralStemmer) { 416 this.pluralStemmer = pluralStemmer; 417 } 418 419 /*** 420 * Gets the name mapper strategy. 421 * 422 * @return the strategy used to convert bean type names into element names 423 * @deprecated getNameMapper is split up in 424 * {@link #getElementNameMapper()} and {@link #getAttributeNameMapper()} 425 */ 426 public NameMapper getNameMapper() { 427 return getElementNameMapper(); 428 } 429 430 /*** 431 * Sets the strategy used to convert bean type names into element names 432 * @param nameMapper the NameMapper strategy to be used 433 * @deprecated setNameMapper is split up in 434 * {@link #setElementNameMapper(NameMapper)} and {@link #setAttributeNameMapper(NameMapper)} 435 */ 436 public void setNameMapper(NameMapper nameMapper) { 437 setElementNameMapper(nameMapper); 438 } 439 440 441 /*** 442 * Gets the name mapping strategy used to convert bean names into elements. 443 * 444 * @return the strategy used to convert bean type names into element 445 * names. If no element mapper is currently defined then a default one is created. 446 */ 447 public NameMapper getElementNameMapper() { 448 if ( elementNameMapper == null ) { 449 elementNameMapper = createNameMapper(); 450 } 451 return elementNameMapper; 452 } 453 454 /*** 455 * Sets the strategy used to convert bean type names into element names 456 * @param nameMapper the NameMapper to use for the conversion 457 */ 458 public void setElementNameMapper(NameMapper nameMapper) { 459 this.elementNameMapper = nameMapper; 460 } 461 462 463 /*** 464 * Gets the name mapping strategy used to convert bean names into attributes. 465 * 466 * @return the strategy used to convert bean type names into attribute 467 * names. If no attributeNamemapper is known, it will default to the ElementNameMapper 468 */ 469 public NameMapper getAttributeNameMapper() { 470 if (attributeNameMapper == null) { 471 attributeNameMapper = createNameMapper(); 472 } 473 return attributeNameMapper; 474 } 475 476 477 /*** 478 * Sets the strategy used to convert bean type names into attribute names 479 * @param nameMapper the NameMapper to use for the convertion 480 */ 481 public void setAttributeNameMapper(NameMapper nameMapper) { 482 this.attributeNameMapper = nameMapper; 483 } 484 485 486 487 488 489 490 // Implementation methods 491 //------------------------------------------------------------------------- 492 493 /*** 494 * A Factory method to lazily create a new strategy 495 * to detect matching singular and plural properties. 496 * 497 * @return new defualt PluralStemmer implementation 498 */ 499 protected PluralStemmer createPluralStemmer() { 500 return new DefaultPluralStemmer(); 501 } 502 503 /*** 504 * A Factory method to lazily create a strategy 505 * used to convert bean type names into element names. 506 * 507 * @return new default NameMapper implementation 508 */ 509 protected NameMapper createNameMapper() { 510 return new DefaultNameMapper(); 511 } 512 513 /*** 514 * Attempt to lookup the XML descriptor for the given class using the 515 * classname + ".betwixt" using the same ClassLoader used to load the class 516 * or return null if it could not be loaded 517 * 518 * @param aClass digester .betwixt file for this class 519 * @return XMLBeanInfo digested from the .betwixt file if one can be found. 520 * Otherwise null. 521 */ 522 protected synchronized XMLBeanInfo findByXMLDescriptor( Class aClass ) { 523 // trim the package name 524 String name = aClass.getName(); 525 int idx = name.lastIndexOf( '.' ); 526 if ( idx >= 0 ) { 527 name = name.substring( idx + 1 ); 528 } 529 name += ".betwixt"; 530 531 URL url = aClass.getResource( name ); 532 if ( url != null ) { 533 try { 534 String urlText = url.toString(); 535 if ( log.isDebugEnabled( )) { 536 log.debug( "Parsing Betwixt XML descriptor: " + urlText ); 537 } 538 // synchronized method so this digester is only used by 539 // one thread at once 540 if ( digester == null ) { 541 digester = new XMLBeanInfoDigester(); 542 digester.setXMLIntrospector( this ); 543 } 544 digester.setBeanClass( aClass ); 545 return (XMLBeanInfo) digester.parse( urlText ); 546 } catch (Exception e) { 547 log.warn( "Caught exception trying to parse: " + name, e ); 548 } 549 } 550 551 if ( log.isTraceEnabled() ) { 552 log.trace( "Could not find betwixt file " + name ); 553 } 554 return null; 555 } 556 557 /*** 558 * Loop through properties and process each one 559 * 560 * @param beanInfo the BeanInfo whose properties will be processed 561 * @param elements ElementDescriptor list to which elements will be added 562 * @param attributes AttributeDescriptor list to which attributes will be added 563 * @throws IntrospectionException if the bean introspection fails 564 */ 565 protected void addProperties( 566 BeanInfo beanInfo, 567 List elements, 568 List attributes) 569 throws 570 IntrospectionException { 571 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); 572 if ( descriptors != null ) { 573 for ( int i = 0, size = descriptors.length; i < size; i++ ) { 574 addProperty(beanInfo, descriptors[i], elements, attributes); 575 } 576 } 577 if (log.isTraceEnabled()) { 578 log.trace(elements); 579 log.trace(attributes); 580 } 581 } 582 583 /*** 584 * Process a property. 585 * Go through and work out whether it's a loop property, a primitive or a standard. 586 * The class property is ignored. 587 * 588 * @param beanInfo the BeanInfo whose property is being processed 589 * @param propertyDescriptor the PropertyDescriptor to process 590 * @param elements ElementDescriptor list to which elements will be added 591 * @param attributes AttributeDescriptor list to which attributes will be added 592 * @throws IntrospectionException if the bean introspection fails 593 */ 594 protected void addProperty( 595 BeanInfo beanInfo, 596 PropertyDescriptor propertyDescriptor, 597 List elements, 598 List attributes) 599 throws 600 IntrospectionException { 601 NodeDescriptor nodeDescriptor = XMLIntrospectorHelper 602 .createDescriptor(propertyDescriptor, 603 isAttributesForPrimitives(), 604 this); 605 if (nodeDescriptor == null) { 606 return; 607 } 608 if (nodeDescriptor instanceof ElementDescriptor) { 609 elements.add(nodeDescriptor); 610 } else { 611 attributes.add(nodeDescriptor); 612 } 613 } 614 615 /*** 616 * Factory method to create XMLBeanInfo instances 617 * 618 * @param beanInfo the BeanInfo from which the XMLBeanInfo will be created 619 * @return XMLBeanInfo describing the bean-xml mapping 620 */ 621 protected XMLBeanInfo createXMLBeanInfo( BeanInfo beanInfo ) { 622 XMLBeanInfo answer = new XMLBeanInfo( beanInfo.getBeanDescriptor().getBeanClass() ); 623 return answer; 624 } 625 626 /*** 627 * Is this class a loop? 628 * 629 * @param type the Class to test 630 * @return true if the type is a loop type 631 */ 632 public boolean isLoopType(Class type) { 633 return XMLIntrospectorHelper.isLoopType(type); 634 } 635 636 637 /*** 638 * Is this class a primitive? 639 * @param type the Class to test 640 * @return true for primitive types 641 */ 642 public boolean isPrimitiveType(Class type) { 643 return XMLIntrospectorHelper.isPrimitiveType(type); 644 } 645 /*** 646 * Should the original <code>java.reflect.Introspector</code> bean info search path be used? 647 * By default it will be false. 648 * 649 * @return boolean if the beanInfoSearchPath should be used. 650 */ 651 public boolean useBeanInfoSearchPath() { 652 return useBeanInfoSearchPath; 653 } 654 655 /*** 656 * Specifies if you want to use the beanInfoSearchPath 657 * @see java.beans.Introspector for more details 658 * @param useBeanInfoSearchPath 659 */ 660 public void setUseBeanInfoSearchPath(boolean useBeanInfoSearchPath) { 661 this.useBeanInfoSearchPath = useBeanInfoSearchPath; 662 } 663 664 }

This page was automatically generated by Maven