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