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 }