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.Transformer;
37  import org.apache.commons.collections.keyvalue.AbstractMapEntry;
38  
39  /**
40   * An implementation of Map for JavaBeans which uses introspection to
41   * get and put properties in the bean.
42   * <p>
43   * If an exception occurs during attempts to get or set a property then the
44   * property is considered non existent in the Map
45   *
46   * @version $Id$
47   */
48  public class BeanMap extends AbstractMap<Object, Object> implements Cloneable {
49  
50      private transient Object bean;
51  
52      private transient HashMap<String, Method> readMethods = new HashMap<String, Method>();
53      private transient HashMap<String, Method> writeMethods = new HashMap<String, Method>();
54      private transient HashMap<String, Class<? extends Object>> types = new HashMap<String, Class<? extends Object>>();
55  
56      /**
57       * An empty array.  Used to invoke accessors via reflection.
58       */
59      public static final Object[] NULL_ARGUMENTS = {};
60  
61      /**
62       * Maps primitive Class types to transformers.  The transformer
63       * transform strings into the appropriate primitive wrapper.
64       *
65       * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
66       */
67      private static final Map<Class<? extends Object>, Transformer> typeTransformers =
68              Collections.unmodifiableMap(createTypeTransformers());
69  
70      /**
71       * This HashMap has been made unmodifiable to prevent issues when
72       * loaded in a shared ClassLoader enviroment.
73       *
74       * @see "http://issues.apache.org/jira/browse/BEANUTILS-112"
75       * @deprecated Use {@link BeanMap#getTypeTransformer(Class)} method
76       */
77      @Deprecated
78      public static HashMap defaultTransformers = new HashMap() {
79          @Override
80          public void clear() {
81              throw new UnsupportedOperationException();
82          }
83          @Override
84          public boolean containsKey(final Object key) {
85              return typeTransformers.containsKey(key);
86          }
87          @Override
88          public boolean containsValue(final Object value) {
89              return typeTransformers.containsValue(value);
90          }
91          @Override
92          public Set entrySet() {
93              return typeTransformers.entrySet();
94          }
95          @Override
96          public Object get(final Object key) {
97              return typeTransformers.get(key);
98          }
99          @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 }