View Javadoc
1 package org.apache.commons.betwixt.digester; 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.IntrospectionException; 60 import java.beans.Introspector; 61 import java.beans.PropertyDescriptor; 62 import java.lang.reflect.Method; 63 import java.util.Collection; 64 import java.util.Date; 65 import java.util.Enumeration; 66 import java.util.HashMap; 67 import java.util.Iterator; 68 import java.util.Map; 69 70 import org.apache.commons.betwixt.AttributeDescriptor; 71 import org.apache.commons.betwixt.ElementDescriptor; 72 import org.apache.commons.betwixt.NodeDescriptor; 73 import org.apache.commons.betwixt.XMLIntrospector; 74 import org.apache.commons.betwixt.expression.IteratorExpression; 75 import org.apache.commons.betwixt.expression.MethodExpression; 76 import org.apache.commons.betwixt.expression.MethodUpdater; 77 import org.apache.commons.betwixt.strategy.PluralStemmer; 78 import org.apache.commons.logging.Log; 79 import org.apache.commons.logging.LogFactory; 80 81 /*** 82 * <p><code>XMLIntrospectorHelper</code> a helper class for 83 * common code shared between the digestor and introspector.</p> 84 * 85 * @author <a href="mailto:jstrachan@apache.org">James Strachan</a> 86 * @author <a href="mailto:martin@mvdb.net">Martin van den Bemt</a> 87 * @version $Id: XMLIntrospectorHelper.java,v 1.16 2003/01/08 22:07:21 rdonkin Exp $ 88 */ 89 public class XMLIntrospectorHelper { 90 91 /*** Log used for logging (Doh!) */ 92 protected static Log log = LogFactory.getLog( XMLIntrospectorHelper.class ); 93 94 /*** Base constructor */ 95 public XMLIntrospectorHelper() { 96 } 97 98 /*** 99 * <p>Gets the current logging implementation.</p> 100 * 101 * @return current log 102 */ 103 public static Log getLog() { 104 return log; 105 } 106 107 /*** 108 * <p>Sets the current logging implementation.</p> 109 * 110 * @param aLog use this <code>Log</code> 111 */ 112 public static void setLog(Log aLog) { 113 log = aLog; 114 } 115 116 117 /*** 118 * Process a property. 119 * Go through and work out whether it's a loop property, a primitive or a standard. 120 * The class property is ignored. 121 * 122 * @param propertyDescriptor create a <code>NodeDescriptor</code> for this property 123 * @param useAttributesForPrimitives write primitives as attributes (rather than elements) 124 * @param introspector use this <code>XMLIntrospector</code> 125 * @return a correctly configured <code>NodeDescriptor</code> for the property 126 * @throws IntrospectionException when bean introspection fails 127 */ 128 public static NodeDescriptor createDescriptor( 129 PropertyDescriptor propertyDescriptor, 130 boolean useAttributesForPrimitives, 131 XMLIntrospector introspector 132 ) throws IntrospectionException { 133 String name = propertyDescriptor.getName(); 134 Class type = propertyDescriptor.getPropertyType(); 135 136 if (log.isTraceEnabled()) { 137 log.trace("Creating descriptor for property: name=" 138 + name + " type=" + type); 139 } 140 141 NodeDescriptor nodeDescriptor = null; 142 Method readMethod = propertyDescriptor.getReadMethod(); 143 Method writeMethod = propertyDescriptor.getWriteMethod(); 144 145 if ( readMethod == null ) { 146 if (log.isTraceEnabled()) { 147 log.trace( "No read method for property: name=" 148 + name + " type=" + type); 149 } 150 return null; 151 } 152 153 if ( log.isTraceEnabled() ) { 154 log.trace( "Read method=" + readMethod.getName() ); 155 } 156 157 // choose response from property type 158 159 // XXX: ignore class property ?? 160 if ( Class.class.equals( type ) && "class".equals( name ) ) { 161 log.trace( "Ignoring class property" ); 162 return null; 163 } 164 if ( isPrimitiveType( type ) ) { 165 if (log.isTraceEnabled()) { 166 log.trace( "Primitive type: " + name); 167 } 168 if ( useAttributesForPrimitives ) { 169 if (log.isTraceEnabled()) { 170 log.trace( "Adding property as attribute: " + name ); 171 } 172 nodeDescriptor = new AttributeDescriptor(); 173 } else { 174 if (log.isTraceEnabled()) { 175 log.trace( "Adding property as element: " + name ); 176 } 177 nodeDescriptor = new ElementDescriptor(true); 178 } 179 nodeDescriptor.setTextExpression( new MethodExpression( readMethod ) ); 180 181 if ( writeMethod != null ) { 182 nodeDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 183 } 184 } else if ( isLoopType( type ) ) { 185 if (log.isTraceEnabled()) { 186 log.trace("Loop type: " + name); 187 } 188 ElementDescriptor loopDescriptor = new ElementDescriptor(); 189 loopDescriptor.setContextExpression( 190 new IteratorExpression( new MethodExpression( readMethod ) ) 191 ); 192 // XXX: need to support some kind of 'add' or handle arrays, Lists or indexed properties 193 //loopDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 194 if ( Map.class.isAssignableFrom( type ) ) { 195 loopDescriptor.setQualifiedName( "entry" ); 196 } 197 198 ElementDescriptor elementDescriptor = new ElementDescriptor(); 199 elementDescriptor.setWrapCollectionsInElement( 200 introspector.isWrapCollectionsInElement()); 201 elementDescriptor.setElementDescriptors( new ElementDescriptor[] { loopDescriptor } ); 202 203 nodeDescriptor = elementDescriptor; 204 } else { 205 if (log.isTraceEnabled()) { 206 log.trace( "Standard property: " + name); 207 } 208 ElementDescriptor elementDescriptor = new ElementDescriptor(); 209 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) ); 210 if ( writeMethod != null ) { 211 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 212 } 213 214 nodeDescriptor = elementDescriptor; 215 } 216 217 if (nodeDescriptor instanceof AttributeDescriptor) { 218 // we want to use the attributemapper only when it is an attribute.. 219 nodeDescriptor.setLocalName( 220 introspector.getAttributeNameMapper().mapTypeToElementName( name ) ); 221 } else { 222 nodeDescriptor.setLocalName( 223 introspector.getElementNameMapper().mapTypeToElementName( name ) ); 224 } 225 226 nodeDescriptor.setPropertyName( propertyDescriptor.getName() ); 227 nodeDescriptor.setPropertyType( type ); 228 229 // XXX: associate more bean information with the descriptor? 230 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() ); 231 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() ); 232 return nodeDescriptor; 233 } 234 235 /*** 236 * Configure an <code>ElementDescriptor</code> from a <code>PropertyDescriptor</code> 237 * 238 * @param elementDescriptor configure this <code>ElementDescriptor</code> 239 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code> 240 */ 241 public static void configureProperty( 242 ElementDescriptor elementDescriptor, 243 PropertyDescriptor propertyDescriptor ) { 244 Class type = propertyDescriptor.getPropertyType(); 245 Method readMethod = propertyDescriptor.getReadMethod(); 246 Method writeMethod = propertyDescriptor.getWriteMethod(); 247 248 elementDescriptor.setLocalName( propertyDescriptor.getName() ); 249 elementDescriptor.setPropertyType( type ); 250 251 // XXX: associate more bean information with the descriptor? 252 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() ); 253 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() ); 254 255 if ( readMethod == null ) { 256 log.trace( "No read method" ); 257 return; 258 } 259 260 if ( log.isTraceEnabled() ) { 261 log.trace( "Read method=" + readMethod.getName() ); 262 } 263 264 // choose response from property type 265 266 // XXX: ignore class property ?? 267 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) { 268 log.trace( "Ignoring class property" ); 269 return; 270 } 271 if ( isPrimitiveType( type ) ) { 272 elementDescriptor.setTextExpression( new MethodExpression( readMethod ) ); 273 elementDescriptor.setPrimitiveType(true); 274 } else if ( isLoopType( type ) ) { 275 log.trace("Loop type ??"); 276 277 // don't wrap this in an extra element as its specified in the 278 // XML descriptor so no need. 279 elementDescriptor.setContextExpression( 280 new IteratorExpression( new MethodExpression( readMethod ) ) 281 ); 282 283 writeMethod = null; 284 } else { 285 log.trace( "Standard property" ); 286 elementDescriptor.setContextExpression( new MethodExpression( readMethod ) ); 287 } 288 289 if ( writeMethod != null ) { 290 elementDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 291 } 292 } 293 294 /*** 295 * Configure an <code>AttributeDescriptor</code> from a <code>PropertyDescriptor</code> 296 * 297 * @param attributeDescriptor configure this <code>AttributeDescriptor</code> 298 * @param propertyDescriptor configure from this <code>PropertyDescriptor</code> 299 */ 300 public static void configureProperty( 301 AttributeDescriptor attributeDescriptor, 302 PropertyDescriptor propertyDescriptor ) { 303 Class type = propertyDescriptor.getPropertyType(); 304 Method readMethod = propertyDescriptor.getReadMethod(); 305 Method writeMethod = propertyDescriptor.getWriteMethod(); 306 307 if ( readMethod == null ) { 308 log.trace( "No read method" ); 309 return; 310 } 311 312 if ( log.isTraceEnabled() ) { 313 log.trace( "Read method=" + readMethod ); 314 } 315 316 // choose response from property type 317 318 // XXX: ignore class property ?? 319 if ( Class.class.equals( type ) && "class".equals( propertyDescriptor.getName() ) ) { 320 log.trace( "Ignoring class property" ); 321 return; 322 } 323 if ( isLoopType( type ) ) { 324 log.warn( "Using loop type for an attribute. Type = " 325 + type.getName() + " attribute: " + attributeDescriptor.getQualifiedName() ); 326 } 327 328 log.trace( "Standard property" ); 329 attributeDescriptor.setTextExpression( new MethodExpression( readMethod ) ); 330 331 if ( writeMethod != null ) { 332 attributeDescriptor.setUpdater( new MethodUpdater( writeMethod ) ); 333 } 334 335 attributeDescriptor.setLocalName( propertyDescriptor.getName() ); 336 attributeDescriptor.setPropertyType( type ); 337 338 // XXX: associate more bean information with the descriptor? 339 //nodeDescriptor.setDisplayName( propertyDescriptor.getDisplayName() ); 340 //nodeDescriptor.setShortDescription( propertyDescriptor.getShortDescription() ); 341 } 342 343 344 /*** 345 * Add any addPropety(PropertyType) methods as Updaters 346 * which are often used for 1-N relationships in beans. 347 * <br> 348 * The tricky part here is finding which ElementDescriptor corresponds 349 * to the method. e.g. a property 'items' might have an Element descriptor 350 * which the method addItem() should match to. 351 * <br> 352 * So the algorithm we'll use 353 * by default is to take the decapitalized name of the property being added 354 * and find the first ElementDescriptor that matches the property starting with 355 * the string. This should work for most use cases. 356 * e.g. addChild() would match the children property. 357 * 358 * @param introspector use this <code>XMLIntrospector</code> for introspection 359 * @param rootDescriptor add defaults to this descriptor 360 * @param beanClass the <code>Class</code> to which descriptor corresponds 361 */ 362 public static void defaultAddMethods( 363 XMLIntrospector introspector, 364 ElementDescriptor rootDescriptor, 365 Class beanClass ) { 366 // lets iterate over all methods looking for one of the form 367 // add*(PropertyType) 368 if ( beanClass != null ) { 369 Method[] methods = beanClass.getMethods(); 370 for ( int i = 0, size = methods.length; i < size; i++ ) { 371 Method method = methods[i]; 372 String name = method.getName(); 373 if ( name.startsWith( "add" ) ) { 374 // XXX: should we filter out non-void returning methods? 375 // some beans will return something as a helper 376 Class[] types = method.getParameterTypes(); 377 if ( types != null && types.length == 1 ) { 378 String propertyName = Introspector.decapitalize( name.substring(3) ); 379 if ( log.isTraceEnabled() ) { 380 log.trace( name + "->" + propertyName ); 381 } 382 383 // now lets try find the ElementDescriptor which displays 384 // a property which starts with propertyName 385 // and if so, we'll set a new Updater on it if there 386 // is not one already 387 ElementDescriptor descriptor = 388 findGetCollectionDescriptor( 389 introspector, 390 rootDescriptor, 391 propertyName ); 392 393 if ( log.isDebugEnabled() ) { 394 log.debug( "!! " + propertyName + " -> " + descriptor ); 395 log.debug( "!! " + name + " -> " + descriptor.getPropertyName() ); 396 } 397 398 if ( descriptor != null ) { 399 descriptor.setUpdater( new MethodUpdater( method ) ); 400 descriptor.setSingularPropertyType( types[0] ); 401 402 if ( log.isDebugEnabled() ) { 403 log.debug( "!! " + method); 404 log.debug( "!! " + types[0]); 405 } 406 407 // is there a child element with no localName 408 ElementDescriptor[] children = descriptor.getElementDescriptors(); 409 if ( children != null && children.length > 0 ) { 410 ElementDescriptor child = children[0]; 411 String localName = child.getLocalName(); 412 if ( localName == null || localName.length() == 0 ) { 413 child.setLocalName( 414 introspector.getElementNameMapper() 415 .mapTypeToElementName( propertyName ) ); 416 } 417 } 418 } else { 419 if ( log.isDebugEnabled() ) { 420 log.debug( 421 "Could not find an ElementDescriptor with property name: " 422 + propertyName + " to attach the add method: " + method 423 ); 424 } 425 } 426 } 427 } 428 } 429 } 430 } 431 432 /*** 433 * Is this a loop type class? 434 * 435 * @param type is this <code>Class</code> a loop type? 436 * @return true if the type is a loop type 437 */ 438 public static boolean isLoopType(Class type) { 439 return type.isArray() 440 || Map.class.isAssignableFrom( type ) 441 || Collection.class.isAssignableFrom( type ) 442 || Enumeration.class.isAssignableFrom( type ) 443 || Iterator.class.isAssignableFrom( type ); 444 } 445 446 447 /*** 448 * Is this a primitive type? 449 * 450 * @param type is this <code>Class<code> a primitive type? 451 * @return true for primitive types 452 */ 453 public static boolean isPrimitiveType(Class type) { 454 if ( type == null ) { 455 return false; 456 457 } else if ( type.isPrimitive() ) { 458 return true; 459 460 } else if ( type.equals( Object.class ) ) { 461 return false; 462 } 463 return type.getName().startsWith( "java.lang." ) 464 || type.isAssignableFrom( Number.class ) 465 || type.isAssignableFrom( String.class ) 466 || type.isAssignableFrom( Date.class ) 467 || type.isAssignableFrom( java.sql.Date.class ) 468 || type.isAssignableFrom( java.sql.Time.class ) 469 || type.isAssignableFrom( java.sql.Timestamp.class ) 470 || type.isAssignableFrom( java.math.BigDecimal.class ) 471 || type.isAssignableFrom( java.math.BigInteger.class ); 472 } 473 474 // Implementation methods 475 //------------------------------------------------------------------------- 476 477 /*** 478 * Attempts to find the element descriptor for the getter property that 479 * typically matches a collection or array. The property name is used 480 * to match. e.g. if an addChild() method is detected the 481 * descriptor for the 'children' getter property should be returned. 482 * 483 * @param introspector use this <code>XMLIntrospector</code> 484 * @param rootDescriptor the <code>ElementDescriptor</code> whose child element will be 485 * searched for a match 486 * @param propertyName the name of the 'adder' method to match 487 * @return <code>ElementDescriptor</code> for the matching getter 488 */ 489 protected static ElementDescriptor findGetCollectionDescriptor( 490 XMLIntrospector introspector, 491 ElementDescriptor rootDescriptor, 492 String propertyName ) { 493 // create the Map of propertyName -> descriptor that the PluralStemmer will choose 494 Map map = new HashMap(); 495 //String propertyName = rootDescriptor.getPropertyName(); 496 if ( log.isTraceEnabled() ) { 497 log.trace( "findPluralDescriptor( " + propertyName 498 + " ):root property name=" + rootDescriptor.getPropertyName() ); 499 } 500 501 if (rootDescriptor.getPropertyName() != null) { 502 map.put(propertyName, rootDescriptor); 503 } 504 makeElementDescriptorMap( rootDescriptor, map ); 505 506 PluralStemmer stemmer = introspector.getPluralStemmer(); 507 ElementDescriptor elementDescriptor = stemmer.findPluralDescriptor( propertyName, map ); 508 509 if ( log.isTraceEnabled() ) { 510 log.trace( 511 "findPluralDescriptor( " + propertyName 512 + " ):ElementDescriptor=" + elementDescriptor ); 513 } 514 515 return elementDescriptor; 516 } 517 518 /*** 519 * Creates a map where the keys are the property names and the values are the ElementDescriptors 520 * 521 * @param rootDescriptor the values of the maps are the children of this 522 * <code>ElementDescriptor</code> index by their property names 523 * @param map the map to which the elements will be added 524 */ 525 protected static void makeElementDescriptorMap( ElementDescriptor rootDescriptor, Map map ) { 526 ElementDescriptor[] children = rootDescriptor.getElementDescriptors(); 527 if ( children != null ) { 528 for ( int i = 0, size = children.length; i < size; i++ ) { 529 ElementDescriptor child = children[i]; 530 String propertyName = child.getPropertyName(); 531 if ( propertyName != null ) { 532 map.put( propertyName, child ); 533 } 534 makeElementDescriptorMap( child, map ); 535 } 536 } 537 } 538 539 /*** 540 * Traverse the tree of element descriptors and find the oldValue and swap it with the newValue. 541 * This would be much easier to do if ElementDescriptor supported a parent relationship. 542 * 543 * @param rootDescriptor traverse child graph for this <code>ElementDescriptor</code> 544 * @param oldValue replace this <code>ElementDescriptor</code> 545 * @param newValue replace with this <code>ElementDescriptor</code> 546 */ 547 protected static void swapDescriptor( 548 ElementDescriptor rootDescriptor, 549 ElementDescriptor oldValue, 550 ElementDescriptor newValue ) { 551 ElementDescriptor[] children = rootDescriptor.getElementDescriptors(); 552 if ( children != null ) { 553 for ( int i = 0, size = children.length; i < size; i++ ) { 554 ElementDescriptor child = children[i]; 555 if ( child == oldValue ) { 556 children[i] = newValue; 557 break; 558 } 559 swapDescriptor( child, oldValue, newValue ); 560 } 561 } 562 } 563 }

This page was automatically generated by Maven