001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.beanutils; 018 019 import java.beans.BeanInfo; 020 import java.beans.IntrospectionException; 021 import java.beans.Introspector; 022 import java.beans.PropertyDescriptor; 023 import java.lang.reflect.Constructor; 024 import java.lang.reflect.InvocationTargetException; 025 import java.lang.reflect.Method; 026 import java.util.AbstractMap; 027 import java.util.AbstractSet; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.Collections; 031 import java.util.HashMap; 032 import java.util.Iterator; 033 import java.util.Map; 034 import java.util.Set; 035 036 import org.apache.commons.collections.list.UnmodifiableList; 037 import org.apache.commons.collections.keyvalue.AbstractMapEntry; 038 import org.apache.commons.collections.set.UnmodifiableSet; 039 import org.apache.commons.collections.Transformer; 040 041 /** 042 * An implementation of Map for JavaBeans which uses introspection to 043 * get and put properties in the bean. 044 * <p> 045 * If an exception occurs during attempts to get or set a property then the 046 * property is considered non existent in the Map 047 * 048 * @version $Revision: 812176 $ $Date: 2009-09-07 15:59:25 +0100 (Mon, 07 Sep 2009) $ 049 * 050 * @author James Strachan 051 * @author Stephen Colebourne 052 */ 053 public class BeanMap extends AbstractMap implements Cloneable { 054 055 private transient Object bean; 056 057 private transient HashMap readMethods = new HashMap(); 058 private transient HashMap writeMethods = new HashMap(); 059 private transient HashMap types = new HashMap(); 060 061 /** 062 * An empty array. Used to invoke accessors via reflection. 063 */ 064 public static final Object[] NULL_ARGUMENTS = {}; 065 066 /** 067 * Maps primitive Class types to transformers. The transformer 068 * transform strings into the appropriate primitive wrapper. 069 * 070 * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance. 071 */ 072 private static final Map typeTransformers = Collections.unmodifiableMap(createTypeTransformers()); 073 074 /** 075 * This HashMap has been made unmodifiable to prevent issues when 076 * loaded in a shared ClassLoader enviroment. 077 * 078 * @see "http://issues.apache.org/jira/browse/BEANUTILS-112" 079 * @deprecated Use {@link BeanMap#getTypeTransformer(Class)} method 080 */ 081 public static HashMap defaultTransformers = new HashMap() { 082 public void clear() { 083 throw new UnsupportedOperationException(); 084 } 085 public boolean containsKey(Object key) { 086 return typeTransformers.containsKey(key); 087 } 088 public boolean containsValue(Object value) { 089 return typeTransformers.containsValue(value); 090 } 091 public Set entrySet() { 092 return typeTransformers.entrySet(); 093 } 094 public Object get(Object key) { 095 return typeTransformers.get(key); 096 } 097 public boolean isEmpty() { 098 return false; 099 } 100 public Set keySet() { 101 return typeTransformers.keySet(); 102 } 103 public Object put(Object key, Object value) { 104 throw new UnsupportedOperationException(); 105 } 106 public void putAll(Map m) { 107 throw new UnsupportedOperationException(); 108 } 109 public Object remove(Object key) { 110 throw new UnsupportedOperationException(); 111 } 112 public int size() { 113 return typeTransformers.size(); 114 } 115 public Collection values() { 116 return typeTransformers.values(); 117 } 118 }; 119 120 private static Map createTypeTransformers() { 121 Map defaultTransformers = new HashMap(); 122 defaultTransformers.put( 123 Boolean.TYPE, 124 new Transformer() { 125 public Object transform( Object input ) { 126 return Boolean.valueOf( input.toString() ); 127 } 128 } 129 ); 130 defaultTransformers.put( 131 Character.TYPE, 132 new Transformer() { 133 public Object transform( Object input ) { 134 return new Character( input.toString().charAt( 0 ) ); 135 } 136 } 137 ); 138 defaultTransformers.put( 139 Byte.TYPE, 140 new Transformer() { 141 public Object transform( Object input ) { 142 return Byte.valueOf( input.toString() ); 143 } 144 } 145 ); 146 defaultTransformers.put( 147 Short.TYPE, 148 new Transformer() { 149 public Object transform( Object input ) { 150 return Short.valueOf( input.toString() ); 151 } 152 } 153 ); 154 defaultTransformers.put( 155 Integer.TYPE, 156 new Transformer() { 157 public Object transform( Object input ) { 158 return Integer.valueOf( input.toString() ); 159 } 160 } 161 ); 162 defaultTransformers.put( 163 Long.TYPE, 164 new Transformer() { 165 public Object transform( Object input ) { 166 return Long.valueOf( input.toString() ); 167 } 168 } 169 ); 170 defaultTransformers.put( 171 Float.TYPE, 172 new Transformer() { 173 public Object transform( Object input ) { 174 return Float.valueOf( input.toString() ); 175 } 176 } 177 ); 178 defaultTransformers.put( 179 Double.TYPE, 180 new Transformer() { 181 public Object transform( Object input ) { 182 return Double.valueOf( input.toString() ); 183 } 184 } 185 ); 186 return defaultTransformers; 187 } 188 189 190 // Constructors 191 //------------------------------------------------------------------------- 192 193 /** 194 * Constructs a new empty <code>BeanMap</code>. 195 */ 196 public BeanMap() { 197 } 198 199 /** 200 * Constructs a new <code>BeanMap</code> that operates on the 201 * specified bean. If the given bean is <code>null</code>, then 202 * this map will be empty. 203 * 204 * @param bean the bean for this map to operate on 205 */ 206 public BeanMap(Object bean) { 207 this.bean = bean; 208 initialise(); 209 } 210 211 // Map interface 212 //------------------------------------------------------------------------- 213 214 /** 215 * Renders a string representation of this object. 216 * @return a <code>String</code> representation of this object 217 */ 218 public String toString() { 219 return "BeanMap<" + String.valueOf(bean) + ">"; 220 } 221 222 /** 223 * Clone this bean map using the following process: 224 * 225 * <ul> 226 * <li>If there is no underlying bean, return a cloned BeanMap without a 227 * bean. 228 * 229 * <li>Since there is an underlying bean, try to instantiate a new bean of 230 * the same type using Class.newInstance(). 231 * 232 * <li>If the instantiation fails, throw a CloneNotSupportedException 233 * 234 * <li>Clone the bean map and set the newly instantiated bean as the 235 * underlying bean for the bean map. 236 * 237 * <li>Copy each property that is both readable and writable from the 238 * existing object to a cloned bean map. 239 * 240 * <li>If anything fails along the way, throw a 241 * CloneNotSupportedException. 242 * 243 * <ul> 244 * 245 * @return a cloned instance of this bean map 246 * @throws CloneNotSupportedException if the underlying bean 247 * cannot be cloned 248 */ 249 public Object clone() throws CloneNotSupportedException { 250 BeanMap newMap = (BeanMap)super.clone(); 251 252 if(bean == null) { 253 // no bean, just an empty bean map at the moment. return a newly 254 // cloned and empty bean map. 255 return newMap; 256 } 257 258 Object newBean = null; 259 Class beanClass = bean.getClass(); // Cannot throw Exception 260 try { 261 newBean = beanClass.newInstance(); 262 } catch (Exception e) { 263 // unable to instantiate 264 throw new CloneNotSupportedException 265 ("Unable to instantiate the underlying bean \"" + 266 beanClass.getName() + "\": " + e); 267 } 268 269 try { 270 newMap.setBean(newBean); 271 } catch (Exception exception) { 272 throw new CloneNotSupportedException 273 ("Unable to set bean in the cloned bean map: " + 274 exception); 275 } 276 277 try { 278 // copy only properties that are readable and writable. If its 279 // not readable, we can't get the value from the old map. If 280 // its not writable, we can't write a value into the new map. 281 Iterator readableKeys = readMethods.keySet().iterator(); 282 while(readableKeys.hasNext()) { 283 Object key = readableKeys.next(); 284 if(getWriteMethod(key) != null) { 285 newMap.put(key, get(key)); 286 } 287 } 288 } catch (Exception exception) { 289 throw new CloneNotSupportedException 290 ("Unable to copy bean values to cloned bean map: " + 291 exception); 292 } 293 294 return newMap; 295 } 296 297 /** 298 * Puts all of the writable properties from the given BeanMap into this 299 * BeanMap. Read-only and Write-only properties will be ignored. 300 * 301 * @param map the BeanMap whose properties to put 302 */ 303 public void putAllWriteable(BeanMap map) { 304 Iterator readableKeys = map.readMethods.keySet().iterator(); 305 while (readableKeys.hasNext()) { 306 Object key = readableKeys.next(); 307 if (getWriteMethod(key) != null) { 308 this.put(key, map.get(key)); 309 } 310 } 311 } 312 313 314 /** 315 * This method reinitializes the bean map to have default values for the 316 * bean's properties. This is accomplished by constructing a new instance 317 * of the bean which the map uses as its underlying data source. This 318 * behavior for <code>clear()</code> differs from the Map contract in that 319 * the mappings are not actually removed from the map (the mappings for a 320 * BeanMap are fixed). 321 */ 322 public void clear() { 323 if(bean == null) { 324 return; 325 } 326 327 Class beanClass = null; 328 try { 329 beanClass = bean.getClass(); 330 bean = beanClass.newInstance(); 331 } 332 catch (Exception e) { 333 throw new UnsupportedOperationException( "Could not create new instance of class: " + beanClass ); 334 } 335 } 336 337 /** 338 * Returns true if the bean defines a property with the given name. 339 * <p> 340 * The given name must be a <code>String</code>; if not, this method 341 * returns false. This method will also return false if the bean 342 * does not define a property with that name. 343 * <p> 344 * Write-only properties will not be matched as the test operates against 345 * property read methods. 346 * 347 * @param name the name of the property to check 348 * @return false if the given name is null or is not a <code>String</code>; 349 * false if the bean does not define a property with that name; or 350 * true if the bean does define a property with that name 351 */ 352 public boolean containsKey(Object name) { 353 Method method = getReadMethod(name); 354 return method != null; 355 } 356 357 /** 358 * Returns true if the bean defines a property whose current value is 359 * the given object. 360 * 361 * @param value the value to check 362 * @return false true if the bean has at least one property whose 363 * current value is that object, false otherwise 364 */ 365 public boolean containsValue(Object value) { 366 // use default implementation 367 return super.containsValue(value); 368 } 369 370 /** 371 * Returns the value of the bean's property with the given name. 372 * <p> 373 * The given name must be a {@link String} and must not be 374 * null; otherwise, this method returns <code>null</code>. 375 * If the bean defines a property with the given name, the value of 376 * that property is returned. Otherwise, <code>null</code> is 377 * returned. 378 * <p> 379 * Write-only properties will not be matched as the test operates against 380 * property read methods. 381 * 382 * @param name the name of the property whose value to return 383 * @return the value of the property with that name 384 */ 385 public Object get(Object name) { 386 if ( bean != null ) { 387 Method method = getReadMethod( name ); 388 if ( method != null ) { 389 try { 390 return method.invoke( bean, NULL_ARGUMENTS ); 391 } 392 catch ( IllegalAccessException e ) { 393 logWarn( e ); 394 } 395 catch ( IllegalArgumentException e ) { 396 logWarn( e ); 397 } 398 catch ( InvocationTargetException e ) { 399 logWarn( e ); 400 } 401 catch ( NullPointerException e ) { 402 logWarn( e ); 403 } 404 } 405 } 406 return null; 407 } 408 409 /** 410 * Sets the bean property with the given name to the given value. 411 * 412 * @param name the name of the property to set 413 * @param value the value to set that property to 414 * @return the previous value of that property 415 * @throws IllegalArgumentException if the given name is null; 416 * if the given name is not a {@link String}; if the bean doesn't 417 * define a property with that name; or if the bean property with 418 * that name is read-only 419 * @throws ClassCastException if an error occurs creating the method args 420 */ 421 public Object put(Object name, Object value) throws IllegalArgumentException, ClassCastException { 422 if ( bean != null ) { 423 Object oldValue = get( name ); 424 Method method = getWriteMethod( name ); 425 if ( method == null ) { 426 throw new IllegalArgumentException( "The bean of type: "+ 427 bean.getClass().getName() + " has no property called: " + name ); 428 } 429 try { 430 Object[] arguments = createWriteMethodArguments( method, value ); 431 method.invoke( bean, arguments ); 432 433 Object newValue = get( name ); 434 firePropertyChange( name, oldValue, newValue ); 435 } 436 catch ( InvocationTargetException e ) { 437 logInfo( e ); 438 throw new IllegalArgumentException( e.getMessage() ); 439 } 440 catch ( IllegalAccessException e ) { 441 logInfo( e ); 442 throw new IllegalArgumentException( e.getMessage() ); 443 } 444 return oldValue; 445 } 446 return null; 447 } 448 449 /** 450 * Returns the number of properties defined by the bean. 451 * 452 * @return the number of properties defined by the bean 453 */ 454 public int size() { 455 return readMethods.size(); 456 } 457 458 459 /** 460 * Get the keys for this BeanMap. 461 * <p> 462 * Write-only properties are <b>not</b> included in the returned set of 463 * property names, although it is possible to set their value and to get 464 * their type. 465 * 466 * @return BeanMap keys. The Set returned by this method is not 467 * modifiable. 468 */ 469 public Set keySet() { 470 return UnmodifiableSet.decorate(readMethods.keySet()); 471 } 472 473 /** 474 * Gets a Set of MapEntry objects that are the mappings for this BeanMap. 475 * <p> 476 * Each MapEntry can be set but not removed. 477 * 478 * @return the unmodifiable set of mappings 479 */ 480 public Set entrySet() { 481 return UnmodifiableSet.decorate(new AbstractSet() { 482 public Iterator iterator() { 483 return entryIterator(); 484 } 485 public int size() { 486 return BeanMap.this.readMethods.size(); 487 } 488 }); 489 } 490 491 /** 492 * Returns the values for the BeanMap. 493 * 494 * @return values for the BeanMap. The returned collection is not 495 * modifiable. 496 */ 497 public Collection values() { 498 ArrayList answer = new ArrayList( readMethods.size() ); 499 for ( Iterator iter = valueIterator(); iter.hasNext(); ) { 500 answer.add( iter.next() ); 501 } 502 return UnmodifiableList.decorate(answer); 503 } 504 505 506 // Helper methods 507 //------------------------------------------------------------------------- 508 509 /** 510 * Returns the type of the property with the given name. 511 * 512 * @param name the name of the property 513 * @return the type of the property, or <code>null</code> if no such 514 * property exists 515 */ 516 public Class getType(String name) { 517 return (Class) types.get( name ); 518 } 519 520 /** 521 * Convenience method for getting an iterator over the keys. 522 * <p> 523 * Write-only properties will not be returned in the iterator. 524 * 525 * @return an iterator over the keys 526 */ 527 public Iterator keyIterator() { 528 return readMethods.keySet().iterator(); 529 } 530 531 /** 532 * Convenience method for getting an iterator over the values. 533 * 534 * @return an iterator over the values 535 */ 536 public Iterator valueIterator() { 537 final Iterator iter = keyIterator(); 538 return new Iterator() { 539 public boolean hasNext() { 540 return iter.hasNext(); 541 } 542 public Object next() { 543 Object key = iter.next(); 544 return get(key); 545 } 546 public void remove() { 547 throw new UnsupportedOperationException( "remove() not supported for BeanMap" ); 548 } 549 }; 550 } 551 552 /** 553 * Convenience method for getting an iterator over the entries. 554 * 555 * @return an iterator over the entries 556 */ 557 public Iterator entryIterator() { 558 final Iterator iter = keyIterator(); 559 return new Iterator() { 560 public boolean hasNext() { 561 return iter.hasNext(); 562 } 563 public Object next() { 564 Object key = iter.next(); 565 Object value = get(key); 566 return new Entry( BeanMap.this, key, value ); 567 } 568 public void remove() { 569 throw new UnsupportedOperationException( "remove() not supported for BeanMap" ); 570 } 571 }; 572 } 573 574 575 // Properties 576 //------------------------------------------------------------------------- 577 578 /** 579 * Returns the bean currently being operated on. The return value may 580 * be null if this map is empty. 581 * 582 * @return the bean being operated on by this map 583 */ 584 public Object getBean() { 585 return bean; 586 } 587 588 /** 589 * Sets the bean to be operated on by this map. The given value may 590 * be null, in which case this map will be empty. 591 * 592 * @param newBean the new bean to operate on 593 */ 594 public void setBean( Object newBean ) { 595 bean = newBean; 596 reinitialise(); 597 } 598 599 /** 600 * Returns the accessor for the property with the given name. 601 * 602 * @param name the name of the property 603 * @return the accessor method for the property, or null 604 */ 605 public Method getReadMethod(String name) { 606 return (Method) readMethods.get(name); 607 } 608 609 /** 610 * Returns the mutator for the property with the given name. 611 * 612 * @param name the name of the property 613 * @return the mutator method for the property, or null 614 */ 615 public Method getWriteMethod(String name) { 616 return (Method) writeMethods.get(name); 617 } 618 619 620 // Implementation methods 621 //------------------------------------------------------------------------- 622 623 /** 624 * Returns the accessor for the property with the given name. 625 * 626 * @param name the name of the property 627 * @return null if the name is null; null if the name is not a 628 * {@link String}; null if no such property exists; or the accessor 629 * method for that property 630 */ 631 protected Method getReadMethod( Object name ) { 632 return (Method) readMethods.get( name ); 633 } 634 635 /** 636 * Returns the mutator for the property with the given name. 637 * 638 * @param name the name of the 639 * @return null if the name is null; null if the name is not a 640 * {@link String}; null if no such property exists; null if the 641 * property is read-only; or the mutator method for that property 642 */ 643 protected Method getWriteMethod( Object name ) { 644 return (Method) writeMethods.get( name ); 645 } 646 647 /** 648 * Reinitializes this bean. Called during {@link #setBean(Object)}. 649 * Does introspection to find properties. 650 */ 651 protected void reinitialise() { 652 readMethods.clear(); 653 writeMethods.clear(); 654 types.clear(); 655 initialise(); 656 } 657 658 private void initialise() { 659 if(getBean() == null) { 660 return; 661 } 662 663 Class beanClass = getBean().getClass(); 664 try { 665 //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null ); 666 BeanInfo beanInfo = Introspector.getBeanInfo( beanClass ); 667 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); 668 if ( propertyDescriptors != null ) { 669 for ( int i = 0; i < propertyDescriptors.length; i++ ) { 670 PropertyDescriptor propertyDescriptor = propertyDescriptors[i]; 671 if ( propertyDescriptor != null ) { 672 String name = propertyDescriptor.getName(); 673 Method readMethod = propertyDescriptor.getReadMethod(); 674 Method writeMethod = propertyDescriptor.getWriteMethod(); 675 Class aType = propertyDescriptor.getPropertyType(); 676 677 if ( readMethod != null ) { 678 readMethods.put( name, readMethod ); 679 } 680 if ( writeMethod != null ) { 681 writeMethods.put( name, writeMethod ); 682 } 683 types.put( name, aType ); 684 } 685 } 686 } 687 } 688 catch ( IntrospectionException e ) { 689 logWarn( e ); 690 } 691 } 692 693 /** 694 * Called during a successful {@link #put(Object,Object)} operation. 695 * Default implementation does nothing. Override to be notified of 696 * property changes in the bean caused by this map. 697 * 698 * @param key the name of the property that changed 699 * @param oldValue the old value for that property 700 * @param newValue the new value for that property 701 */ 702 protected void firePropertyChange( Object key, Object oldValue, Object newValue ) { 703 } 704 705 // Implementation classes 706 //------------------------------------------------------------------------- 707 708 /** 709 * Map entry used by {@link BeanMap}. 710 */ 711 protected static class Entry extends AbstractMapEntry { 712 private BeanMap owner; 713 714 /** 715 * Constructs a new <code>Entry</code>. 716 * 717 * @param owner the BeanMap this entry belongs to 718 * @param key the key for this entry 719 * @param value the value for this entry 720 */ 721 protected Entry( BeanMap owner, Object key, Object value ) { 722 super( key, value ); 723 this.owner = owner; 724 } 725 726 /** 727 * Sets the value. 728 * 729 * @param value the new value for the entry 730 * @return the old value for the entry 731 */ 732 public Object setValue(Object value) { 733 Object key = getKey(); 734 Object oldValue = owner.get( key ); 735 736 owner.put( key, value ); 737 Object newValue = owner.get( key ); 738 super.setValue( newValue ); 739 return oldValue; 740 } 741 } 742 743 /** 744 * Creates an array of parameters to pass to the given mutator method. 745 * If the given object is not the right type to pass to the method 746 * directly, it will be converted using {@link #convertType(Class,Object)}. 747 * 748 * @param method the mutator method 749 * @param value the value to pass to the mutator method 750 * @return an array containing one object that is either the given value 751 * or a transformed value 752 * @throws IllegalAccessException if {@link #convertType(Class,Object)} 753 * raises it 754 * @throws IllegalArgumentException if any other exception is raised 755 * by {@link #convertType(Class,Object)} 756 * @throws ClassCastException if an error occurs creating the method args 757 */ 758 protected Object[] createWriteMethodArguments( Method method, Object value ) 759 throws IllegalAccessException, ClassCastException { 760 try { 761 if ( value != null ) { 762 Class[] types = method.getParameterTypes(); 763 if ( types != null && types.length > 0 ) { 764 Class paramType = types[0]; 765 if ( ! paramType.isAssignableFrom( value.getClass() ) ) { 766 value = convertType( paramType, value ); 767 } 768 } 769 } 770 Object[] answer = { value }; 771 return answer; 772 } 773 catch ( InvocationTargetException e ) { 774 logInfo( e ); 775 throw new IllegalArgumentException( e.getMessage() ); 776 } 777 catch ( InstantiationException e ) { 778 logInfo( e ); 779 throw new IllegalArgumentException( e.getMessage() ); 780 } 781 } 782 783 /** 784 * Converts the given value to the given type. First, reflection is 785 * is used to find a public constructor declared by the given class 786 * that takes one argument, which must be the precise type of the 787 * given value. If such a constructor is found, a new object is 788 * created by passing the given value to that constructor, and the 789 * newly constructed object is returned.<P> 790 * 791 * If no such constructor exists, and the given type is a primitive 792 * type, then the given value is converted to a string using its 793 * {@link Object#toString() toString()} method, and that string is 794 * parsed into the correct primitive type using, for instance, 795 * {@link Integer#valueOf(String)} to convert the string into an 796 * <code>int</code>.<P> 797 * 798 * If no special constructor exists and the given type is not a 799 * primitive type, this method returns the original value. 800 * 801 * @param newType the type to convert the value to 802 * @param value the value to convert 803 * @return the converted value 804 * @throws NumberFormatException if newType is a primitive type, and 805 * the string representation of the given value cannot be converted 806 * to that type 807 * @throws InstantiationException if the constructor found with 808 * reflection raises it 809 * @throws InvocationTargetException if the constructor found with 810 * reflection raises it 811 * @throws IllegalAccessException never 812 * @throws IllegalArgumentException never 813 */ 814 protected Object convertType( Class newType, Object value ) 815 throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 816 817 // try call constructor 818 Class[] types = { value.getClass() }; 819 try { 820 Constructor constructor = newType.getConstructor( types ); 821 Object[] arguments = { value }; 822 return constructor.newInstance( arguments ); 823 } 824 catch ( NoSuchMethodException e ) { 825 // try using the transformers 826 Transformer transformer = getTypeTransformer( newType ); 827 if ( transformer != null ) { 828 return transformer.transform( value ); 829 } 830 return value; 831 } 832 } 833 834 /** 835 * Returns a transformer for the given primitive type. 836 * 837 * @param aType the primitive type whose transformer to return 838 * @return a transformer that will convert strings into that type, 839 * or null if the given type is not a primitive type 840 */ 841 protected Transformer getTypeTransformer( Class aType ) { 842 return (Transformer) typeTransformers.get( aType ); 843 } 844 845 /** 846 * Logs the given exception to <code>System.out</code>. Used to display 847 * warnings while accessing/mutating the bean. 848 * 849 * @param ex the exception to log 850 */ 851 protected void logInfo(Exception ex) { 852 // Deliberately do not use LOG4J or Commons Logging to avoid dependencies 853 System.out.println( "INFO: Exception: " + ex ); 854 } 855 856 /** 857 * Logs the given exception to <code>System.err</code>. Used to display 858 * errors while accessing/mutating the bean. 859 * 860 * @param ex the exception to log 861 */ 862 protected void logWarn(Exception ex) { 863 // Deliberately do not use LOG4J or Commons Logging to avoid dependencies 864 System.out.println( "WARN: Exception: " + ex ); 865 ex.printStackTrace(); 866 } 867 }