Coverage Report - org.apache.commons.beanutils.BeanMap
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanMap
70%
126/179
69%
32/46
1.952
BeanMap$1
100%
13/13
N/A
1.952
BeanMap$10
100%
3/3
N/A
1.952
BeanMap$11
80%
4/5
N/A
1.952
BeanMap$12
83%
5/6
N/A
1.952
BeanMap$2
100%
2/2
N/A
1.952
BeanMap$3
100%
2/2
N/A
1.952
BeanMap$4
100%
2/2
N/A
1.952
BeanMap$5
100%
2/2
N/A
1.952
BeanMap$6
100%
2/2
N/A
1.952
BeanMap$7
100%
2/2
N/A
1.952
BeanMap$8
100%
2/2
N/A
1.952
BeanMap$9
100%
2/2
N/A
1.952
BeanMap$Entry
100%
9/9
N/A
1.952
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  * Unless required by applicable law or agreed to in writing, software
 12  
  * distributed under the License is distributed on an "AS IS" BASIS,
 13  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  * See the License for the specific language governing permissions and
 15  
  * limitations under the License.
 16  
  */
 17  
 package org.apache.commons.beanutils;
 18  
 
 19  
 import java.beans.BeanInfo;
 20  
 import java.beans.IntrospectionException;
 21  
 import java.beans.Introspector;
 22  
 import java.beans.PropertyDescriptor;
 23  
 import java.lang.reflect.Constructor;
 24  
 import java.lang.reflect.InvocationTargetException;
 25  
 import java.lang.reflect.Method;
 26  
 import java.util.AbstractMap;
 27  
 import java.util.AbstractSet;
 28  
 import java.util.ArrayList;
 29  
 import java.util.Collection;
 30  
 import java.util.Collections;
 31  
 import java.util.HashMap;
 32  
 import java.util.Iterator;
 33  
 import java.util.Map;
 34  
 import java.util.Set;
 35  
 
 36  
 import org.apache.commons.collections.list.UnmodifiableList;
 37  
 import org.apache.commons.collections.keyvalue.AbstractMapEntry;
 38  
 import org.apache.commons.collections.set.UnmodifiableSet;
 39  
 import org.apache.commons.collections.Transformer;
 40  
 
 41  
 /** 
 42  
  * An implementation of Map for JavaBeans which uses introspection to
 43  
  * get and put properties in the bean.
 44  
  * <p>
 45  
  * If an exception occurs during attempts to get or set a property then the
 46  
  * property is considered non existent in the Map
 47  
  *
 48  
  * @version $Revision: 812176 $ $Date: 2009-09-07 10:59:25 -0400 (Mon, 07 Sep 2009) $
 49  
  * 
 50  
  * @author James Strachan
 51  
  * @author Stephen Colebourne
 52  
  */
 53  433
 public class BeanMap extends AbstractMap implements Cloneable {
 54  
 
 55  
     private transient Object bean;
 56  
 
 57  145
     private transient HashMap readMethods = new HashMap();
 58  145
     private transient HashMap writeMethods = new HashMap();
 59  145
     private transient HashMap types = new HashMap();
 60  
 
 61  
     /**
 62  
      * An empty array.  Used to invoke accessors via reflection.
 63  
      */
 64  1
     public static final Object[] NULL_ARGUMENTS = {};
 65  
 
 66  
     /**
 67  
      * Maps primitive Class types to transformers.  The transformer
 68  
      * transform strings into the appropriate primitive wrapper.
 69  
      *
 70  
      * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
 71  
      */
 72  1
     private static final Map typeTransformers = Collections.unmodifiableMap(createTypeTransformers());
 73  
 
 74  
     /**
 75  
      * This HashMap has been made unmodifiable to prevent issues when
 76  
      * loaded in a shared ClassLoader enviroment.
 77  
      *
 78  
      * @see "http://issues.apache.org/jira/browse/BEANUTILS-112"
 79  
      * @deprecated Use {@link BeanMap#getTypeTransformer(Class)} method
 80  
      */
 81  1
     public static HashMap defaultTransformers = new HashMap() {
 82  
         public void clear() {
 83  1
             throw new UnsupportedOperationException();
 84  
         }
 85  
         public boolean containsKey(Object key) {
 86  2
             return typeTransformers.containsKey(key);
 87  
         }
 88  
         public boolean containsValue(Object value) {
 89  2
             return typeTransformers.containsValue(value);
 90  
         }
 91  
         public Set entrySet() {
 92  1
             return typeTransformers.entrySet();
 93  
         }
 94  
         public Object get(Object key) {
 95  9
             return typeTransformers.get(key);
 96  
         }
 97  
         public boolean isEmpty() {
 98  1
             return false;
 99  
         }
 100  
         public Set keySet() {
 101  1
             return typeTransformers.keySet();
 102  
         }
 103  
         public Object put(Object key, Object value) {
 104  1
             throw new UnsupportedOperationException();
 105  
         }
 106  
         public void putAll(Map m) {
 107  1
             throw new UnsupportedOperationException();
 108  
         }
 109  
         public Object remove(Object key) {
 110  1
             throw new UnsupportedOperationException();
 111  
         }
 112  
         public int size() {
 113  1
             return typeTransformers.size();
 114  
         }
 115  
         public Collection values() {
 116  1
             return typeTransformers.values();
 117  
         }
 118  
     };
 119  
     
 120  
     private static Map createTypeTransformers() {
 121  1
         Map defaultTransformers = new HashMap();
 122  1
         defaultTransformers.put( 
 123  
             Boolean.TYPE, 
 124  1
             new Transformer() {
 125  
                 public Object transform( Object input ) {
 126  2
                     return Boolean.valueOf( input.toString() );
 127  
                 }
 128  
             }
 129  
         );
 130  1
         defaultTransformers.put( 
 131  
             Character.TYPE, 
 132  1
             new Transformer() {
 133  
                 public Object transform( Object input ) {
 134  4
                     return new Character( input.toString().charAt( 0 ) );
 135  
                 }
 136  
             }
 137  
         );
 138  1
         defaultTransformers.put( 
 139  
             Byte.TYPE, 
 140  1
             new Transformer() {
 141  
                 public Object transform( Object input ) {
 142  4
                     return Byte.valueOf( input.toString() );
 143  
                 }
 144  
             }
 145  
         );
 146  1
         defaultTransformers.put( 
 147  
             Short.TYPE, 
 148  1
             new Transformer() {
 149  
                 public Object transform( Object input ) {
 150  4
                     return Short.valueOf( input.toString() );
 151  
                 }
 152  
             }
 153  
         );
 154  1
         defaultTransformers.put( 
 155  
             Integer.TYPE, 
 156  1
             new Transformer() {
 157  
                 public Object transform( Object input ) {
 158  7
                     return Integer.valueOf( input.toString() );
 159  
                 }
 160  
             }
 161  
         );
 162  1
         defaultTransformers.put( 
 163  
             Long.TYPE, 
 164  1
             new Transformer() {
 165  
                 public Object transform( Object input ) {
 166  5
                     return Long.valueOf( input.toString() );
 167  
                 }
 168  
             }
 169  
         );
 170  1
         defaultTransformers.put( 
 171  
             Float.TYPE, 
 172  1
             new Transformer() {
 173  
                 public Object transform( Object input ) {
 174  4
                     return Float.valueOf( input.toString() );
 175  
                 }
 176  
             }
 177  
         );
 178  1
         defaultTransformers.put( 
 179  
             Double.TYPE, 
 180  1
             new Transformer() {
 181  
                 public Object transform( Object input ) {
 182  4
                     return Double.valueOf( input.toString() );
 183  
                 }
 184  
             }
 185  
         );
 186  1
         return defaultTransformers;
 187  
     }
 188  
     
 189  
     
 190  
     // Constructors
 191  
     //-------------------------------------------------------------------------
 192  
 
 193  
     /**
 194  
      * Constructs a new empty <code>BeanMap</code>.
 195  
      */
 196  77
     public BeanMap() {
 197  77
     }
 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  68
     public BeanMap(Object bean) {
 207  68
         this.bean = bean;
 208  68
         initialise();
 209  68
     }
 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  2
         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  1
         BeanMap newMap = (BeanMap)super.clone();
 251  
 
 252  1
         if(bean == null) {
 253  
             // no bean, just an empty bean map at the moment.  return a newly
 254  
             // cloned and empty bean map.
 255  0
             return newMap;
 256  
         }
 257  
 
 258  1
         Object newBean = null;            
 259  1
         Class beanClass = bean.getClass(); // Cannot throw Exception
 260  
         try {
 261  1
             newBean = beanClass.newInstance();
 262  0
         } catch (Exception e) {
 263  
             // unable to instantiate
 264  0
             throw new CloneNotSupportedException
 265  
                 ("Unable to instantiate the underlying bean \"" +
 266  
                  beanClass.getName() + "\": " + e);
 267  1
         }
 268  
             
 269  
         try {
 270  1
             newMap.setBean(newBean);
 271  0
         } catch (Exception exception) {
 272  0
             throw new CloneNotSupportedException
 273  
                 ("Unable to set bean in the cloned bean map: " + 
 274  
                  exception);
 275  1
         }
 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  1
             Iterator readableKeys = readMethods.keySet().iterator();
 282  12
             while(readableKeys.hasNext()) {
 283  11
                 Object key = readableKeys.next();
 284  11
                 if(getWriteMethod(key) != null) {
 285  10
                     newMap.put(key, get(key));
 286  
                 }
 287  11
             }
 288  0
         } catch (Exception exception) {
 289  0
             throw new CloneNotSupportedException
 290  
                 ("Unable to copy bean values to cloned bean map: " +
 291  
                  exception);
 292  1
         }
 293  
 
 294  1
         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  1
         Iterator readableKeys = map.readMethods.keySet().iterator();
 305  12
         while (readableKeys.hasNext()) {
 306  11
             Object key = readableKeys.next();
 307  11
             if (getWriteMethod(key) != null) {
 308  10
                 this.put(key, map.get(key));
 309  
             }
 310  11
         }
 311  1
     }
 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  0
         if(bean == null) {
 324  0
             return;
 325  
         }
 326  
 
 327  0
         Class beanClass = null;
 328  
         try {
 329  0
             beanClass = bean.getClass();
 330  0
             bean = beanClass.newInstance();
 331  
         }
 332  0
         catch (Exception e) {
 333  0
             throw new UnsupportedOperationException( "Could not create new instance of class: " + beanClass );
 334  0
         }
 335  0
     }
 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  47
         Method method = getReadMethod(name);
 354  47
         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  37
         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  10591
         if ( bean != null ) {
 387  10580
             Method method = getReadMethod( name );
 388  10580
             if ( method != null ) {
 389  
                 try {
 390  10580
                     return method.invoke( bean, NULL_ARGUMENTS );
 391  
                 }
 392  0
                 catch (  IllegalAccessException e ) {
 393  0
                     logWarn( e );
 394  
                 }
 395  0
                 catch ( IllegalArgumentException e ) {
 396  0
                     logWarn(  e );
 397  
                 }
 398  0
                 catch ( InvocationTargetException e ) {
 399  0
                     logWarn(  e );
 400  
                 }
 401  0
                 catch ( NullPointerException e ) {
 402  0
                     logWarn(  e );
 403  0
                 }
 404  
             }
 405  
         }
 406  11
         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  24
         if ( bean != null ) {
 423  24
             Object oldValue = get( name );
 424  24
             Method method = getWriteMethod( name );
 425  24
             if ( method == null ) {
 426  0
                 throw new IllegalArgumentException( "The bean of type: "+ 
 427  
                         bean.getClass().getName() + " has no property called: " + name );
 428  
             }
 429  
             try {
 430  24
                 Object[] arguments = createWriteMethodArguments( method, value );
 431  24
                 method.invoke( bean, arguments );
 432  
 
 433  24
                 Object newValue = get( name );
 434  24
                 firePropertyChange( name, oldValue, newValue );
 435  
             }
 436  0
             catch ( InvocationTargetException e ) {
 437  0
                 logInfo( e );
 438  0
                 throw new IllegalArgumentException( e.getMessage() );
 439  
             }
 440  0
             catch ( IllegalAccessException e ) {
 441  0
                 logInfo( e );
 442  0
                 throw new IllegalArgumentException( e.getMessage() );
 443  24
             }
 444  24
             return oldValue;
 445  
         }
 446  0
         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  290
         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  134
         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  373
         return UnmodifiableSet.decorate(new AbstractSet() {
 482  
             public Iterator iterator() {
 483  1612
                 return entryIterator();
 484  
             }
 485  
             public int size() {
 486  416
               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  222
         ArrayList answer = new ArrayList( readMethods.size() );
 499  222
         for ( Iterator iter = valueIterator(); iter.hasNext(); ) {
 500  1298
             answer.add( iter.next() );
 501  
         }
 502  222
         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  0
         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  1834
         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  222
         final Iterator iter = keyIterator();
 538  222
         return new Iterator() {            
 539  
             public boolean hasNext() {
 540  1520
                 return iter.hasNext();
 541  
             }
 542  
             public Object next() {
 543  1298
                 Object key = iter.next();
 544  1298
                 return get(key);
 545  
             }
 546  
             public void remove() {
 547  0
                 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  1612
         final Iterator iter = keyIterator();
 559  1612
         return new Iterator() {            
 560  
             public boolean hasNext() {
 561  10123
                 return iter.hasNext();
 562  
             }            
 563  
             public Object next() {
 564  9184
                 Object key = iter.next();
 565  9182
                 Object value = get(key);
 566  9182
                 return new Entry( BeanMap.this, key, value );
 567  
             }            
 568  
             public void remove() {
 569  0
                 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  138
         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  1
         bean = newBean;
 596  1
         reinitialise();
 597  1
     }
 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  1
         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  1
         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  10627
         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  46
         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  1
         readMethods.clear();
 653  1
         writeMethods.clear();
 654  1
         types.clear();
 655  1
         initialise();
 656  1
     }
 657  
 
 658  
     private void initialise() {
 659  69
         if(getBean() == null) {
 660  0
             return;
 661  
         }
 662  
 
 663  69
         Class  beanClass = getBean().getClass();
 664  
         try {
 665  
             //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
 666  69
             BeanInfo beanInfo = Introspector.getBeanInfo( beanClass );
 667  69
             PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
 668  69
             if ( propertyDescriptors != null ) {
 669  69
                 IntrospectorUtils.setNonSyntheticReadWriteMethods(beanClass, propertyDescriptors);
 670  828
                 for ( int i = 0; i < propertyDescriptors.length; i++ ) {
 671  759
                     PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
 672  759
                     if ( propertyDescriptor != null ) {
 673  759
                         String name = propertyDescriptor.getName();
 674  759
                         Method readMethod = propertyDescriptor.getReadMethod();
 675  759
                         Method writeMethod = propertyDescriptor.getWriteMethod();
 676  759
                         Class aType = propertyDescriptor.getPropertyType();
 677  
 
 678  759
                         if ( readMethod != null ) {
 679  759
                             readMethods.put( name, readMethod );
 680  
                         }
 681  759
                         if ( writeMethod != null ) {
 682  690
                             writeMethods.put( name, writeMethod );
 683  
                         }
 684  759
                         types.put( name, aType );
 685  
                     }
 686  
                 }
 687  
             }
 688  
         }
 689  0
         catch ( IntrospectionException e ) {
 690  0
             logWarn(  e );
 691  
         }
 692  0
         catch (NoSuchMethodException e) {
 693  0
             logWarn(  e );
 694  69
         }
 695  69
     }
 696  
 
 697  
     /**
 698  
      * Called during a successful {@link #put(Object,Object)} operation.
 699  
      * Default implementation does nothing.  Override to be notified of
 700  
      * property changes in the bean caused by this map.
 701  
      *
 702  
      * @param key  the name of the property that changed
 703  
      * @param oldValue  the old value for that property
 704  
      * @param newValue  the new value for that property
 705  
      */
 706  
     protected void firePropertyChange( Object key, Object oldValue, Object newValue ) {
 707  24
     }
 708  
 
 709  
     // Implementation classes
 710  
     //-------------------------------------------------------------------------
 711  
 
 712  
     /**
 713  
      * Map entry used by {@link BeanMap}.
 714  
      */
 715  
     protected static class Entry extends AbstractMapEntry {        
 716  
         private BeanMap owner;
 717  
         
 718  
         /**
 719  
          * Constructs a new <code>Entry</code>.
 720  
          *
 721  
          * @param owner  the BeanMap this entry belongs to
 722  
          * @param key  the key for this entry
 723  
          * @param value  the value for this entry
 724  
          */
 725  
         protected Entry( BeanMap owner, Object key, Object value ) {
 726  9182
             super( key, value );
 727  9182
             this.owner = owner;
 728  9182
         }
 729  
 
 730  
         /**
 731  
          * Sets the value.
 732  
          *
 733  
          * @param value  the new value for the entry
 734  
          * @return the old value for the entry
 735  
          */
 736  
         public Object setValue(Object value) {
 737  3
             Object key = getKey();
 738  3
             Object oldValue = owner.get( key );
 739  
 
 740  3
             owner.put( key, value );
 741  3
             Object newValue = owner.get( key );
 742  3
             super.setValue( newValue );
 743  3
             return oldValue;
 744  
         }
 745  
     }
 746  
 
 747  
     /**
 748  
      * Creates an array of parameters to pass to the given mutator method.
 749  
      * If the given object is not the right type to pass to the method 
 750  
      * directly, it will be converted using {@link #convertType(Class,Object)}.
 751  
      *
 752  
      * @param method  the mutator method
 753  
      * @param value  the value to pass to the mutator method
 754  
      * @return an array containing one object that is either the given value
 755  
      *   or a transformed value
 756  
      * @throws IllegalAccessException if {@link #convertType(Class,Object)}
 757  
      *   raises it
 758  
      * @throws IllegalArgumentException if any other exception is raised
 759  
      *   by {@link #convertType(Class,Object)}
 760  
      * @throws ClassCastException if an error occurs creating the method args
 761  
      */
 762  
     protected Object[] createWriteMethodArguments( Method method, Object value ) 
 763  
         throws IllegalAccessException, ClassCastException {            
 764  
         try {
 765  24
             if ( value != null ) {
 766  24
                 Class[] types = method.getParameterTypes();
 767  24
                 if ( types != null && types.length > 0 ) {
 768  24
                     Class paramType = types[0];
 769  24
                     if ( ! paramType.isAssignableFrom( value.getClass() ) ) {
 770  18
                         value = convertType( paramType, value );
 771  
                     }
 772  
                 }
 773  
             }
 774  24
             Object[] answer = { value };
 775  24
             return answer;
 776  
         }
 777  0
         catch ( InvocationTargetException e ) {
 778  0
             logInfo( e );
 779  0
             throw new IllegalArgumentException( e.getMessage() );
 780  
         }
 781  0
         catch ( InstantiationException e ) {
 782  0
             logInfo( e );
 783  0
             throw new IllegalArgumentException( e.getMessage() );
 784  
         }
 785  
     }
 786  
 
 787  
     /**
 788  
      * Converts the given value to the given type.  First, reflection is
 789  
      * is used to find a public constructor declared by the given class 
 790  
      * that takes one argument, which must be the precise type of the 
 791  
      * given value.  If such a constructor is found, a new object is
 792  
      * created by passing the given value to that constructor, and the
 793  
      * newly constructed object is returned.<P>
 794  
      *
 795  
      * If no such constructor exists, and the given type is a primitive
 796  
      * type, then the given value is converted to a string using its 
 797  
      * {@link Object#toString() toString()} method, and that string is
 798  
      * parsed into the correct primitive type using, for instance, 
 799  
      * {@link Integer#valueOf(String)} to convert the string into an
 800  
      * <code>int</code>.<P>
 801  
      *
 802  
      * If no special constructor exists and the given type is not a 
 803  
      * primitive type, this method returns the original value.
 804  
      *
 805  
      * @param newType  the type to convert the value to
 806  
      * @param value  the value to convert
 807  
      * @return the converted value
 808  
      * @throws NumberFormatException if newType is a primitive type, and 
 809  
      *  the string representation of the given value cannot be converted
 810  
      *  to that type
 811  
      * @throws InstantiationException  if the constructor found with 
 812  
      *  reflection raises it
 813  
      * @throws InvocationTargetException  if the constructor found with
 814  
      *  reflection raises it
 815  
      * @throws IllegalAccessException  never
 816  
      * @throws IllegalArgumentException  never
 817  
      */
 818  
     protected Object convertType( Class newType, Object value ) 
 819  
         throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
 820  
         
 821  
         // try call constructor
 822  18
         Class[] types = { value.getClass() };
 823  
         try {
 824  18
             Constructor constructor = newType.getConstructor( types );        
 825  0
             Object[] arguments = { value };
 826  0
             return constructor.newInstance( arguments );
 827  
         }
 828  18
         catch ( NoSuchMethodException e ) {
 829  
             // try using the transformers
 830  18
             Transformer transformer = getTypeTransformer( newType );
 831  18
             if ( transformer != null ) {
 832  18
                 return transformer.transform( value );
 833  
             }
 834  0
             return value;
 835  
         }
 836  
     }
 837  
 
 838  
     /**
 839  
      * Returns a transformer for the given primitive type.
 840  
      *
 841  
      * @param aType  the primitive type whose transformer to return
 842  
      * @return a transformer that will convert strings into that type,
 843  
      *  or null if the given type is not a primitive type
 844  
      */
 845  
     protected Transformer getTypeTransformer( Class aType ) {
 846  26
         return (Transformer) typeTransformers.get( aType );
 847  
     }
 848  
 
 849  
     /**
 850  
      * Logs the given exception to <code>System.out</code>.  Used to display
 851  
      * warnings while accessing/mutating the bean.
 852  
      *
 853  
      * @param ex  the exception to log
 854  
      */
 855  
     protected void logInfo(Exception ex) {
 856  
         // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
 857  0
         System.out.println( "INFO: Exception: " + ex );
 858  0
     }
 859  
 
 860  
     /**
 861  
      * Logs the given exception to <code>System.err</code>.  Used to display
 862  
      * errors while accessing/mutating the bean.
 863  
      *
 864  
      * @param ex  the exception to log
 865  
      */
 866  
     protected void logWarn(Exception ex) {
 867  
         // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
 868  0
         System.out.println( "WARN: Exception: " + ex );
 869  0
         ex.printStackTrace();
 870  0
     }
 871  
 }