View Javadoc

    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: 557796 $ $Date: 2007-07-19 23:28:49 +0100 (Thu, 19 Jul 2007) $
    49   *
    50   * @author James Strachan
    51   * @author Stephen Colebourne
    52   */
    53  public class BeanMap extends AbstractMap implements Cloneable {
    54
    55      private transient Object bean;
    56
    57      private transient HashMap readMethods = new HashMap();
    58      private transient HashMap writeMethods = new HashMap();
    59      private transient HashMap types = new HashMap();
    60
    61      /***
    62       * An empty array.  Used to invoke accessors via reflection.
    63       */
    64      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      private static 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      public static HashMap defaultTransformers = new HashMap() {
    82          public void clear() {
    83              throw new UnsupportedOperationException();
    84          }
    85          public boolean containsKey(Object key) {
    86              return typeTransformers.containsKey(key);
    87          }
    88          public boolean containsValue(Object value) {
    89              return typeTransformers.containsValue(value);
    90          }
    91          public Set entrySet() {
    92              return typeTransformers.entrySet();
    93          }
    94          public Object get(Object key) {
    95              return typeTransformers.get(key);
    96          }
    97          public boolean isEmpty() {
    98              return false;
    99          }
    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 }