001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.beanutils;
018    
019    import java.beans.BeanInfo;
020    import java.beans.IntrospectionException;
021    import java.beans.Introspector;
022    import java.beans.PropertyDescriptor;
023    import java.lang.reflect.Constructor;
024    import java.lang.reflect.InvocationTargetException;
025    import java.lang.reflect.Method;
026    import java.util.AbstractMap;
027    import java.util.AbstractSet;
028    import java.util.ArrayList;
029    import java.util.Collection;
030    import java.util.Collections;
031    import java.util.HashMap;
032    import java.util.Iterator;
033    import java.util.Map;
034    import java.util.Set;
035    
036    import org.apache.commons.collections.list.UnmodifiableList;
037    import org.apache.commons.collections.keyvalue.AbstractMapEntry;
038    import org.apache.commons.collections.set.UnmodifiableSet;
039    import org.apache.commons.collections.Transformer;
040    
041    /** 
042     * An implementation of Map for JavaBeans which uses introspection to
043     * get and put properties in the bean.
044     * <p>
045     * If an exception occurs during attempts to get or set a property then the
046     * property is considered non existent in the Map
047     *
048     * @version $Revision: 812176 $ $Date: 2009-09-07 15:59:25 +0100 (Mon, 07 Sep 2009) $
049     * 
050     * @author James Strachan
051     * @author Stephen Colebourne
052     */
053    public class BeanMap extends AbstractMap implements Cloneable {
054    
055        private transient Object bean;
056    
057        private transient HashMap readMethods = new HashMap();
058        private transient HashMap writeMethods = new HashMap();
059        private transient HashMap types = new HashMap();
060    
061        /**
062         * An empty array.  Used to invoke accessors via reflection.
063         */
064        public static final Object[] NULL_ARGUMENTS = {};
065    
066        /**
067         * Maps primitive Class types to transformers.  The transformer
068         * transform strings into the appropriate primitive wrapper.
069         *
070         * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
071         */
072        private static final Map typeTransformers = Collections.unmodifiableMap(createTypeTransformers());
073    
074        /**
075         * This HashMap has been made unmodifiable to prevent issues when
076         * loaded in a shared ClassLoader enviroment.
077         *
078         * @see "http://issues.apache.org/jira/browse/BEANUTILS-112"
079         * @deprecated Use {@link BeanMap#getTypeTransformer(Class)} method
080         */
081        public static HashMap defaultTransformers = new HashMap() {
082            public void clear() {
083                throw new UnsupportedOperationException();
084            }
085            public boolean containsKey(Object key) {
086                return typeTransformers.containsKey(key);
087            }
088            public boolean containsValue(Object value) {
089                return typeTransformers.containsValue(value);
090            }
091            public Set entrySet() {
092                return typeTransformers.entrySet();
093            }
094            public Object get(Object key) {
095                return typeTransformers.get(key);
096            }
097            public boolean isEmpty() {
098                return false;
099            }
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 = bean.getClass(); // Cannot throw Exception
260            try {
261                newBean = beanClass.newInstance();
262            } catch (Exception e) {
263                // unable to instantiate
264                throw new CloneNotSupportedException
265                    ("Unable to instantiate the underlying bean \"" +
266                     beanClass.getName() + "\": " + e);
267            }
268                
269            try {
270                newMap.setBean(newBean);
271            } catch (Exception exception) {
272                throw new CloneNotSupportedException
273                    ("Unable to set bean in the cloned bean map: " + 
274                     exception);
275            }
276                
277            try {
278                // copy only properties that are readable and writable.  If its
279                // not readable, we can't get the value from the old map.  If
280                // its not writable, we can't write a value into the new map.
281                Iterator readableKeys = readMethods.keySet().iterator();
282                while(readableKeys.hasNext()) {
283                    Object key = readableKeys.next();
284                    if(getWriteMethod(key) != null) {
285                        newMap.put(key, get(key));
286                    }
287                }
288            } catch (Exception exception) {
289                throw new CloneNotSupportedException
290                    ("Unable to copy bean values to cloned bean map: " +
291                     exception);
292            }
293    
294            return newMap;
295        }
296    
297        /**
298         * Puts all of the writable properties from the given BeanMap into this
299         * BeanMap. Read-only and Write-only properties will be ignored.
300         *
301         * @param map  the BeanMap whose properties to put
302         */
303        public void putAllWriteable(BeanMap map) {
304            Iterator readableKeys = map.readMethods.keySet().iterator();
305            while (readableKeys.hasNext()) {
306                Object key = readableKeys.next();
307                if (getWriteMethod(key) != null) {
308                    this.put(key, map.get(key));
309                }
310            }
311        }
312    
313    
314        /**
315         * This method reinitializes the bean map to have default values for the
316         * bean's properties.  This is accomplished by constructing a new instance
317         * of the bean which the map uses as its underlying data source.  This
318         * behavior for <code>clear()</code> differs from the Map contract in that
319         * the mappings are not actually removed from the map (the mappings for a
320         * BeanMap are fixed).
321         */
322        public void clear() {
323            if(bean == null) {
324                return;
325            }
326    
327            Class beanClass = null;
328            try {
329                beanClass = bean.getClass();
330                bean = beanClass.newInstance();
331            }
332            catch (Exception e) {
333                throw new UnsupportedOperationException( "Could not create new instance of class: " + beanClass );
334            }
335        }
336    
337        /**
338         * Returns true if the bean defines a property with the given name.
339         * <p>
340         * The given name must be a <code>String</code>; if not, this method
341         * returns false. This method will also return false if the bean
342         * does not define a property with that name.
343         * <p>
344         * Write-only properties will not be matched as the test operates against
345         * property read methods.
346         *
347         * @param name  the name of the property to check
348         * @return false if the given name is null or is not a <code>String</code>;
349         *   false if the bean does not define a property with that name; or
350         *   true if the bean does define a property with that name
351         */
352        public boolean containsKey(Object name) {
353            Method method = getReadMethod(name);
354            return method != null;
355        }
356    
357        /**
358         * Returns true if the bean defines a property whose current value is
359         * the given object.
360         *
361         * @param value  the value to check
362         * @return false  true if the bean has at least one property whose 
363         *   current value is that object, false otherwise
364         */
365        public boolean containsValue(Object value) {
366            // use default implementation
367            return super.containsValue(value);
368        }
369    
370        /**
371         * Returns the value of the bean's property with the given name.
372         * <p>
373         * The given name must be a {@link String} and must not be 
374         * null; otherwise, this method returns <code>null</code>.
375         * If the bean defines a property with the given name, the value of
376         * that property is returned.  Otherwise, <code>null</code> is 
377         * returned.
378         * <p>
379         * Write-only properties will not be matched as the test operates against
380         * property read methods.
381         *
382         * @param name  the name of the property whose value to return
383         * @return  the value of the property with that name
384         */
385        public Object get(Object name) {
386            if ( bean != null ) {
387                Method method = getReadMethod( name );
388                if ( method != null ) {
389                    try {
390                        return method.invoke( bean, NULL_ARGUMENTS );
391                    }
392                    catch (  IllegalAccessException e ) {
393                        logWarn( e );
394                    }
395                    catch ( IllegalArgumentException e ) {
396                        logWarn(  e );
397                    }
398                    catch ( InvocationTargetException e ) {
399                        logWarn(  e );
400                    }
401                    catch ( NullPointerException e ) {
402                        logWarn(  e );
403                    }
404                }
405            }
406            return null;
407        }
408    
409        /**
410         * Sets the bean property with the given name to the given value.
411         *
412         * @param name  the name of the property to set
413         * @param value  the value to set that property to
414         * @return  the previous value of that property
415         * @throws IllegalArgumentException  if the given name is null;
416         *   if the given name is not a {@link String}; if the bean doesn't
417         *   define a property with that name; or if the bean property with
418         *   that name is read-only
419         * @throws ClassCastException if an error occurs creating the method args
420         */
421        public Object put(Object name, Object value) throws IllegalArgumentException, ClassCastException {
422            if ( bean != null ) {
423                Object oldValue = get( name );
424                Method method = getWriteMethod( name );
425                if ( method == null ) {
426                    throw new IllegalArgumentException( "The bean of type: "+ 
427                            bean.getClass().getName() + " has no property called: " + name );
428                }
429                try {
430                    Object[] arguments = createWriteMethodArguments( method, value );
431                    method.invoke( bean, arguments );
432    
433                    Object newValue = get( name );
434                    firePropertyChange( name, oldValue, newValue );
435                }
436                catch ( InvocationTargetException e ) {
437                    logInfo( e );
438                    throw new IllegalArgumentException( e.getMessage() );
439                }
440                catch ( IllegalAccessException e ) {
441                    logInfo( e );
442                    throw new IllegalArgumentException( e.getMessage() );
443                }
444                return oldValue;
445            }
446            return null;
447        }
448                        
449        /**
450         * Returns the number of properties defined by the bean.
451         *
452         * @return  the number of properties defined by the bean
453         */
454        public int size() {
455            return readMethods.size();
456        }
457    
458        
459        /**
460         * Get the keys for this BeanMap.
461         * <p>
462         * Write-only properties are <b>not</b> included in the returned set of
463         * property names, although it is possible to set their value and to get 
464         * their type.
465         * 
466         * @return BeanMap keys.  The Set returned by this method is not
467         *        modifiable.
468         */
469        public Set keySet() {
470            return UnmodifiableSet.decorate(readMethods.keySet());
471        }
472    
473        /**
474         * Gets a Set of MapEntry objects that are the mappings for this BeanMap.
475         * <p>
476         * Each MapEntry can be set but not removed.
477         * 
478         * @return the unmodifiable set of mappings
479         */
480        public Set entrySet() {
481            return UnmodifiableSet.decorate(new AbstractSet() {
482                public Iterator iterator() {
483                    return entryIterator();
484                }
485                public int size() {
486                  return BeanMap.this.readMethods.size();
487                }
488            });
489        }
490    
491        /**
492         * Returns the values for the BeanMap.
493         * 
494         * @return values for the BeanMap.  The returned collection is not
495         *        modifiable.
496         */
497        public Collection values() {
498            ArrayList answer = new ArrayList( readMethods.size() );
499            for ( Iterator iter = valueIterator(); iter.hasNext(); ) {
500                answer.add( iter.next() );
501            }
502            return UnmodifiableList.decorate(answer);
503        }
504    
505    
506        // Helper methods
507        //-------------------------------------------------------------------------
508    
509        /**
510         * Returns the type of the property with the given name.
511         *
512         * @param name  the name of the property
513         * @return  the type of the property, or <code>null</code> if no such
514         *  property exists
515         */
516        public Class getType(String name) {
517            return (Class) types.get( name );
518        }
519    
520        /**
521         * Convenience method for getting an iterator over the keys.
522         * <p>
523         * Write-only properties will not be returned in the iterator.
524         *
525         * @return an iterator over the keys
526         */
527        public Iterator keyIterator() {
528            return readMethods.keySet().iterator();
529        }
530    
531        /**
532         * Convenience method for getting an iterator over the values.
533         *
534         * @return an iterator over the values
535         */
536        public Iterator valueIterator() {
537            final Iterator iter = keyIterator();
538            return new Iterator() {            
539                public boolean hasNext() {
540                    return iter.hasNext();
541                }
542                public Object next() {
543                    Object key = iter.next();
544                    return get(key);
545                }
546                public void remove() {
547                    throw new UnsupportedOperationException( "remove() not supported for BeanMap" );
548                }
549            };
550        }
551    
552        /**
553         * Convenience method for getting an iterator over the entries.
554         *
555         * @return an iterator over the entries
556         */
557        public Iterator entryIterator() {
558            final Iterator iter = keyIterator();
559            return new Iterator() {            
560                public boolean hasNext() {
561                    return iter.hasNext();
562                }            
563                public Object next() {
564                    Object key = iter.next();
565                    Object value = get(key);
566                    return new Entry( BeanMap.this, key, value );
567                }            
568                public void remove() {
569                    throw new UnsupportedOperationException( "remove() not supported for BeanMap" );
570                }
571            };
572        }
573    
574    
575        // Properties
576        //-------------------------------------------------------------------------
577    
578        /**
579         * Returns the bean currently being operated on.  The return value may
580         * be null if this map is empty.
581         *
582         * @return the bean being operated on by this map
583         */
584        public Object getBean() {
585            return bean;
586        }
587    
588        /**
589         * Sets the bean to be operated on by this map.  The given value may
590         * be null, in which case this map will be empty.
591         *
592         * @param newBean  the new bean to operate on
593         */
594        public void setBean( Object newBean ) {
595            bean = newBean;
596            reinitialise();
597        }
598    
599        /**
600         * Returns the accessor for the property with the given name.
601         *
602         * @param name  the name of the property 
603         * @return the accessor method for the property, or null
604         */
605        public Method getReadMethod(String name) {
606            return (Method) readMethods.get(name);
607        }
608    
609        /**
610         * Returns the mutator for the property with the given name.
611         *
612         * @param name  the name of the property
613         * @return the mutator method for the property, or null
614         */
615        public Method getWriteMethod(String name) {
616            return (Method) writeMethods.get(name);
617        }
618    
619    
620        // Implementation methods
621        //-------------------------------------------------------------------------
622    
623        /**
624         * Returns the accessor for the property with the given name.
625         *
626         * @param name  the name of the property 
627         * @return null if the name is null; null if the name is not a 
628         * {@link String}; null if no such property exists; or the accessor
629         *  method for that property
630         */
631        protected Method getReadMethod( Object name ) {
632            return (Method) readMethods.get( name );
633        }
634    
635        /**
636         * Returns the mutator for the property with the given name.
637         *
638         * @param name  the name of the 
639         * @return null if the name is null; null if the name is not a 
640         * {@link String}; null if no such property exists; null if the 
641         * property is read-only; or the mutator method for that property
642         */
643        protected Method getWriteMethod( Object name ) {
644            return (Method) writeMethods.get( name );
645        }
646    
647        /**
648         * Reinitializes this bean.  Called during {@link #setBean(Object)}.
649         * Does introspection to find properties.
650         */
651        protected void reinitialise() {
652            readMethods.clear();
653            writeMethods.clear();
654            types.clear();
655            initialise();
656        }
657    
658        private void initialise() {
659            if(getBean() == null) {
660                return;
661            }
662    
663            Class  beanClass = getBean().getClass();
664            try {
665                //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
666                BeanInfo beanInfo = Introspector.getBeanInfo( beanClass );
667                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
668                if ( propertyDescriptors != null ) {
669                    for ( int i = 0; i < propertyDescriptors.length; i++ ) {
670                        PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
671                        if ( propertyDescriptor != null ) {
672                            String name = propertyDescriptor.getName();
673                            Method readMethod = propertyDescriptor.getReadMethod();
674                            Method writeMethod = propertyDescriptor.getWriteMethod();
675                            Class aType = propertyDescriptor.getPropertyType();
676    
677                            if ( readMethod != null ) {
678                                readMethods.put( name, readMethod );
679                            }
680                            if ( writeMethod != null ) {
681                                writeMethods.put( name, writeMethod );
682                            }
683                            types.put( name, aType );
684                        }
685                    }
686                }
687            }
688            catch ( IntrospectionException e ) {
689                logWarn(  e );
690            }
691        }
692    
693        /**
694         * Called during a successful {@link #put(Object,Object)} operation.
695         * Default implementation does nothing.  Override to be notified of
696         * property changes in the bean caused by this map.
697         *
698         * @param key  the name of the property that changed
699         * @param oldValue  the old value for that property
700         * @param newValue  the new value for that property
701         */
702        protected void firePropertyChange( Object key, Object oldValue, Object newValue ) {
703        }
704    
705        // Implementation classes
706        //-------------------------------------------------------------------------
707    
708        /**
709         * Map entry used by {@link BeanMap}.
710         */
711        protected static class Entry extends AbstractMapEntry {        
712            private BeanMap owner;
713            
714            /**
715             * Constructs a new <code>Entry</code>.
716             *
717             * @param owner  the BeanMap this entry belongs to
718             * @param key  the key for this entry
719             * @param value  the value for this entry
720             */
721            protected Entry( BeanMap owner, Object key, Object value ) {
722                super( key, value );
723                this.owner = owner;
724            }
725    
726            /**
727             * Sets the value.
728             *
729             * @param value  the new value for the entry
730             * @return the old value for the entry
731             */
732            public Object setValue(Object value) {
733                Object key = getKey();
734                Object oldValue = owner.get( key );
735    
736                owner.put( key, value );
737                Object newValue = owner.get( key );
738                super.setValue( newValue );
739                return oldValue;
740            }
741        }
742    
743        /**
744         * Creates an array of parameters to pass to the given mutator method.
745         * If the given object is not the right type to pass to the method 
746         * directly, it will be converted using {@link #convertType(Class,Object)}.
747         *
748         * @param method  the mutator method
749         * @param value  the value to pass to the mutator method
750         * @return an array containing one object that is either the given value
751         *   or a transformed value
752         * @throws IllegalAccessException if {@link #convertType(Class,Object)}
753         *   raises it
754         * @throws IllegalArgumentException if any other exception is raised
755         *   by {@link #convertType(Class,Object)}
756         * @throws ClassCastException if an error occurs creating the method args
757         */
758        protected Object[] createWriteMethodArguments( Method method, Object value ) 
759            throws IllegalAccessException, ClassCastException {            
760            try {
761                if ( value != null ) {
762                    Class[] types = method.getParameterTypes();
763                    if ( types != null && types.length > 0 ) {
764                        Class paramType = types[0];
765                        if ( ! paramType.isAssignableFrom( value.getClass() ) ) {
766                            value = convertType( paramType, value );
767                        }
768                    }
769                }
770                Object[] answer = { value };
771                return answer;
772            }
773            catch ( InvocationTargetException e ) {
774                logInfo( e );
775                throw new IllegalArgumentException( e.getMessage() );
776            }
777            catch ( InstantiationException e ) {
778                logInfo( e );
779                throw new IllegalArgumentException( e.getMessage() );
780            }
781        }
782    
783        /**
784         * Converts the given value to the given type.  First, reflection is
785         * is used to find a public constructor declared by the given class 
786         * that takes one argument, which must be the precise type of the 
787         * given value.  If such a constructor is found, a new object is
788         * created by passing the given value to that constructor, and the
789         * newly constructed object is returned.<P>
790         *
791         * If no such constructor exists, and the given type is a primitive
792         * type, then the given value is converted to a string using its 
793         * {@link Object#toString() toString()} method, and that string is
794         * parsed into the correct primitive type using, for instance, 
795         * {@link Integer#valueOf(String)} to convert the string into an
796         * <code>int</code>.<P>
797         *
798         * If no special constructor exists and the given type is not a 
799         * primitive type, this method returns the original value.
800         *
801         * @param newType  the type to convert the value to
802         * @param value  the value to convert
803         * @return the converted value
804         * @throws NumberFormatException if newType is a primitive type, and 
805         *  the string representation of the given value cannot be converted
806         *  to that type
807         * @throws InstantiationException  if the constructor found with 
808         *  reflection raises it
809         * @throws InvocationTargetException  if the constructor found with
810         *  reflection raises it
811         * @throws IllegalAccessException  never
812         * @throws IllegalArgumentException  never
813         */
814        protected Object convertType( Class newType, Object value ) 
815            throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
816            
817            // try call constructor
818            Class[] types = { value.getClass() };
819            try {
820                Constructor constructor = newType.getConstructor( types );        
821                Object[] arguments = { value };
822                return constructor.newInstance( arguments );
823            }
824            catch ( NoSuchMethodException e ) {
825                // try using the transformers
826                Transformer transformer = getTypeTransformer( newType );
827                if ( transformer != null ) {
828                    return transformer.transform( value );
829                }
830                return value;
831            }
832        }
833    
834        /**
835         * Returns a transformer for the given primitive type.
836         *
837         * @param aType  the primitive type whose transformer to return
838         * @return a transformer that will convert strings into that type,
839         *  or null if the given type is not a primitive type
840         */
841        protected Transformer getTypeTransformer( Class aType ) {
842            return (Transformer) typeTransformers.get( aType );
843        }
844    
845        /**
846         * Logs the given exception to <code>System.out</code>.  Used to display
847         * warnings while accessing/mutating the bean.
848         *
849         * @param ex  the exception to log
850         */
851        protected void logInfo(Exception ex) {
852            // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
853            System.out.println( "INFO: Exception: " + ex );
854        }
855    
856        /**
857         * Logs the given exception to <code>System.err</code>.  Used to display
858         * errors while accessing/mutating the bean.
859         *
860         * @param ex  the exception to log
861         */
862        protected void logWarn(Exception ex) {
863            // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
864            System.out.println( "WARN: Exception: " + ex );
865            ex.printStackTrace();
866        }
867    }