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 */
017package org.apache.commons.beanutils;
018
019import java.beans.BeanInfo;
020import java.beans.IntrospectionException;
021import java.beans.Introspector;
022import java.beans.PropertyDescriptor;
023import java.lang.reflect.Constructor;
024import java.lang.reflect.InvocationTargetException;
025import java.lang.reflect.Method;
026import java.util.AbstractMap;
027import java.util.AbstractSet;
028import java.util.ArrayList;
029import java.util.Collection;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.Iterator;
033import java.util.Map;
034import java.util.Set;
035
036import org.apache.commons.collections.Transformer;
037import org.apache.commons.collections.keyvalue.AbstractMapEntry;
038
039/**
040 * An implementation of Map for JavaBeans which uses introspection to
041 * get and put properties in the bean.
042 * <p>
043 * If an exception occurs during attempts to get or set a property then the
044 * property is considered non existent in the Map
045 *
046 */
047public class BeanMap extends AbstractMap<Object, Object> implements Cloneable {
048
049    /**
050     * Map entry used by {@link BeanMap}.
051     */
052    protected static class Entry extends AbstractMapEntry {
053        private final BeanMap owner;
054
055        /**
056         * Constructs a new <code>Entry</code>.
057         *
058         * @param owner  the BeanMap this entry belongs to
059         * @param key  the key for this entry
060         * @param value  the value for this entry
061         */
062        protected Entry( final BeanMap owner, final Object key, final Object value ) {
063            super( key, value );
064            this.owner = owner;
065        }
066
067        /**
068         * Sets the value.
069         *
070         * @param value  the new value for the entry
071         * @return the old value for the entry
072         */
073        @Override
074        public Object setValue(final Object value) {
075            final Object key = getKey();
076            final Object oldValue = owner.get( key );
077
078            owner.put( key, value );
079            final Object newValue = owner.get( key );
080            super.setValue( newValue );
081            return oldValue;
082        }
083    }
084
085    /**
086     * An empty array.  Used to invoke accessors via reflection.
087     */
088    public static final Object[] NULL_ARGUMENTS = {};
089    /**
090     * Maps primitive Class types to transformers.  The transformer
091     * transform strings into the appropriate primitive wrapper.
092     *
093     * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
094     */
095    private static final Map<Class<? extends Object>, Transformer> typeTransformers =
096            Collections.unmodifiableMap(createTypeTransformers());
097    /**
098     * This HashMap has been made unmodifiable to prevent issues when
099     * loaded in a shared ClassLoader enviroment.
100     *
101     * @see "http://issues.apache.org/jira/browse/BEANUTILS-112"
102     * @deprecated Use {@link BeanMap#getTypeTransformer(Class)} method
103     */
104    @Deprecated
105    public static HashMap defaultTransformers = new HashMap() {
106        private static final long serialVersionUID = 1L;
107        @Override
108        public void clear() {
109            throw new UnsupportedOperationException();
110        }
111        @Override
112        public boolean containsKey(final Object key) {
113            return typeTransformers.containsKey(key);
114        }
115        @Override
116        public boolean containsValue(final Object value) {
117            return typeTransformers.containsValue(value);
118        }
119        @Override
120        public Set entrySet() {
121            return typeTransformers.entrySet();
122        }
123        @Override
124        public Object get(final Object key) {
125            return typeTransformers.get(key);
126        }
127        @Override
128        public boolean isEmpty() {
129            return false;
130        }
131        @Override
132        public Set keySet() {
133            return typeTransformers.keySet();
134        }
135        @Override
136        public Object put(final Object key, final Object value) {
137            throw new UnsupportedOperationException();
138        }
139        @Override
140        public void putAll(final Map m) {
141            throw new UnsupportedOperationException();
142        }
143        @Override
144        public Object remove(final Object key) {
145            throw new UnsupportedOperationException();
146        }
147        @Override
148        public int size() {
149            return typeTransformers.size();
150        }
151        @Override
152        public Collection values() {
153            return typeTransformers.values();
154        }
155    };
156
157    private static Map<Class<? extends Object>, Transformer> createTypeTransformers() {
158        final Map<Class<? extends Object>, Transformer> defaultTransformers =
159                new HashMap<>();
160        defaultTransformers.put(
161            Boolean.TYPE,
162            input -> Boolean.valueOf( input.toString() )
163        );
164        defaultTransformers.put(
165            Character.TYPE,
166            input -> Character.valueOf(input.toString().charAt( 0 ))
167        );
168        defaultTransformers.put(
169            Byte.TYPE,
170            input -> Byte.valueOf( input.toString() )
171        );
172        defaultTransformers.put(
173            Short.TYPE,
174            input -> Short.valueOf( input.toString() )
175        );
176        defaultTransformers.put(
177            Integer.TYPE,
178            input -> Integer.valueOf( input.toString() )
179        );
180        defaultTransformers.put(
181            Long.TYPE,
182            input -> Long.valueOf( input.toString() )
183        );
184        defaultTransformers.put(
185            Float.TYPE,
186            input -> Float.valueOf( input.toString() )
187        );
188        defaultTransformers.put(
189            Double.TYPE,
190            input -> Double.valueOf( input.toString() )
191        );
192        return defaultTransformers;
193    }
194
195    private transient Object bean;
196
197    private transient HashMap<String, Method> readMethods = new HashMap<>();
198
199    private transient HashMap<String, Method> writeMethods = new HashMap<>();
200
201    // Constructors
202    //-------------------------------------------------------------------------
203
204    private transient HashMap<String, Class<? extends Object>> types = new HashMap<>();
205
206    /**
207     * Constructs a new empty <code>BeanMap</code>.
208     */
209    public BeanMap() {
210    }
211
212    // Map interface
213    //-------------------------------------------------------------------------
214
215    /**
216     * Constructs a new <code>BeanMap</code> that operates on the
217     * specified bean.  If the given bean is <code>null</code>, then
218     * this map will be empty.
219     *
220     * @param bean  the bean for this map to operate on
221     */
222    public BeanMap(final Object bean) {
223        this.bean = bean;
224        initialise();
225    }
226
227    /**
228     * This method reinitializes the bean map to have default values for the
229     * bean's properties.  This is accomplished by constructing a new instance
230     * of the bean which the map uses as its underlying data source.  This
231     * behavior for <code>clear()</code> differs from the Map contract in that
232     * the mappings are not actually removed from the map (the mappings for a
233     * BeanMap are fixed).
234     */
235    @Override
236    public void clear() {
237        if (bean == null) {
238            return;
239        }
240        Class<? extends Object> beanClass = null;
241        try {
242            beanClass = bean.getClass();
243            bean = beanClass.getConstructor().newInstance();
244        } catch (final Exception e) {
245            throw new UnsupportedOperationException("Could not create new instance of class: " + beanClass, e);
246        }
247    }
248
249    /**
250     * Clone this bean map using the following process:
251     *
252     * <ul>
253     * <li>If there is no underlying bean, return a cloned BeanMap without a
254     * bean.
255     * </li>
256     * <li>Since there is an underlying bean, try to instantiate a new bean of
257     * the same type using Class.newInstance().
258     * </li>
259     * <li>If the instantiation fails, throw a CloneNotSupportedException
260     * </li>
261     * <li>Clone the bean map and set the newly instantiated bean as the
262     * underlying bean for the bean map.
263     * </li>
264     * <li>Copy each property that is both readable and writable from the
265     * existing object to a cloned bean map.
266     * </li>
267     * <li>If anything fails along the way, throw a
268     * CloneNotSupportedException.
269     * </li>
270     * </ul>
271     *
272     * @return a cloned instance of this bean map
273     * @throws CloneNotSupportedException if the underlying bean
274     * cannot be cloned
275     */
276    @Override
277    public Object clone() throws CloneNotSupportedException {
278        final BeanMap newMap = (BeanMap) super.clone();
279        if (bean == null) {
280            // no bean, just an empty bean map at the moment. return a newly
281            // cloned and empty bean map.
282            return newMap;
283        }
284        Object newBean = null;
285        final Class<? extends Object> beanClass = bean.getClass(); // Cannot throw Exception
286        try {
287            newBean = beanClass.getConstructor().newInstance();
288        } catch (final Exception e) {
289            // unable to instantiate
290            final CloneNotSupportedException cnse = new CloneNotSupportedException(
291                    "Unable to instantiate the underlying bean \"" + beanClass.getName() + "\": " + e);
292            cnse.initCause(e);
293            throw cnse;
294        }
295        try {
296            newMap.setBean(newBean);
297        } catch (final Exception exception) {
298            final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to set bean in the cloned bean map: " + exception);
299            cnse.initCause(exception);
300            throw cnse;
301        }
302        try {
303            for (final Object key : readMethods.keySet()) {
304                if (getWriteMethod(key) != null) {
305                    newMap.put(key, get(key));
306                }
307            }
308        } catch (final Exception exception) {
309            final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to copy bean values to cloned bean map: " + exception);
310            cnse.initCause(exception);
311            throw cnse;
312        }
313
314        return newMap;
315    }
316
317    /**
318     * Returns true if the bean defines a property with the given name.
319     * <p>
320     * The given name must be a <code>String</code>; if not, this method
321     * returns false. This method will also return false if the bean
322     * does not define a property with that name.
323     * <p>
324     * Write-only properties will not be matched as the test operates against
325     * property read methods.
326     *
327     * @param name  the name of the property to check
328     * @return false if the given name is null or is not a <code>String</code>;
329     *   false if the bean does not define a property with that name; or
330     *   true if the bean does define a property with that name
331     */
332    @Override
333    public boolean containsKey(final Object name) {
334        final Method method = getReadMethod(name);
335        return method != null;
336    }
337
338    /**
339     * Returns true if the bean defines a property whose current value is
340     * the given object.
341     *
342     * @param value  the value to check
343     * @return false  true if the bean has at least one property whose
344     *   current value is that object, false otherwise
345     */
346    @Override
347    public boolean containsValue(final Object value) {
348        // use default implementation
349        return super.containsValue(value);
350    }
351
352    /**
353     * Converts the given value to the given type.  First, reflection is
354     * is used to find a public constructor declared by the given class
355     * that takes one argument, which must be the precise type of the
356     * given value.  If such a constructor is found, a new object is
357     * created by passing the given value to that constructor, and the
358     * newly constructed object is returned.<P>
359     *
360     * If no such constructor exists, and the given type is a primitive
361     * type, then the given value is converted to a string using its
362     * {@link Object#toString() toString()} method, and that string is
363     * parsed into the correct primitive type using, for instance,
364     * {@link Integer#valueOf(String)} to convert the string into an
365     * <code>int</code>.<P>
366     *
367     * If no special constructor exists and the given type is not a
368     * primitive type, this method returns the original value.
369     *
370     * @param newType  the type to convert the value to
371     * @param value  the value to convert
372     * @return the converted value
373     * @throws NumberFormatException if newType is a primitive type, and
374     *  the string representation of the given value cannot be converted
375     *  to that type
376     * @throws InstantiationException  if the constructor found with
377     *  reflection raises it
378     * @throws InvocationTargetException  if the constructor found with
379     *  reflection raises it
380     * @throws IllegalAccessException  never
381     * @throws IllegalArgumentException  never
382     */
383    protected Object convertType( final Class<?> newType, final Object value )
384        throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
385
386        // try call constructor
387        final Class<?>[] types = { value.getClass() };
388        try {
389            final Constructor<?> constructor = newType.getConstructor( types );
390            final Object[] arguments = { value };
391            return constructor.newInstance( arguments );
392        }
393        catch ( final NoSuchMethodException e ) {
394            // try using the transformers
395            final Transformer transformer = getTypeTransformer( newType );
396            if ( transformer != null ) {
397                return transformer.transform( value );
398            }
399            return value;
400        }
401    }
402
403    /**
404     * Creates an array of parameters to pass to the given mutator method.
405     * If the given object is not the right type to pass to the method
406     * directly, it will be converted using {@link #convertType(Class,Object)}.
407     *
408     * @param method  the mutator method
409     * @param value  the value to pass to the mutator method
410     * @return an array containing one object that is either the given value
411     *   or a transformed value
412     * @throws IllegalAccessException if {@link #convertType(Class,Object)}
413     *   raises it
414     * @throws IllegalArgumentException if any other exception is raised
415     *   by {@link #convertType(Class,Object)}
416     * @throws ClassCastException if an error occurs creating the method args
417     */
418    protected Object[] createWriteMethodArguments( final Method method, Object value )
419        throws IllegalAccessException, ClassCastException {
420        try {
421            if ( value != null ) {
422                final Class<? extends Object>[] types = method.getParameterTypes();
423                if ( types != null && types.length > 0 ) {
424                    final Class<? extends Object> paramType = types[0];
425                    if ( ! paramType.isAssignableFrom( value.getClass() ) ) {
426                        value = convertType( paramType, value );
427                    }
428                }
429            }
430            return new Object[] { value };
431        }
432        catch ( final InvocationTargetException | InstantiationException e ) {
433            throw new IllegalArgumentException(e.getMessage(), e);
434        }
435    }
436
437    /**
438     * Convenience method for getting an iterator over the entries.
439     *
440     * @return an iterator over the entries
441     */
442    public Iterator<Map.Entry<Object, Object>> entryIterator() {
443        final Iterator<String> iter = keyIterator();
444        return new Iterator<Map.Entry<Object, Object>>() {
445            @Override
446            public boolean hasNext() {
447                return iter.hasNext();
448            }
449            @Override
450            public Map.Entry<Object, Object> next() {
451                final Object key = iter.next();
452                final Object value = get(key);
453                @SuppressWarnings("unchecked")
454                final
455                // This should not cause any problems; the key is actually a
456                // string, but it does no harm to expose it as Object
457                Map.Entry<Object, Object> tmpEntry = new Entry( BeanMap.this, key, value );
458                return tmpEntry;
459            }
460            @Override
461            public void remove() {
462                throw new UnsupportedOperationException( "remove() not supported for BeanMap" );
463            }
464        };
465    }
466
467    /**
468     * Gets a Set of MapEntry objects that are the mappings for this BeanMap.
469     * <p>
470     * Each MapEntry can be set but not removed.
471     *
472     * @return the unmodifiable set of mappings
473     */
474    @Override
475    public Set<Map.Entry<Object, Object>> entrySet() {
476        return Collections.unmodifiableSet(new AbstractSet<Map.Entry<Object, Object>>() {
477            @Override
478            public Iterator<Map.Entry<Object, Object>> iterator() {
479                return entryIterator();
480            }
481            @Override
482            public int size() {
483              return BeanMap.this.readMethods.size();
484            }
485        });
486    }
487
488    /**
489     * Called during a successful {@link #put(Object,Object)} operation.
490     * Default implementation does nothing.  Override to be notified of
491     * property changes in the bean caused by this map.
492     *
493     * @param key  the name of the property that changed
494     * @param oldValue  the old value for that property
495     * @param newValue  the new value for that property
496     */
497    protected void firePropertyChange( final Object key, final Object oldValue, final Object newValue ) {
498    }
499
500    /**
501     * Returns the value of the bean's property with the given name.
502     * <p>
503     * The given name must be a {@link String} and must not be
504     * null; otherwise, this method returns <code>null</code>.
505     * If the bean defines a property with the given name, the value of
506     * that property is returned.  Otherwise, <code>null</code> is
507     * returned.
508     * <p>
509     * Write-only properties will not be matched as the test operates against
510     * property read methods.
511     *
512     * @param name  the name of the property whose value to return
513     * @return  the value of the property with that name
514     */
515    @Override
516    public Object get(final Object name) {
517        if ( bean != null ) {
518            final Method method = getReadMethod( name );
519            if ( method != null ) {
520                try {
521                    return method.invoke( bean, NULL_ARGUMENTS );
522                }
523                catch ( final IllegalAccessException | IllegalArgumentException | InvocationTargetException | NullPointerException e ) {
524                    logWarn(  e );
525                }
526            }
527        }
528        return null;
529    }
530
531    /**
532     * Returns the bean currently being operated on.  The return value may
533     * be null if this map is empty.
534     *
535     * @return the bean being operated on by this map
536     */
537    public Object getBean() {
538        return bean;
539    }
540
541    // Helper methods
542    //-------------------------------------------------------------------------
543
544    /**
545     * Returns the accessor for the property with the given name.
546     *
547     * @param name  the name of the property
548     * @return null if the name is null; null if the name is not a
549     * {@link String}; null if no such property exists; or the accessor
550     *  method for that property
551     */
552    protected Method getReadMethod( final Object name ) {
553        return readMethods.get( name );
554    }
555
556    /**
557     * Returns the accessor for the property with the given name.
558     *
559     * @param name  the name of the property
560     * @return the accessor method for the property, or null
561     */
562    public Method getReadMethod(final String name) {
563        return readMethods.get(name);
564    }
565
566    /**
567     * Returns the type of the property with the given name.
568     *
569     * @param name  the name of the property
570     * @return  the type of the property, or <code>null</code> if no such
571     *  property exists
572     */
573    public Class<?> getType(final String name) {
574        return types.get( name );
575    }
576
577    /**
578     * Returns a transformer for the given primitive type.
579     *
580     * @param aType  the primitive type whose transformer to return
581     * @return a transformer that will convert strings into that type,
582     *  or null if the given type is not a primitive type
583     */
584    protected Transformer getTypeTransformer( final Class<?> aType ) {
585        return typeTransformers.get( aType );
586    }
587
588    // Properties
589    //-------------------------------------------------------------------------
590
591    /**
592     * Returns the mutator for the property with the given name.
593     *
594     * @param name  the name of the
595     * @return null if the name is null; null if the name is not a
596     * {@link String}; null if no such property exists; null if the
597     * property is read-only; or the mutator method for that property
598     */
599    protected Method getWriteMethod( final Object name ) {
600        return writeMethods.get( name );
601    }
602
603    /**
604     * Returns the mutator for the property with the given name.
605     *
606     * @param name  the name of the property
607     * @return the mutator method for the property, or null
608     */
609    public Method getWriteMethod(final String name) {
610        return writeMethods.get(name);
611    }
612
613    private void initialise() {
614        if(getBean() == null) {
615            return;
616        }
617
618        final Class<? extends Object>  beanClass = getBean().getClass();
619        try {
620            //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
621            final BeanInfo beanInfo = Introspector.getBeanInfo( beanClass );
622            final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
623            if ( propertyDescriptors != null ) {
624                for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
625                    if ( propertyDescriptor != null ) {
626                        final String name = propertyDescriptor.getName();
627                        final Method readMethod = propertyDescriptor.getReadMethod();
628                        final Method writeMethod = propertyDescriptor.getWriteMethod();
629                        final Class<? extends Object> aType = propertyDescriptor.getPropertyType();
630
631                        if ( readMethod != null ) {
632                            readMethods.put( name, readMethod );
633                        }
634                        if ( writeMethod != null ) {
635                            writeMethods.put( name, writeMethod );
636                        }
637                        types.put( name, aType );
638                    }
639                }
640            }
641        }
642        catch ( final IntrospectionException e ) {
643            logWarn(  e );
644        }
645    }
646
647    /**
648     * Convenience method for getting an iterator over the keys.
649     * <p>
650     * Write-only properties will not be returned in the iterator.
651     *
652     * @return an iterator over the keys
653     */
654    public Iterator<String> keyIterator() {
655        return readMethods.keySet().iterator();
656    }
657
658    // Implementation methods
659    //-------------------------------------------------------------------------
660
661    /**
662     * Get the keys for this BeanMap.
663     * <p>
664     * Write-only properties are <strong>not</strong> included in the returned set of
665     * property names, although it is possible to set their value and to get
666     * their type.
667     *
668     * @return BeanMap keys.  The Set returned by this method is not
669     *        modifiable.
670     */
671    @SuppressWarnings({ "unchecked", "rawtypes" })
672    // The set actually contains strings; however, because it cannot be
673    // modified there is no danger in selling it as Set<Object>
674    @Override
675    public Set<Object> keySet() {
676        return Collections.unmodifiableSet((Set) readMethods.keySet());
677    }
678
679    /**
680     * Logs the given exception to <code>System.out</code>.  Used to display
681     * warnings while accessing/mutating the bean.
682     *
683     * @param ex  the exception to log
684     */
685    protected void logInfo(final Exception ex) {
686        // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
687        System.out.println( "INFO: Exception: " + ex );
688    }
689
690    /**
691     * Logs the given exception to <code>System.err</code>.  Used to display
692     * errors while accessing/mutating the bean.
693     *
694     * @param ex  the exception to log
695     */
696    protected void logWarn(final Exception ex) {
697        // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
698        System.out.println( "WARN: Exception: " + ex );
699        ex.printStackTrace();
700    }
701
702    /**
703     * Sets the bean property with the given name to the given value.
704     *
705     * @param name  the name of the property to set
706     * @param value  the value to set that property to
707     * @return  the previous value of that property
708     * @throws IllegalArgumentException  if the given name is null;
709     *   if the given name is not a {@link String}; if the bean doesn't
710     *   define a property with that name; or if the bean property with
711     *   that name is read-only
712     * @throws ClassCastException if an error occurs creating the method args
713     */
714    @Override
715    public Object put(final Object name, final Object value) throws IllegalArgumentException, ClassCastException {
716        if ( bean != null ) {
717            final Object oldValue = get( name );
718            final Method method = getWriteMethod( name );
719            if ( method == null ) {
720                throw new IllegalArgumentException( "The bean of type: "+
721                        bean.getClass().getName() + " has no property called: " + name );
722            }
723            try {
724                final Object[] arguments = createWriteMethodArguments( method, value );
725                method.invoke( bean, arguments );
726
727                final Object newValue = get( name );
728                firePropertyChange( name, oldValue, newValue );
729            }
730            catch ( final InvocationTargetException | IllegalAccessException e ) {
731                throw new IllegalArgumentException(e.getMessage(), e);
732            }
733            return oldValue;
734        }
735        return null;
736    }
737
738    /**
739     * Puts all of the writable properties from the given BeanMap into this
740     * BeanMap. Read-only and Write-only properties will be ignored.
741     *
742     * @param map  the BeanMap whose properties to put
743     */
744    public void putAllWriteable(final BeanMap map) {
745        for (final Object key : map.readMethods.keySet()) {
746            if (getWriteMethod(key) != null) {
747                put(key, map.get(key));
748            }
749        }
750    }
751
752    // Implementation classes
753    //-------------------------------------------------------------------------
754
755    /**
756     * Reinitializes this bean.  Called during {@link #setBean(Object)}.
757     * Does introspection to find properties.
758     */
759    protected void reinitialise() {
760        readMethods.clear();
761        writeMethods.clear();
762        types.clear();
763        initialise();
764    }
765
766    /**
767     * Sets the bean to be operated on by this map.  The given value may
768     * be null, in which case this map will be empty.
769     *
770     * @param newBean  the new bean to operate on
771     */
772    public void setBean( final Object newBean ) {
773        bean = newBean;
774        reinitialise();
775    }
776
777    /**
778     * Returns the number of properties defined by the bean.
779     *
780     * @return  the number of properties defined by the bean
781     */
782    @Override
783    public int size() {
784        return readMethods.size();
785    }
786
787    /**
788     * Renders a string representation of this object.
789     * @return a <code>String</code> representation of this object
790     */
791    @Override
792    public String toString() {
793        return "BeanMap<" + String.valueOf(bean) + ">";
794    }
795
796    /**
797     * Convenience method for getting an iterator over the values.
798     *
799     * @return an iterator over the values
800     */
801    public Iterator<Object> valueIterator() {
802        final Iterator<?> iter = keyIterator();
803        return new Iterator<Object>() {
804            @Override
805            public boolean hasNext() {
806                return iter.hasNext();
807            }
808            @Override
809            public Object next() {
810                final Object key = iter.next();
811                return get(key);
812            }
813            @Override
814            public void remove() {
815                throw new UnsupportedOperationException( "remove() not supported for BeanMap" );
816            }
817        };
818    }
819
820    /**
821     * Returns the values for the BeanMap.
822     *
823     * @return values for the BeanMap.  The returned collection is not
824     *        modifiable.
825     */
826    @Override
827    public Collection<Object> values() {
828        final ArrayList<Object> answer = new ArrayList<>( readMethods.size() );
829        for ( final Iterator<Object> iter = valueIterator(); iter.hasNext(); ) {
830            answer.add( iter.next() );
831        }
832        return Collections.unmodifiableList(answer);
833    }
834}