BeanMap.java

  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.beanutils2;

  18. import java.beans.BeanInfo;
  19. import java.beans.IntrospectionException;
  20. import java.beans.Introspector;
  21. import java.beans.PropertyDescriptor;
  22. import java.lang.reflect.Constructor;
  23. import java.lang.reflect.InvocationTargetException;
  24. import java.lang.reflect.Method;
  25. import java.util.AbstractMap;
  26. import java.util.AbstractSet;
  27. import java.util.ArrayList;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.HashMap;
  31. import java.util.Iterator;
  32. import java.util.Map;
  33. import java.util.Set;
  34. import java.util.function.Function;

  35. /**
  36.  * An implementation of Map for JavaBeans which uses introspection to get and put properties in the bean.
  37.  * <p>
  38.  * If an exception occurs during attempts to get or set a property then the property is considered non existent in the Map
  39.  * </p>
  40.  */
  41. public class BeanMap extends AbstractMap<String, Object> implements Cloneable {

  42.     /**
  43.      * Map entry used by {@link BeanMap}.
  44.      */
  45.     protected static class Entry extends AbstractMap.SimpleEntry<String, Object> {

  46.         private static final long serialVersionUID = 1L;

  47.         /**
  48.          * The owner.
  49.          */
  50.         private final BeanMap owner;

  51.         /**
  52.          * Constructs a new {@code Entry}.
  53.          *
  54.          * @param owner the BeanMap this entry belongs to
  55.          * @param key   the key for this entry
  56.          * @param value the value for this entry
  57.          */
  58.         protected Entry(final BeanMap owner, final String key, final Object value) {
  59.             super(key, value);
  60.             this.owner = owner;
  61.         }

  62.         /**
  63.          * Sets the value.
  64.          *
  65.          * @param value the new value for the entry
  66.          * @return the old value for the entry
  67.          */
  68.         @Override
  69.         public Object setValue(final Object value) {
  70.             final String key = getKey();
  71.             final Object oldValue = owner.get(key);

  72.             owner.put(key, value);
  73.             final Object newValue = owner.get(key);
  74.             super.setValue(newValue);
  75.             return oldValue;
  76.         }
  77.     }

  78.     /**
  79.      * An empty array. Used to invoke accessors via reflection.
  80.      */
  81.     public static final Object[] NULL_ARGUMENTS = {};

  82.     /**
  83.      * Maps primitive Class types to transformers. The transformer transform strings into the appropriate primitive wrapper.
  84.      *
  85.      * N.B. private & unmodifiable replacement for the (public & static) defaultTransformers instance.
  86.      */
  87.     private static final Map<Class<? extends Object>, Function<?, ?>> typeTransformers = Collections.unmodifiableMap(createTypeTransformers());

  88.     private static Map<Class<? extends Object>, Function<?, ?>> createTypeTransformers() {
  89.         final Map<Class<? extends Object>, Function<?, ?>> defTransformers = new HashMap<>();
  90.         defTransformers.put(Boolean.TYPE, input -> Boolean.valueOf(input.toString()));
  91.         defTransformers.put(Character.TYPE, input -> Character.valueOf(input.toString().charAt(0)));
  92.         defTransformers.put(Byte.TYPE, input -> Byte.valueOf(input.toString()));
  93.         defTransformers.put(Short.TYPE, input -> Short.valueOf(input.toString()));
  94.         defTransformers.put(Integer.TYPE, input -> Integer.valueOf(input.toString()));
  95.         defTransformers.put(Long.TYPE, input -> Long.valueOf(input.toString()));
  96.         defTransformers.put(Float.TYPE, input -> Float.valueOf(input.toString()));
  97.         defTransformers.put(Double.TYPE, input -> Double.valueOf(input.toString()));
  98.         return defTransformers;
  99.     }

  100.     private transient Object bean;

  101.     private final transient HashMap<String, Method> readMethods = new HashMap<>();

  102.     private final transient HashMap<String, Method> writeMethods = new HashMap<>();

  103.     private final transient HashMap<String, Class<? extends Object>> types = new HashMap<>();

  104.     /**
  105.      * Constructs a new empty {@code BeanMap}.
  106.      */
  107.     public BeanMap() {
  108.     }

  109.     // Map interface

  110.     /**
  111.      * Constructs a new {@code BeanMap} that operates on the specified bean. If the given bean is {@code null}, then this map will be empty.
  112.      *
  113.      * @param bean the bean for this map to operate on
  114.      */
  115.     public BeanMap(final Object bean) {
  116.         this.bean = bean;
  117.         initialize();
  118.     }

  119.     /**
  120.      * This method reinitializes the bean map to have default values for the bean's properties. This is accomplished by constructing a new instance of the bean
  121.      * which the map uses as its underlying data source. This behavior for {@code clear()} differs from the Map contract in that the mappings are not actually
  122.      * removed from the map (the mappings for a BeanMap are fixed).
  123.      */
  124.     @Override
  125.     public void clear() {
  126.         if (bean == null) {
  127.             return;
  128.         }
  129.         Class<? extends Object> beanClass = null;
  130.         try {
  131.             beanClass = bean.getClass();
  132.             bean = beanClass.newInstance();
  133.         } catch (final Exception e) {
  134.             throw new UnsupportedOperationException("Could not create new instance of class: " + beanClass, e);
  135.         }
  136.     }

  137.     /**
  138.      * Clone this bean map using the following process:
  139.      *
  140.      * <ul>
  141.      * <li>If there is no underlying bean, return a cloned BeanMap without a bean.
  142.      * <li>Since there is an underlying bean, try to instantiate a new bean of the same type using Class.newInstance().
  143.      * <li>If the instantiation fails, throw a CloneNotSupportedException
  144.      * <li>Clone the bean map and set the newly instantiated bean as the underlying bean for the bean map.
  145.      * <li>Copy each property that is both readable and writable from the existing object to a cloned bean map.
  146.      * <li>If anything fails along the way, throw a CloneNotSupportedException.
  147.      * </ul>
  148.      *
  149.      * @return a cloned instance of this bean map
  150.      * @throws CloneNotSupportedException if the underlying bean cannot be cloned
  151.      */
  152.     @Override
  153.     public Object clone() throws CloneNotSupportedException {
  154.         final BeanMap newMap = (BeanMap) super.clone();
  155.         if (bean == null) {
  156.             // no bean, just an empty bean map at the moment. return a newly
  157.             // cloned and empty bean map.
  158.             return newMap;
  159.         }
  160.         Object newBean = null;
  161.         final Class<? extends Object> beanClass = bean.getClass(); // Cannot throw Exception
  162.         try {
  163.             newBean = beanClass.newInstance();
  164.         } catch (final Exception e) {
  165.             // unable to instantiate
  166.             final CloneNotSupportedException cnse = new CloneNotSupportedException(
  167.                     "Unable to instantiate the underlying bean \"" + beanClass.getName() + "\": " + e);
  168.             cnse.initCause(e);
  169.             throw cnse;
  170.         }
  171.         try {
  172.             newMap.setBean(newBean);
  173.         } catch (final Exception e) {
  174.             final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to set bean in the cloned bean map: " + e);
  175.             cnse.initCause(e);
  176.             throw cnse;
  177.         }
  178.         try {
  179.             // copy only properties that are readable and writable. If its
  180.             // not readable, we can't get the value from the old map. If
  181.             // its not writable, we can't write a value into the new map.
  182.             readMethods.keySet().forEach(key -> {
  183.                 if (getWriteMethod(key) != null) {
  184.                     newMap.put(key, get(key));
  185.                 }
  186.             });
  187.         } catch (final Exception e) {
  188.             final CloneNotSupportedException cnse = new CloneNotSupportedException("Unable to copy bean values to cloned bean map: " + e);
  189.             cnse.initCause(e);
  190.             throw cnse;
  191.         }
  192.         return newMap;
  193.     }

  194.     /**
  195.      * Returns true if the bean defines a property with the given name.
  196.      * <p>
  197.      * The given name must be a {@code String}; if not, this method returns false. This method will also return false if the bean does not define a property
  198.      * with that name.
  199.      * </p>
  200.      * <p>
  201.      * Write-only properties will not be matched as the test operates against property read methods.
  202.      * </p>
  203.      *
  204.      * @param name the name of the property to check
  205.      * @return false if the given name is null or is not a {@code String}; false if the bean does not define a property with that name; or true if the bean does
  206.      *         define a property with that name
  207.      */
  208.     @Override
  209.     public boolean containsKey(final Object name) {
  210.         return getReadMethod(name) != null;
  211.     }

  212.     /**
  213.      * Converts the given value to the given type. First, reflection is used to find a public constructor declared by the given class that takes one argument,
  214.      * which must be the precise type of the given value. If such a constructor is found, a new object is created by passing the given value to that
  215.      * constructor, and the newly constructed object is returned.
  216.      * <p>
  217.      * If no such constructor exists, and the given type is a primitive type, then the given value is converted to a string using its {@link Object#toString()
  218.      * toString()} method, and that string is parsed into the correct primitive type using, for instance, {@link Integer#valueOf(String)} to convert the string
  219.      * into an {@code int}.
  220.      * </p>
  221.      * <p>
  222.      * If no special constructor exists and the given type is not a primitive type, this method returns the original value.
  223.      * </p>
  224.      *
  225.      * @param <R>     The return type.
  226.      * @param newType the type to convert the value to
  227.      * @param value   the value to convert
  228.      * @return the converted value
  229.      * @throws NumberFormatException     if newType is a primitive type, and the string representation of the given value cannot be converted to that type
  230.      * @throws InstantiationException    if the constructor found with reflection raises it
  231.      * @throws InvocationTargetException if the constructor found with reflection raises it
  232.      * @throws IllegalAccessException    never
  233.      * @throws IllegalArgumentException  never
  234.      */
  235.     protected <R> Object convertType(final Class<R> newType, final Object value)
  236.             throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

  237.         // try call constructor
  238.         try {
  239.             final Constructor<R> constructor = newType.getConstructor(value.getClass());
  240.             return constructor.newInstance(value);
  241.         } catch (final NoSuchMethodException e) {
  242.             // try using the transformers
  243.             final Function<Object, R> transformer = getTypeTransformer(newType);
  244.             if (transformer != null) {
  245.                 return transformer.apply(value);
  246.             }
  247.             return value;
  248.         }
  249.     }

  250.     /**
  251.      * Creates an array of parameters to pass to the given mutator method. If the given object is not the right type to pass to the method directly, it will be
  252.      * converted using {@link #convertType(Class,Object)}.
  253.      *
  254.      * @param method the mutator method
  255.      * @param value  the value to pass to the mutator method
  256.      * @return an array containing one object that is either the given value or a transformed value
  257.      * @throws IllegalAccessException   if {@link #convertType(Class,Object)} raises it
  258.      * @throws IllegalArgumentException if any other exception is raised by {@link #convertType(Class,Object)}
  259.      * @throws ClassCastException       if an error occurs creating the method args
  260.      */
  261.     protected Object[] createWriteMethodArguments(final Method method, Object value) throws IllegalAccessException, ClassCastException {
  262.         try {
  263.             if (value != null) {
  264.                 final Class<? extends Object>[] paramTypes = method.getParameterTypes();
  265.                 if (paramTypes != null && paramTypes.length > 0) {
  266.                     final Class<? extends Object> paramType = paramTypes[0];
  267.                     if (!paramType.isAssignableFrom(value.getClass())) {
  268.                         value = convertType(paramType, value);
  269.                     }
  270.                 }
  271.             }

  272.             return new Object[] { value };
  273.         } catch (final InvocationTargetException | InstantiationException e) {
  274.             throw new IllegalArgumentException(e.getMessage(), e);
  275.         }
  276.     }

  277.     /**
  278.      * Convenience method for getting an iterator over the entries.
  279.      *
  280.      * @return an iterator over the entries
  281.      */
  282.     public Iterator<Map.Entry<String, Object>> entryIterator() {
  283.         final Iterator<String> iter = keyIterator();
  284.         return new Iterator<Map.Entry<String, Object>>() {
  285.             @Override
  286.             public boolean hasNext() {
  287.                 return iter.hasNext();
  288.             }

  289.             @Override
  290.             public Map.Entry<String, Object> next() {
  291.                 final String key = iter.next();
  292.                 final Object value = get(key);
  293.                 // This should not cause any problems; the key is actually a
  294.                 // string, but it does no harm to expose it as Object
  295.                 return new Entry(BeanMap.this, key, value);
  296.             }

  297.             @Override
  298.             public void remove() {
  299.                 throw new UnsupportedOperationException("remove() not supported for BeanMap");
  300.             }
  301.         };
  302.     }

  303.     /**
  304.      * Gets a Set of MapEntry objects that are the mappings for this BeanMap.
  305.      * <p>
  306.      * Each MapEntry can be set but not removed.
  307.      * </p>
  308.      *
  309.      * @return the unmodifiable set of mappings
  310.      */
  311.     @Override
  312.     public Set<Map.Entry<String, Object>> entrySet() {
  313.         return Collections.unmodifiableSet(new AbstractSet<Map.Entry<String, Object>>() {
  314.             @Override
  315.             public Iterator<Map.Entry<String, Object>> iterator() {
  316.                 return entryIterator();
  317.             }

  318.             @Override
  319.             public int size() {
  320.                 return BeanMap.this.readMethods.size();
  321.             }
  322.         });
  323.     }

  324.     /**
  325.      * Called during a successful {@link #put(String,Object)} operation. Default implementation does nothing. Override to be notified of property changes in the
  326.      * bean caused by this map.
  327.      *
  328.      * @param key      the name of the property that changed
  329.      * @param oldValue the old value for that property
  330.      * @param newValue the new value for that property
  331.      */
  332.     protected void firePropertyChange(final Object key, final Object oldValue, final Object newValue) {
  333.         // noop
  334.     }

  335.     /**
  336.      * Gets the value of the bean's property with the given name.
  337.      * <p>
  338.      * The given name must be a {@link String} and must not be null; otherwise, this method returns {@code null}. If the bean defines a property with the given
  339.      * name, the value of that property is returned. Otherwise, {@code null} is returned.
  340.      * </p>
  341.      * <p>
  342.      * Write-only properties will not be matched as the test operates against property read methods.
  343.      * </p>
  344.      *
  345.      * @param name the name of the property whose value to return
  346.      * @return the value of the property with that name
  347.      */
  348.     @Override
  349.     public Object get(final Object name) {
  350.         if (bean != null) {
  351.             final Method method = getReadMethod(name);
  352.             if (method != null) {
  353.                 try {
  354.                     return method.invoke(bean, NULL_ARGUMENTS);
  355.                 } catch (final IllegalAccessException | NullPointerException | InvocationTargetException | IllegalArgumentException e) {
  356.                     logWarn(e);
  357.                 }
  358.             }
  359.         }
  360.         return null;
  361.     }

  362.     /**
  363.      * Gets the bean currently being operated on. The return value may be null if this map is empty.
  364.      *
  365.      * @return the bean being operated on by this map
  366.      */
  367.     public Object getBean() {
  368.         return bean;
  369.     }

  370.     // Helper methods

  371.     /**
  372.      * Gets the accessor for the property with the given name.
  373.      *
  374.      * @param name the name of the property
  375.      * @return null if the name is null; null if the name is not a {@link String}; null if no such property exists; or the accessor method for that property
  376.      */
  377.     protected Method getReadMethod(final Object name) {
  378.         return readMethods.get(name);
  379.     }

  380.     /**
  381.      * Gets the accessor for the property with the given name.
  382.      *
  383.      * @param name the name of the property
  384.      * @return the accessor method for the property, or null
  385.      */
  386.     public Method getReadMethod(final String name) {
  387.         return readMethods.get(name);
  388.     }

  389.     /**
  390.      * Gets the type of the property with the given name.
  391.      *
  392.      * @param name the name of the property
  393.      * @return the type of the property, or {@code null} if no such property exists
  394.      */
  395.     public Class<?> getType(final String name) {
  396.         return types.get(name);
  397.     }

  398.     /**
  399.      * Gets a transformer for the given primitive type.
  400.      *
  401.      * @param <R>  The transformer result type.
  402.      * @param type the primitive type whose transformer to return
  403.      * @return a transformer that will convert strings into that type, or null if the given type is not a primitive type
  404.      */
  405.     protected <R> Function<Object, R> getTypeTransformer(final Class<R> type) {
  406.         return (Function<Object, R>) typeTransformers.get(type);
  407.     }

  408.     /**
  409.      * Gets the mutator for the property with the given name.
  410.      *
  411.      * @param name the name of the
  412.      * @return null if the name is null; null if the name is not a {@link String}; null if no such property exists; null if the property is read-only; or the
  413.      *         mutator method for that property
  414.      */
  415.     protected Method getWriteMethod(final Object name) {
  416.         return writeMethods.get(name);
  417.     }

  418.     /**
  419.      * Gets the mutator for the property with the given name.
  420.      *
  421.      * @param name the name of the property
  422.      * @return the mutator method for the property, or null
  423.      */
  424.     public Method getWriteMethod(final String name) {
  425.         return writeMethods.get(name);
  426.     }

  427.     private void initialize() {
  428.         if (getBean() == null) {
  429.             return;
  430.         }

  431.         final Class<? extends Object> beanClass = getBean().getClass();
  432.         try {
  433.             // BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
  434.             final BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
  435.             final PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
  436.             if (propertyDescriptors != null) {
  437.                 for (final PropertyDescriptor propertyDescriptor : propertyDescriptors) {
  438.                     if (propertyDescriptor != null) {
  439.                         final String name = propertyDescriptor.getName();
  440.                         final Method readMethod = propertyDescriptor.getReadMethod();
  441.                         final Method writeMethod = propertyDescriptor.getWriteMethod();
  442.                         final Class<? extends Object> aType = propertyDescriptor.getPropertyType();

  443.                         if (readMethod != null) {
  444.                             readMethods.put(name, readMethod);
  445.                         }
  446.                         if (writeMethod != null) {
  447.                             writeMethods.put(name, writeMethod);
  448.                         }
  449.                         types.put(name, aType);
  450.                     }
  451.                 }
  452.             }
  453.         } catch (final IntrospectionException e) {
  454.             logWarn(e);
  455.         }
  456.     }

  457.     /**
  458.      * Convenience method for getting an iterator over the keys.
  459.      * <p>
  460.      * Write-only properties will not be returned in the iterator.
  461.      * </p>
  462.      *
  463.      * @return an iterator over the keys
  464.      */
  465.     public Iterator<String> keyIterator() {
  466.         return readMethods.keySet().iterator();
  467.     }

  468.     // Implementation methods

  469.     /**
  470.      * Gets the keys for this BeanMap.
  471.      * <p>
  472.      * Write-only properties are <strong>not</strong> included in the returned set of property names, although it is possible to set their value and to get
  473.      * their type.
  474.      * </p>
  475.      *
  476.      * @return BeanMap keys. The Set returned by this method is not modifiable.
  477.      */
  478.     @SuppressWarnings({ "unchecked", "rawtypes" })
  479.     // The set actually contains strings; however, because it cannot be
  480.     // modified there is no danger in selling it as Set<Object>
  481.     @Override
  482.     public Set<String> keySet() {
  483.         return Collections.unmodifiableSet((Set) readMethods.keySet());
  484.     }

  485.     /**
  486.      * Logs the given exception to {@code System.out}. Used to display warnings while accessing/mutating the bean.
  487.      *
  488.      * @param ex the exception to log
  489.      */
  490.     protected void logInfo(final Exception ex) {
  491.         // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
  492.         System.out.println("INFO: Exception: " + ex);
  493.     }

  494.     /**
  495.      * Logs the given exception to {@code System.err}. Used to display errors while accessing/mutating the bean.
  496.      *
  497.      * @param ex the exception to log
  498.      */
  499.     protected void logWarn(final Exception ex) {
  500.         // Deliberately do not use LOG4J or Commons Logging to avoid dependencies
  501.         System.out.println("WARN: Exception: " + ex);
  502.         ex.printStackTrace();
  503.     }

  504.     /**
  505.      * Sets the bean property with the given name to the given value.
  506.      *
  507.      * @param name  the name of the property to set
  508.      * @param value the value to set that property to
  509.      * @return the previous value of that property
  510.      * @throws IllegalArgumentException if the given name is null; if the given name is not a {@link String}; if the bean doesn't define a property with that
  511.      *                                  name; or if the bean property with that name is read-only
  512.      * @throws ClassCastException       if an error occurs creating the method args
  513.      */
  514.     @Override
  515.     public Object put(final String name, final Object value) throws IllegalArgumentException, ClassCastException {
  516.         if (bean != null) {
  517.             final Object oldValue = get(name);
  518.             final Method method = getWriteMethod(name);
  519.             if (method == null) {
  520.                 throw new IllegalArgumentException("The bean of type: " + bean.getClass().getName() + " has no property called: " + name);
  521.             }
  522.             try {
  523.                 final Object[] arguments = createWriteMethodArguments(method, value);
  524.                 method.invoke(bean, arguments);

  525.                 final Object newValue = get(name);
  526.                 firePropertyChange(name, oldValue, newValue);
  527.             } catch (final InvocationTargetException | IllegalAccessException e) {
  528.                 throw new IllegalArgumentException(e.getMessage(), e);
  529.             }
  530.             return oldValue;
  531.         }
  532.         return null;
  533.     }

  534.     /**
  535.      * Puts all of the writable properties from the given BeanMap into this BeanMap. Read-only and Write-only properties will be ignored.
  536.      *
  537.      * @param map the BeanMap whose properties to put
  538.      */
  539.     public void putAllWriteable(final BeanMap map) {
  540.         map.readMethods.keySet().forEach(key -> {
  541.             if (getWriteMethod(key) != null) {
  542.                 put(key, map.get(key));
  543.             }
  544.         });
  545.     }

  546.     // Implementation classes

  547.     /**
  548.      * Reinitializes this bean. Called during {@link #setBean(Object)}. Does introspection to find properties.
  549.      */
  550.     protected void reinitialise() {
  551.         readMethods.clear();
  552.         writeMethods.clear();
  553.         types.clear();
  554.         initialize();
  555.     }

  556.     /**
  557.      * Sets the bean to be operated on by this map. The given value may be null, in which case this map will be empty.
  558.      *
  559.      * @param newBean the new bean to operate on
  560.      */
  561.     public void setBean(final Object newBean) {
  562.         bean = newBean;
  563.         reinitialise();
  564.     }

  565.     /**
  566.      * Returns the number of properties defined by the bean.
  567.      *
  568.      * @return the number of properties defined by the bean
  569.      */
  570.     @Override
  571.     public int size() {
  572.         return readMethods.size();
  573.     }

  574.     /**
  575.      * Renders a string representation of this object.
  576.      *
  577.      * @return a {@code String} representation of this object
  578.      */
  579.     @Override
  580.     public String toString() {
  581.         return "BeanMap<" + bean + ">";
  582.     }

  583.     /**
  584.      * Convenience method for getting an iterator over the values.
  585.      *
  586.      * @return an iterator over the values
  587.      */
  588.     public Iterator<Object> valueIterator() {
  589.         final Iterator<?> iter = keyIterator();
  590.         return new Iterator<Object>() {
  591.             @Override
  592.             public boolean hasNext() {
  593.                 return iter.hasNext();
  594.             }

  595.             @Override
  596.             public Object next() {
  597.                 final Object key = iter.next();
  598.                 return get(key);
  599.             }

  600.             @Override
  601.             public void remove() {
  602.                 throw new UnsupportedOperationException("remove() not supported for BeanMap");
  603.             }
  604.         };
  605.     }

  606.     /**
  607.      * Gets the values for the BeanMap.
  608.      *
  609.      * @return values for the BeanMap. The returned collection is not modifiable.
  610.      */
  611.     @Override
  612.     public Collection<Object> values() {
  613.         final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
  614.         valueIterator().forEachRemaining(answer::add);
  615.         return Collections.unmodifiableList(answer);
  616.     }
  617. }