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: 557796 $ $Date: 2007-07-19 23:28:49 +0100 (Thu, 19 Jul 2007) $ 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 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 = null; 260 try { 261 beanClass = bean.getClass(); 262 newBean = beanClass.newInstance(); 263 } catch (Exception e) { 264 // unable to instantiate 265 throw new CloneNotSupportedException 266 ("Unable to instantiate the underlying bean \"" + 267 beanClass.getName() + "\": " + e); 268 } 269 270 try { 271 newMap.setBean(newBean); 272 } catch (Exception exception) { 273 throw new CloneNotSupportedException 274 ("Unable to set bean in the cloned bean map: " + 275 exception); 276 } 277 278 try { 279 // copy only properties that are readable and writable. If its 280 // not readable, we can't get the value from the old map. If 281 // its not writable, we can't write a value into the new map. 282 Iterator readableKeys = readMethods.keySet().iterator(); 283 while(readableKeys.hasNext()) { 284 Object key = readableKeys.next(); 285 if(getWriteMethod(key) != null) { 286 newMap.put(key, get(key)); 287 } 288 } 289 } catch (Exception exception) { 290 throw new CloneNotSupportedException 291 ("Unable to copy bean values to cloned bean map: " + 292 exception); 293 } 294 295 return newMap; 296 } 297 298 /** 299 * Puts all of the writable properties from the given BeanMap into this 300 * BeanMap. Read-only and Write-only properties will be ignored. 301 * 302 * @param map the BeanMap whose properties to put 303 */ 304 public void putAllWriteable(BeanMap map) { 305 Iterator readableKeys = map.readMethods.keySet().iterator(); 306 while (readableKeys.hasNext()) { 307 Object key = readableKeys.next(); 308 if (getWriteMethod(key) != null) { 309 this.put(key, map.get(key)); 310 } 311 } 312 } 313 314 315 /** 316 * This method reinitializes the bean map to have default values for the 317 * bean's properties. This is accomplished by constructing a new instance 318 * of the bean which the map uses as its underlying data source. This 319 * behavior for <code>clear()</code> differs from the Map contract in that 320 * the mappings are not actually removed from the map (the mappings for a 321 * BeanMap are fixed). 322 */ 323 public void clear() { 324 if(bean == null) { 325 return; 326 } 327 328 Class beanClass = null; 329 try { 330 beanClass = bean.getClass(); 331 bean = beanClass.newInstance(); 332 } 333 catch (Exception e) { 334 throw new UnsupportedOperationException( "Could not create new instance of class: " + beanClass ); 335 } 336 } 337 338 /** 339 * Returns true if the bean defines a property with the given name. 340 * <p> 341 * The given name must be a <code>String</code>; if not, this method 342 * returns false. This method will also return false if the bean 343 * does not define a property with that name. 344 * <p> 345 * Write-only properties will not be matched as the test operates against 346 * property read methods. 347 * 348 * @param name the name of the property to check 349 * @return false if the given name is null or is not a <code>String</code>; 350 * false if the bean does not define a property with that name; or 351 * true if the bean does define a property with that name 352 */ 353 public boolean containsKey(Object name) { 354 Method method = getReadMethod(name); 355 return method != null; 356 } 357 358 /** 359 * Returns true if the bean defines a property whose current value is 360 * the given object. 361 * 362 * @param value the value to check 363 * @return false true if the bean has at least one property whose 364 * current value is that object, false otherwise 365 */ 366 public boolean containsValue(Object value) { 367 // use default implementation 368 return super.containsValue(value); 369 } 370 371 /** 372 * Returns the value of the bean's property with the given name. 373 * <p> 374 * The given name must be a {@link String} and must not be 375 * null; otherwise, this method returns <code>null</code>. 376 * If the bean defines a property with the given name, the value of 377 * that property is returned. Otherwise, <code>null</code> is 378 * returned. 379 * <p> 380 * Write-only properties will not be matched as the test operates against 381 * property read methods. 382 * 383 * @param name the name of the property whose value to return 384 * @return the value of the property with that name 385 */ 386 public Object get(Object name) { 387 if ( bean != null ) { 388 Method method = getReadMethod( name ); 389 if ( method != null ) { 390 try { 391 return method.invoke( bean, NULL_ARGUMENTS ); 392 } 393 catch ( IllegalAccessException e ) { 394 logWarn( e ); 395 } 396 catch ( IllegalArgumentException e ) { 397 logWarn( e ); 398 } 399 catch ( InvocationTargetException e ) { 400 logWarn( e ); 401 } 402 catch ( NullPointerException e ) { 403 logWarn( e ); 404 } 405 } 406 } 407 return null; 408 } 409 410 /** 411 * Sets the bean property with the given name to the given value. 412 * 413 * @param name the name of the property to set 414 * @param value the value to set that property to 415 * @return the previous value of that property 416 * @throws IllegalArgumentException if the given name is null; 417 * if the given name is not a {@link String}; if the bean doesn't 418 * define a property with that name; or if the bean property with 419 * that name is read-only 420 * @throws ClassCastException if an error occurs creating the method args 421 */ 422 public Object put(Object name, Object value) throws IllegalArgumentException, ClassCastException { 423 if ( bean != null ) { 424 Object oldValue = get( name ); 425 Method method = getWriteMethod( name ); 426 if ( method == null ) { 427 throw new IllegalArgumentException( "The bean of type: "+ 428 bean.getClass().getName() + " has no property called: " + name ); 429 } 430 try { 431 Object[] arguments = createWriteMethodArguments( method, value ); 432 method.invoke( bean, arguments ); 433 434 Object newValue = get( name ); 435 firePropertyChange( name, oldValue, newValue ); 436 } 437 catch ( InvocationTargetException e ) { 438 logInfo( e ); 439 throw new IllegalArgumentException( e.getMessage() ); 440 } 441 catch ( IllegalAccessException e ) { 442 logInfo( e ); 443 throw new IllegalArgumentException( e.getMessage() ); 444 } 445 return oldValue; 446 } 447 return null; 448 } 449 450 /** 451 * Returns the number of properties defined by the bean. 452 * 453 * @return the number of properties defined by the bean 454 */ 455 public int size() { 456 return readMethods.size(); 457 } 458 459 460 /** 461 * Get the keys for this BeanMap. 462 * <p> 463 * Write-only properties are <b>not</b> included in the returned set of 464 * property names, although it is possible to set their value and to get 465 * their type. 466 * 467 * @return BeanMap keys. The Set returned by this method is not 468 * modifiable. 469 */ 470 public Set keySet() { 471 return UnmodifiableSet.decorate(readMethods.keySet()); 472 } 473 474 /** 475 * Gets a Set of MapEntry objects that are the mappings for this BeanMap. 476 * <p> 477 * Each MapEntry can be set but not removed. 478 * 479 * @return the unmodifiable set of mappings 480 */ 481 public Set entrySet() { 482 return UnmodifiableSet.decorate(new AbstractSet() { 483 public Iterator iterator() { 484 return entryIterator(); 485 } 486 public int size() { 487 return BeanMap.this.readMethods.size(); 488 } 489 }); 490 } 491 492 /** 493 * Returns the values for the BeanMap. 494 * 495 * @return values for the BeanMap. The returned collection is not 496 * modifiable. 497 */ 498 public Collection values() { 499 ArrayList answer = new ArrayList( readMethods.size() ); 500 for ( Iterator iter = valueIterator(); iter.hasNext(); ) { 501 answer.add( iter.next() ); 502 } 503 return UnmodifiableList.decorate(answer); 504 } 505 506 507 // Helper methods 508 //------------------------------------------------------------------------- 509 510 /** 511 * Returns the type of the property with the given name. 512 * 513 * @param name the name of the property 514 * @return the type of the property, or <code>null</code> if no such 515 * property exists 516 */ 517 public Class getType(String name) { 518 return (Class) types.get( name ); 519 } 520 521 /** 522 * Convenience method for getting an iterator over the keys. 523 * <p> 524 * Write-only properties will not be returned in the iterator. 525 * 526 * @return an iterator over the keys 527 */ 528 public Iterator keyIterator() { 529 return readMethods.keySet().iterator(); 530 } 531 532 /** 533 * Convenience method for getting an iterator over the values. 534 * 535 * @return an iterator over the values 536 */ 537 public Iterator valueIterator() { 538 final Iterator iter = keyIterator(); 539 return new Iterator() { 540 public boolean hasNext() { 541 return iter.hasNext(); 542 } 543 public Object next() { 544 Object key = iter.next(); 545 return get(key); 546 } 547 public void remove() { 548 throw new UnsupportedOperationException( "remove() not supported for BeanMap" ); 549 } 550 }; 551 } 552 553 /** 554 * Convenience method for getting an iterator over the entries. 555 * 556 * @return an iterator over the entries 557 */ 558 public Iterator entryIterator() { 559 final Iterator iter = keyIterator(); 560 return new Iterator() { 561 public boolean hasNext() { 562 return iter.hasNext(); 563 } 564 public Object next() { 565 Object key = iter.next(); 566 Object value = get(key); 567 return new Entry( BeanMap.this, key, value ); 568 } 569 public void remove() { 570 throw new UnsupportedOperationException( "remove() not supported for BeanMap" ); 571 } 572 }; 573 } 574 575 576 // Properties 577 //------------------------------------------------------------------------- 578 579 /** 580 * Returns the bean currently being operated on. The return value may 581 * be null if this map is empty. 582 * 583 * @return the bean being operated on by this map 584 */ 585 public Object getBean() { 586 return bean; 587 } 588 589 /** 590 * Sets the bean to be operated on by this map. The given value may 591 * be null, in which case this map will be empty. 592 * 593 * @param newBean the new bean to operate on 594 */ 595 public void setBean( Object newBean ) { 596 bean = newBean; 597 reinitialise(); 598 } 599 600 /** 601 * Returns the accessor for the property with the given name. 602 * 603 * @param name the name of the property 604 * @return the accessor method for the property, or null 605 */ 606 public Method getReadMethod(String name) { 607 return (Method) readMethods.get(name); 608 } 609 610 /** 611 * Returns the mutator for the property with the given name. 612 * 613 * @param name the name of the property 614 * @return the mutator method for the property, or null 615 */ 616 public Method getWriteMethod(String name) { 617 return (Method) writeMethods.get(name); 618 } 619 620 621 // Implementation methods 622 //------------------------------------------------------------------------- 623 624 /** 625 * Returns the accessor for the property with the given name. 626 * 627 * @param name the name of the property 628 * @return null if the name is null; null if the name is not a 629 * {@link String}; null if no such property exists; or the accessor 630 * method for that property 631 */ 632 protected Method getReadMethod( Object name ) { 633 return (Method) readMethods.get( name ); 634 } 635 636 /** 637 * Returns the mutator for the property with the given name. 638 * 639 * @param name the name of the 640 * @return null if the name is null; null if the name is not a 641 * {@link String}; null if no such property exists; null if the 642 * property is read-only; or the mutator method for that property 643 */ 644 protected Method getWriteMethod( Object name ) { 645 return (Method) writeMethods.get( name ); 646 } 647 648 /** 649 * Reinitializes this bean. Called during {@link #setBean(Object)}. 650 * Does introspection to find properties. 651 */ 652 protected void reinitialise() { 653 readMethods.clear(); 654 writeMethods.clear(); 655 types.clear(); 656 initialise(); 657 } 658 659 private void initialise() { 660 if(getBean() == null) { 661 return; 662 } 663 664 Class beanClass = getBean().getClass(); 665 try { 666 //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null ); 667 BeanInfo beanInfo = Introspector.getBeanInfo( beanClass ); 668 PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors(); 669 if ( propertyDescriptors != null ) { 670 for ( int i = 0; i < propertyDescriptors.length; i++ ) { 671 PropertyDescriptor propertyDescriptor = propertyDescriptors[i]; 672 if ( propertyDescriptor != null ) { 673 String name = propertyDescriptor.getName(); 674 Method readMethod = propertyDescriptor.getReadMethod(); 675 Method writeMethod = propertyDescriptor.getWriteMethod(); 676 Class aType = propertyDescriptor.getPropertyType(); 677 678 if ( readMethod != null ) { 679 readMethods.put( name, readMethod ); 680 } 681 if ( writeMethod != null ) { 682 writeMethods.put( name, writeMethod ); 683 } 684 types.put( name, aType ); 685 } 686 } 687 } 688 } 689 catch ( IntrospectionException e ) { 690 logWarn( e ); 691 } 692 } 693 694 /** 695 * Called during a successful {@link #put(Object,Object)} operation. 696 * Default implementation does nothing. Override to be notified of 697 * property changes in the bean caused by this map. 698 * 699 * @param key the name of the property that changed 700 * @param oldValue the old value for that property 701 * @param newValue the new value for that property 702 */ 703 protected void firePropertyChange( Object key, Object oldValue, Object newValue ) { 704 } 705 706 // Implementation classes 707 //------------------------------------------------------------------------- 708 709 /** 710 * Map entry used by {@link BeanMap}. 711 */ 712 protected static class Entry extends AbstractMapEntry { 713 private BeanMap owner; 714 715 /** 716 * Constructs a new <code>Entry</code>. 717 * 718 * @param owner the BeanMap this entry belongs to 719 * @param key the key for this entry 720 * @param value the value for this entry 721 */ 722 protected Entry( BeanMap owner, Object key, Object value ) { 723 super( key, value ); 724 this.owner = owner; 725 } 726 727 /** 728 * Sets the value. 729 * 730 * @param value the new value for the entry 731 * @return the old value for the entry 732 */ 733 public Object setValue(Object value) { 734 Object key = getKey(); 735 Object oldValue = owner.get( key ); 736 737 owner.put( key, value ); 738 Object newValue = owner.get( key ); 739 super.setValue( newValue ); 740 return oldValue; 741 } 742 } 743 744 /** 745 * Creates an array of parameters to pass to the given mutator method. 746 * If the given object is not the right type to pass to the method 747 * directly, it will be converted using {@link #convertType(Class,Object)}. 748 * 749 * @param method the mutator method 750 * @param value the value to pass to the mutator method 751 * @return an array containing one object that is either the given value 752 * or a transformed value 753 * @throws IllegalAccessException if {@link #convertType(Class,Object)} 754 * raises it 755 * @throws IllegalArgumentException if any other exception is raised 756 * by {@link #convertType(Class,Object)} 757 * @throws ClassCastException if an error occurs creating the method args 758 */ 759 protected Object[] createWriteMethodArguments( Method method, Object value ) 760 throws IllegalAccessException, ClassCastException { 761 try { 762 if ( value != null ) { 763 Class[] types = method.getParameterTypes(); 764 if ( types != null && types.length > 0 ) { 765 Class paramType = types[0]; 766 if ( ! paramType.isAssignableFrom( value.getClass() ) ) { 767 value = convertType( paramType, value ); 768 } 769 } 770 } 771 Object[] answer = { value }; 772 return answer; 773 } 774 catch ( InvocationTargetException e ) { 775 logInfo( e ); 776 throw new IllegalArgumentException( e.getMessage() ); 777 } 778 catch ( InstantiationException e ) { 779 logInfo( e ); 780 throw new IllegalArgumentException( e.getMessage() ); 781 } 782 } 783 784 /** 785 * Converts the given value to the given type. First, reflection is 786 * is used to find a public constructor declared by the given class 787 * that takes one argument, which must be the precise type of the 788 * given value. If such a constructor is found, a new object is 789 * created by passing the given value to that constructor, and the 790 * newly constructed object is returned.<P> 791 * 792 * If no such constructor exists, and the given type is a primitive 793 * type, then the given value is converted to a string using its 794 * {@link Object#toString() toString()} method, and that string is 795 * parsed into the correct primitive type using, for instance, 796 * {@link Integer#valueOf(String)} to convert the string into an 797 * <code>int</code>.<P> 798 * 799 * If no special constructor exists and the given type is not a 800 * primitive type, this method returns the original value. 801 * 802 * @param newType the type to convert the value to 803 * @param value the value to convert 804 * @return the converted value 805 * @throws NumberFormatException if newType is a primitive type, and 806 * the string representation of the given value cannot be converted 807 * to that type 808 * @throws InstantiationException if the constructor found with 809 * reflection raises it 810 * @throws InvocationTargetException if the constructor found with 811 * reflection raises it 812 * @throws IllegalAccessException never 813 * @throws IllegalArgumentException never 814 */ 815 protected Object convertType( Class newType, Object value ) 816 throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { 817 818 // try call constructor 819 Class[] types = { value.getClass() }; 820 try { 821 Constructor constructor = newType.getConstructor( types ); 822 Object[] arguments = { value }; 823 return constructor.newInstance( arguments ); 824 } 825 catch ( NoSuchMethodException e ) { 826 // try using the transformers 827 Transformer transformer = getTypeTransformer( newType ); 828 if ( transformer != null ) { 829 return transformer.transform( value ); 830 } 831 return value; 832 } 833 } 834 835 /** 836 * Returns a transformer for the given primitive type. 837 * 838 * @param aType the primitive type whose transformer to return 839 * @return a transformer that will convert strings into that type, 840 * or null if the given type is not a primitive type 841 */ 842 protected Transformer getTypeTransformer( Class aType ) { 843 return (Transformer) typeTransformers.get( aType ); 844 } 845 846 /** 847 * Logs the given exception to <code>System.out</code>. Used to display 848 * warnings while accessing/mutating the bean. 849 * 850 * @param ex the exception to log 851 */ 852 protected void logInfo(Exception ex) { 853 // Deliberately do not use LOG4J or Commons Logging to avoid dependencies 854 System.out.println( "INFO: Exception: " + ex ); 855 } 856 857 /** 858 * Logs the given exception to <code>System.err</code>. Used to display 859 * errors while accessing/mutating the bean. 860 * 861 * @param ex the exception to log 862 */ 863 protected void logWarn(Exception ex) { 864 // Deliberately do not use LOG4J or Commons Logging to avoid dependencies 865 System.out.println( "WARN: Exception: " + ex ); 866 ex.printStackTrace(); 867 } 868 }