This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-beanutils.git
The following commit(s) were added to refs/heads/master by this push:
new 9c41fcd [BEANUTILS-527] Convert from Collections4 to
java.util.function #8.
9c41fcd is described below
commit 9c41fcdb88209fd754c45e9aef4eff5e671c5ab0
Author: Gary Gregory <[email protected]>
AuthorDate: Sun Oct 20 18:36:27 2019 -0400
[BEANUTILS-527] Convert from Collections4 to java.util.function #8.
Use java.util.functions.Function instead of transformer.
---
.../org/apache/commons/beanutils2/BeanMap.java | 1574 +++++++++-----------
.../apache/commons/beanutils2/BeanMapTestCase.java | 974 ++++++------
2 files changed, 1232 insertions(+), 1316 deletions(-)
diff --git a/src/main/java/org/apache/commons/beanutils2/BeanMap.java
b/src/main/java/org/apache/commons/beanutils2/BeanMap.java
index c692739..1e06488 100644
--- a/src/main/java/org/apache/commons/beanutils2/BeanMap.java
+++ b/src/main/java/org/apache/commons/beanutils2/BeanMap.java
@@ -1,829 +1,745 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.beanutils2;
-
-import java.beans.BeanInfo;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.collections4.Transformer;
-import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
-
-/**
- * An implementation of Map for JavaBeans which uses introspection to
- * get and put properties in the bean.
- * <p>
- * If an exception occurs during attempts to get or set a property then the
- * property is considered non existent in the Map
- *
- */
-public class BeanMap extends AbstractMap<Object, Object> implements Cloneable {
-
- private transient Object bean;
-
- private transient HashMap<String, Method> readMethods = new HashMap<>();
- private transient HashMap<String, Method> writeMethods = new HashMap<>();
- private transient HashMap<String, Class<? extends Object>> types = new
HashMap<>();
-
- /**
- * An empty array. Used to invoke accessors via reflection.
- */
- public static final Object[] NULL_ARGUMENTS = {};
-
- /**
- * Maps primitive Class types to transformers. The transformer
- * transform strings into the appropriate primitive wrapper.
- *
- * N.B. private & unmodifiable replacement for the (public & static)
defaultTransformers instance.
- */
- private static final Map<Class<? extends Object>, Transformer>
typeTransformers =
- Collections.unmodifiableMap(createTypeTransformers());
-
- private static Map<Class<? extends Object>, Transformer>
createTypeTransformers() {
- final Map<Class<? extends Object>, Transformer> defTransformers =
- new HashMap<>();
- defTransformers.put(
- Boolean.TYPE,
- input -> Boolean.valueOf( input.toString() )
- );
- defTransformers.put(
- Character.TYPE,
- input -> Character.valueOf( input.toString().charAt( 0 ) )
- );
- defTransformers.put(
- Byte.TYPE,
- input -> Byte.valueOf( input.toString() )
- );
- defTransformers.put(
- Short.TYPE,
- input -> Short.valueOf( input.toString() )
- );
- defTransformers.put(
- Integer.TYPE,
- input -> Integer.valueOf( input.toString() )
- );
- defTransformers.put(
- Long.TYPE,
- input -> Long.valueOf( input.toString() )
- );
- defTransformers.put(
- Float.TYPE,
- input -> Float.valueOf( input.toString() )
- );
- defTransformers.put(
- Double.TYPE,
- input -> Double.valueOf( input.toString() )
- );
- return defTransformers;
- }
-
-
- // Constructors
- //-------------------------------------------------------------------------
-
- /**
- * Constructs a new empty <code>BeanMap</code>.
- */
- public BeanMap() {
- }
-
- /**
- * Constructs a new <code>BeanMap</code> that operates on the
- * specified bean. If the given bean is <code>null</code>, then
- * this map will be empty.
- *
- * @param bean the bean for this map to operate on
- */
- public BeanMap(final Object bean) {
- this.bean = bean;
- initialise();
- }
-
- // Map interface
- //-------------------------------------------------------------------------
-
- /**
- * Renders a string representation of this object.
- * @return a <code>String</code> representation of this object
- */
- @Override
- public String toString() {
- return "BeanMap<" + String.valueOf(bean) + ">";
- }
-
- /**
- * Clone this bean map using the following process:
- *
- * <ul>
- * <li>If there is no underlying bean, return a cloned BeanMap without a
- * bean.
- *
- * <li>Since there is an underlying bean, try to instantiate a new bean of
- * the same type using Class.newInstance().
- *
- * <li>If the instantiation fails, throw a CloneNotSupportedException
- *
- * <li>Clone the bean map and set the newly instantiated bean as the
- * underlying bean for the bean map.
- *
- * <li>Copy each property that is both readable and writable from the
- * existing object to a cloned bean map.
- *
- * <li>If anything fails along the way, throw a
- * CloneNotSupportedException.
- *
- * </ul>
- *
- * @return a cloned instance of this bean map
- * @throws CloneNotSupportedException if the underlying bean
- * cannot be cloned
- */
- @Override
- public Object clone() throws CloneNotSupportedException {
- final BeanMap newMap = (BeanMap)super.clone();
-
- if(bean == null) {
- // no bean, just an empty bean map at the moment. return a newly
- // cloned and empty bean map.
- return newMap;
- }
-
- Object newBean = null;
- final Class<? extends Object> beanClass = bean.getClass(); // Cannot
throw Exception
- try {
- newBean = beanClass.newInstance();
- } catch (final Exception e) {
- // unable to instantiate
- final CloneNotSupportedException cnse = new
CloneNotSupportedException
- ("Unable to instantiate the underlying bean \"" +
- beanClass.getName() + "\": " + e);
- BeanUtils.initCause(cnse, e);
- throw cnse;
- }
-
- try {
- newMap.setBean(newBean);
- } catch (final Exception exception) {
- final CloneNotSupportedException cnse = new
CloneNotSupportedException
- ("Unable to set bean in the cloned bean map: " +
- exception);
- BeanUtils.initCause(cnse, exception);
- throw cnse;
- }
-
- try {
- // copy only properties that are readable and writable. If its
- // not readable, we can't get the value from the old map. If
- // its not writable, we can't write a value into the new map.
- for (final Object key : readMethods.keySet()) {
- if(getWriteMethod(key) != null) {
- newMap.put(key, get(key));
- }
- }
- } catch (final Exception exception) {
- final CloneNotSupportedException cnse = new
CloneNotSupportedException
- ("Unable to copy bean values to cloned bean map: " +
- exception);
- BeanUtils.initCause(cnse, exception);
- throw cnse;
- }
-
- return newMap;
- }
-
- /**
- * Puts all of the writable properties from the given BeanMap into this
- * BeanMap. Read-only and Write-only properties will be ignored.
- *
- * @param map the BeanMap whose properties to put
- */
- public void putAllWriteable(final BeanMap map) {
- for (final Object key : map.readMethods.keySet()) {
- if (getWriteMethod(key) != null) {
- this.put(key, map.get(key));
- }
- }
- }
-
-
- /**
- * 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 which the map uses as its underlying data source. This
- * behavior for <code>clear()</code> differs from the Map contract in that
- * the mappings are not actually removed from the map (the mappings for a
- * BeanMap are fixed).
- */
- @Override
- public void clear() {
- if(bean == null) {
- return;
- }
-
- Class<? extends Object> beanClass = null;
- try {
- beanClass = bean.getClass();
- bean = beanClass.newInstance();
- }
- catch (final Exception e) {
- final UnsupportedOperationException uoe =
- new UnsupportedOperationException("Could not create new
instance of class: " + beanClass);
- BeanUtils.initCause(uoe, e);
- throw uoe;
- }
- }
-
- /**
- * Returns true if the bean defines a property with the given name.
- * <p>
- * The given name must be a <code>String</code>; if not, this method
- * returns false. This method will also return false if the bean
- * does not define a property with that name.
- * <p>
- * Write-only properties will not be matched as the test operates against
- * property read methods.
- *
- * @param name the name of the property to check
- * @return false if the given name is null or is not a <code>String</code>;
- * false if the bean does not define a property with that name; or
- * true if the bean does define a property with that name
- */
- @Override
- public boolean containsKey(final Object name) {
- final Method method = getReadMethod(name);
- return method != null;
- }
-
- /**
- * Returns true if the bean defines a property whose current value is
- * the given object.
- *
- * @param value the value to check
- * @return false true if the bean has at least one property whose
- * current value is that object, false otherwise
- */
- @Override
- public boolean containsValue(final Object value) {
- // use default implementation
- return super.containsValue(value);
- }
-
- /**
- * Returns the value of the bean's property with the given name.
- * <p>
- * The given name must be a {@link String} and must not be
- * null; otherwise, this method returns <code>null</code>.
- * If the bean defines a property with the given name, the value of
- * that property is returned. Otherwise, <code>null</code> is
- * returned.
- * <p>
- * Write-only properties will not be matched as the test operates against
- * property read methods.
- *
- * @param name the name of the property whose value to return
- * @return the value of the property with that name
- */
- @Override
- public Object get(final Object name) {
- if ( bean != null ) {
- final Method method = getReadMethod( name );
- if ( method != null ) {
- try {
- return method.invoke( bean, NULL_ARGUMENTS );
- }
- catch ( final IllegalAccessException e ) {
- logWarn( e );
- }
- catch ( final IllegalArgumentException e ) {
- logWarn( e );
- }
- catch ( final InvocationTargetException e ) {
- logWarn( e );
- }
- catch ( final NullPointerException e ) {
- logWarn( e );
- }
- }
- }
- return null;
- }
-
- /**
- * Sets the bean property with the given name to the given value.
- *
- * @param name the name of the property to set
- * @param value the value to set that property to
- * @return the previous value of that property
- * @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 name; or if the bean property with
- * that name is read-only
- * @throws ClassCastException if an error occurs creating the method args
- */
- @Override
- public Object put(final Object name, final Object value) throws
IllegalArgumentException, ClassCastException {
- if ( bean != null ) {
- final Object oldValue = get( name );
- final Method method = getWriteMethod( name );
- if ( method == null ) {
- throw new IllegalArgumentException( "The bean of type: "+
- bean.getClass().getName() + " has no property called:
" + name );
- }
- try {
- final Object[] arguments = createWriteMethodArguments( method,
value );
- method.invoke( bean, arguments );
-
- final Object newValue = get( name );
- firePropertyChange( name, oldValue, newValue );
- }
- catch ( final InvocationTargetException e ) {
- final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
- if (BeanUtils.initCause(iae, e) == false) {
- logInfo(e);
- }
- throw iae;
- }
- catch ( final IllegalAccessException e ) {
- final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
- if (BeanUtils.initCause(iae, e) == false) {
- logInfo(e);
- }
- throw iae;
- }
- return oldValue;
- }
- return null;
- }
-
- /**
- * Returns the number of properties defined by the bean.
- *
- * @return the number of properties defined by the bean
- */
- @Override
- public int size() {
- return readMethods.size();
- }
-
-
- /**
- * Get the keys for this BeanMap.
- * <p>
- * Write-only properties are <b>not</b> included in the returned set of
- * property names, although it is possible to set their value and to get
- * their type.
- *
- * @return BeanMap keys. The Set returned by this method is not
- * modifiable.
- */
- @SuppressWarnings({ "unchecked", "rawtypes" })
- // The set actually contains strings; however, because it cannot be
- // modified there is no danger in selling it as Set<Object>
- @Override
- public Set<Object> keySet() {
- return Collections.unmodifiableSet((Set) readMethods.keySet());
- }
-
- /**
- * Gets a Set of MapEntry objects that are the mappings for this BeanMap.
- * <p>
- * Each MapEntry can be set but not removed.
- *
- * @return the unmodifiable set of mappings
- */
- @Override
- public Set<Map.Entry<Object, Object>> entrySet() {
- return Collections.unmodifiableSet(new AbstractSet<Map.Entry<Object,
Object>>() {
- @Override
- public Iterator<Map.Entry<Object, Object>> iterator() {
- return entryIterator();
- }
- @Override
- public int size() {
- return BeanMap.this.readMethods.size();
- }
- });
- }
-
- /**
- * Returns the values for the BeanMap.
- *
- * @return values for the BeanMap. The returned collection is not
- * modifiable.
- */
- @Override
- public Collection<Object> values() {
- final ArrayList<Object> answer = new ArrayList<>( readMethods.size() );
- for ( final Iterator<Object> iter = valueIterator(); iter.hasNext(); )
{
- answer.add( iter.next() );
- }
- return Collections.unmodifiableList(answer);
- }
-
-
- // Helper methods
- //-------------------------------------------------------------------------
-
- /**
- * Returns the type of the property with the given name.
- *
- * @param name the name of the property
- * @return the type of the property, or <code>null</code> if no such
- * property exists
- */
- public Class<?> getType(final String name) {
- return types.get( name );
- }
-
- /**
- * Convenience method for getting an iterator over the keys.
- * <p>
- * Write-only properties will not be returned in the iterator.
- *
- * @return an iterator over the keys
- */
- public Iterator<String> keyIterator() {
- return readMethods.keySet().iterator();
- }
-
- /**
- * Convenience method for getting an iterator over the values.
- *
- * @return an iterator over the values
- */
- public Iterator<Object> valueIterator() {
- final Iterator<?> iter = keyIterator();
- return new Iterator<Object>() {
- @Override
- public boolean hasNext() {
- return iter.hasNext();
- }
- @Override
- public Object next() {
- final Object key = iter.next();
- return get(key);
- }
- @Override
- public void remove() {
- throw new UnsupportedOperationException( "remove() not
supported for BeanMap" );
- }
- };
- }
-
- /**
- * Convenience method for getting an iterator over the entries.
- *
- * @return an iterator over the entries
- */
- public Iterator<Map.Entry<Object, Object>> entryIterator() {
- final Iterator<String> iter = keyIterator();
- return new Iterator<Map.Entry<Object, Object>>() {
- @Override
- public boolean hasNext() {
- return iter.hasNext();
- }
- @Override
- public Map.Entry<Object, Object> next() {
- final Object key = iter.next();
- final Object value = get(key);
- @SuppressWarnings("unchecked")
- final
- // This should not cause any problems; the key is actually a
- // string, but it does no harm to expose it as Object
- Map.Entry<Object, Object> tmpEntry = new Entry( BeanMap.this,
key, value );
- return tmpEntry;
- }
- @Override
- public void remove() {
- throw new UnsupportedOperationException( "remove() not
supported for BeanMap" );
- }
- };
- }
-
-
- // Properties
- //-------------------------------------------------------------------------
-
- /**
- * Returns the bean currently being operated on. The return value may
- * be null if this map is empty.
- *
- * @return the bean being operated on by this map
- */
- public Object getBean() {
- return bean;
- }
-
- /**
- * Sets the bean to be operated on by this map. The given value may
- * be null, in which case this map will be empty.
- *
- * @param newBean the new bean to operate on
- */
- public void setBean( final Object newBean ) {
- bean = newBean;
- reinitialise();
- }
-
- /**
- * Returns the accessor for the property with the given name.
- *
- * @param name the name of the property
- * @return the accessor method for the property, or null
- */
- public Method getReadMethod(final String name) {
- return readMethods.get(name);
- }
-
- /**
- * Returns the mutator for the property with the given name.
- *
- * @param name the name of the property
- * @return the mutator method for the property, or null
- */
- public Method getWriteMethod(final String name) {
- return writeMethods.get(name);
- }
-
-
- // Implementation methods
- //-------------------------------------------------------------------------
-
- /**
- * Returns the accessor for the property with the given name.
- *
- * @param name the name of the property
- * @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
- */
- protected Method getReadMethod( final Object name ) {
- return readMethods.get( name );
- }
-
- /**
- * Returns the mutator for the property with the given name.
- *
- * @param name the name of the
- * @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 mutator method for that property
- */
- protected Method getWriteMethod( final Object name ) {
- return writeMethods.get( name );
- }
-
- /**
- * Reinitializes this bean. Called during {@link #setBean(Object)}.
- * Does introspection to find properties.
- */
- protected void reinitialise() {
- readMethods.clear();
- writeMethods.clear();
- types.clear();
- initialise();
- }
-
- private void initialise() {
- if(getBean() == null) {
- return;
- }
-
- final Class<? extends Object> beanClass = getBean().getClass();
- try {
- //BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
- final BeanInfo beanInfo = Introspector.getBeanInfo( beanClass );
- final PropertyDescriptor[] propertyDescriptors =
beanInfo.getPropertyDescriptors();
- if ( propertyDescriptors != null ) {
- for (final PropertyDescriptor propertyDescriptor :
propertyDescriptors) {
- if ( propertyDescriptor != null ) {
- final String name = propertyDescriptor.getName();
- final Method readMethod =
propertyDescriptor.getReadMethod();
- final Method writeMethod =
propertyDescriptor.getWriteMethod();
- final Class<? extends Object> aType =
propertyDescriptor.getPropertyType();
-
- if ( readMethod != null ) {
- readMethods.put( name, readMethod );
- }
- if ( writeMethod != null ) {
- writeMethods.put( name, writeMethod );
- }
- types.put( name, aType );
- }
- }
- }
- }
- catch ( final IntrospectionException e ) {
- logWarn( e );
- }
- }
-
- /**
- * Called during a successful {@link #put(Object,Object)} operation.
- * Default implementation does nothing. Override to be notified of
- * property changes in the bean caused by this map.
- *
- * @param key the name of the property that changed
- * @param oldValue the old value for that property
- * @param newValue the new value for that property
- */
- protected void firePropertyChange( final Object key, final Object
oldValue, final Object newValue ) {
- }
-
- // Implementation classes
- //-------------------------------------------------------------------------
-
- /**
- * Map entry used by {@link BeanMap}.
- */
- protected static class Entry extends AbstractMapEntry<Object, Object> {
- private final BeanMap owner;
-
- /**
- * Constructs a new <code>Entry</code>.
- *
- * @param owner the BeanMap this entry belongs to
- * @param key the key for this entry
- * @param value the value for this entry
- */
- protected Entry( final BeanMap owner, final Object key, final Object
value ) {
- super( key, value );
- this.owner = owner;
- }
-
- /**
- * Sets the value.
- *
- * @param value the new value for the entry
- * @return the old value for the entry
- */
- @Override
- public Object setValue(final Object value) {
- final Object key = getKey();
- final Object oldValue = owner.get( key );
-
- owner.put( key, value );
- final Object newValue = owner.get( key );
- super.setValue( newValue );
- return oldValue;
- }
- }
-
- /**
- * 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 converted using {@link #convertType(Class,Object)}.
- *
- * @param method the mutator method
- * @param value the value to pass to the mutator method
- * @return an array containing one object that is either the given value
- * or a transformed value
- * @throws IllegalAccessException if {@link #convertType(Class,Object)}
- * raises it
- * @throws IllegalArgumentException if any other exception is raised
- * by {@link #convertType(Class,Object)}
- * @throws ClassCastException if an error occurs creating the method args
- */
- protected Object[] createWriteMethodArguments( final Method method, Object
value )
- throws IllegalAccessException, ClassCastException {
- try {
- if ( value != null ) {
- final Class<? extends Object>[] parmTypes =
method.getParameterTypes();
- if ( parmTypes != null && parmTypes.length > 0 ) {
- final Class<? extends Object> paramType = parmTypes[0];
- if ( ! paramType.isAssignableFrom( value.getClass() ) ) {
- value = convertType( paramType, value );
- }
- }
- }
- final Object[] answer = { value };
- return answer;
- }
- catch ( final InvocationTargetException e ) {
- final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
- if (BeanUtils.initCause(iae, e) == false) {
- logInfo(e);
- }
- throw iae;
- }
- catch ( final InstantiationException e ) {
- final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
- if (BeanUtils.initCause(iae, e) == false) {
- logInfo(e);
- }
- BeanUtils.initCause(iae, e);
- throw iae;
- }
- }
-
- /**
- * Converts the given value to the given type. First, reflection is
- * is used to find a public constructor declared by the given class
- * that takes one argument, 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 constructor, and the
- * newly constructed object is returned.<P>
- *
- * 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() toString()} method, and that string is
- * parsed into the correct primitive type using, for instance,
- * {@link Integer#valueOf(String)} to convert the string into an
- * <code>int</code>.<P>
- *
- * If no special constructor exists and the given type is not a
- * primitive type, this method returns the original value.
- *
- * @param newType the type to convert the value to
- * @param value the value to convert
- * @return the converted value
- * @throws NumberFormatException if newType is a primitive type, and
- * the string representation of the given value cannot be converted
- * to that type
- * @throws InstantiationException if the constructor found with
- * reflection raises it
- * @throws InvocationTargetException if the constructor found with
- * reflection raises it
- * @throws IllegalAccessException never
- * @throws IllegalArgumentException never
- */
- protected Object convertType( final Class<?> newType, final Object value )
- throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
-
- // try call constructor
- try {
- final Constructor<?> constructor = newType.getConstructor(
value.getClass() );
- final Object[] arguments = { value };
- return constructor.newInstance( arguments );
- }
- catch ( final NoSuchMethodException e ) {
- // try using the transformers
- final Transformer transformer = getTypeTransformer( newType );
- if ( transformer != null ) {
- return transformer.transform( value );
- }
- return value;
- }
- }
-
- /**
- * Returns a transformer for the given primitive type.
- *
- * @param aType the primitive type whose transformer to return
- * @return a transformer that will convert strings into that type,
- * or null if the given type is not a primitive type
- */
- protected Transformer getTypeTransformer( final Class<?> aType ) {
- return typeTransformers.get( aType );
- }
-
- /**
- * Logs the given exception to <code>System.out</code>. Used to display
- * warnings while accessing/mutating the bean.
- *
- * @param ex the exception to log
- */
- protected void logInfo(final Exception ex) {
- // Deliberately do not use LOG4J or Commons Logging to avoid
dependencies
- System.out.println( "INFO: Exception: " + ex );
- }
-
- /**
- * Logs the given exception to <code>System.err</code>. Used to display
- * errors while accessing/mutating the bean.
- *
- * @param ex the exception to log
- */
- protected void logWarn(final Exception ex) {
- // Deliberately do not use LOG4J or Commons Logging to avoid
dependencies
- System.out.println( "WARN: Exception: " + ex );
- ex.printStackTrace();
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+
+import org.apache.commons.collections4.keyvalue.AbstractMapEntry;
+
+/**
+ * An implementation of Map for JavaBeans which uses introspection to get and
put properties in the bean.
+ * <p>
+ * If an exception occurs during attempts to get or set a property then the
property is considered non existent in the
+ * Map
+ * </p>
+ */
+public class BeanMap extends AbstractMap<Object, Object> implements Cloneable {
+
+ private transient Object bean;
+
+ private transient HashMap<String, Method> readMethods = new HashMap<>();
+ private transient HashMap<String, Method> writeMethods = new HashMap<>();
+ private transient HashMap<String, Class<? extends Object>> types = new
HashMap<>();
+
+ /**
+ * An empty array. Used to invoke accessors via reflection.
+ */
+ public static final Object[] NULL_ARGUMENTS = {};
+
+ /**
+ * Maps primitive Class types to transformers. The transformer transform
strings into the appropriate primitive
+ * wrapper.
+ *
+ * N.B. private & unmodifiable replacement for the (public & static)
defaultTransformers instance.
+ */
+ private static final Map<Class<? extends Object>, Function<?, ?>>
typeTransformers = Collections
+ .unmodifiableMap(createTypeTransformers());
+
+ private static Map<Class<? extends Object>, Function<?, ?>>
createTypeTransformers() {
+ final Map<Class<? extends Object>, Function<?, ?>> defTransformers =
new HashMap<>();
+ defTransformers.put(Boolean.TYPE, input ->
Boolean.valueOf(input.toString()));
+ defTransformers.put(Character.TYPE, input ->
Character.valueOf(input.toString().charAt(0)));
+ defTransformers.put(Byte.TYPE, input ->
Byte.valueOf(input.toString()));
+ defTransformers.put(Short.TYPE, input ->
Short.valueOf(input.toString()));
+ defTransformers.put(Integer.TYPE, input ->
Integer.valueOf(input.toString()));
+ defTransformers.put(Long.TYPE, input ->
Long.valueOf(input.toString()));
+ defTransformers.put(Float.TYPE, input ->
Float.valueOf(input.toString()));
+ defTransformers.put(Double.TYPE, input ->
Double.valueOf(input.toString()));
+ return defTransformers;
+ }
+
+ // Constructors
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Constructs a new empty <code>BeanMap</code>.
+ */
+ public BeanMap() {
+ }
+
+ /**
+ * Constructs a new <code>BeanMap</code> that operates on the specified
bean. If the given bean is
+ * <code>null</code>, then this map will be empty.
+ *
+ * @param bean the bean for this map to operate on
+ */
+ public BeanMap(final Object bean) {
+ this.bean = bean;
+ initialise();
+ }
+
+ // Map interface
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Renders a string representation of this object.
+ *
+ * @return a <code>String</code> representation of this object
+ */
+ @Override
+ public String toString() {
+ return "BeanMap<" + String.valueOf(bean) + ">";
+ }
+
+ /**
+ * Clone this bean map using the following process:
+ *
+ * <ul>
+ * <li>If there is no underlying bean, return a cloned BeanMap without a
bean.
+ *
+ * <li>Since there is an underlying bean, try to instantiate a new bean of
the same type using Class.newInstance().
+ *
+ * <li>If the instantiation fails, throw a CloneNotSupportedException
+ *
+ * <li>Clone the bean map and set the newly instantiated bean as the
underlying bean for the bean map.
+ *
+ * <li>Copy each property that is both readable and writable from the
existing object to a cloned bean map.
+ *
+ * <li>If anything fails along the way, throw a CloneNotSupportedException.
+ *
+ * </ul>
+ *
+ * @return a cloned instance of this bean map
+ * @throws CloneNotSupportedException if the underlying bean cannot be
cloned
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ final BeanMap newMap = (BeanMap) super.clone();
+
+ if (bean == null) {
+ // no bean, just an empty bean map at the moment. return a newly
+ // cloned and empty bean map.
+ return newMap;
+ }
+
+ Object newBean = null;
+ final Class<? extends Object> beanClass = bean.getClass(); // Cannot
throw Exception
+ try {
+ newBean = beanClass.newInstance();
+ } catch (final Exception e) {
+ // unable to instantiate
+ final CloneNotSupportedException cnse = new
CloneNotSupportedException(
+ "Unable to instantiate the underlying bean \"" +
beanClass.getName() + "\": " + e);
+ BeanUtils.initCause(cnse, e);
+ throw cnse;
+ }
+
+ try {
+ newMap.setBean(newBean);
+ } catch (final Exception exception) {
+ final CloneNotSupportedException cnse = new
CloneNotSupportedException(
+ "Unable to set bean in the cloned bean map: " + exception);
+ BeanUtils.initCause(cnse, exception);
+ throw cnse;
+ }
+
+ try {
+ // copy only properties that are readable and writable. If its
+ // not readable, we can't get the value from the old map. If
+ // its not writable, we can't write a value into the new map.
+ for (final Object key : readMethods.keySet()) {
+ if (getWriteMethod(key) != null) {
+ newMap.put(key, get(key));
+ }
+ }
+ } catch (final Exception exception) {
+ final CloneNotSupportedException cnse = new
CloneNotSupportedException(
+ "Unable to copy bean values to cloned bean map: " +
exception);
+ BeanUtils.initCause(cnse, exception);
+ throw cnse;
+ }
+
+ return newMap;
+ }
+
+ /**
+ * Puts all of the writable properties from the given BeanMap into this
BeanMap. Read-only and Write-only properties
+ * will be ignored.
+ *
+ * @param map the BeanMap whose properties to put
+ */
+ public void putAllWriteable(final BeanMap map) {
+ for (final Object key : map.readMethods.keySet()) {
+ if (getWriteMethod(key) != null) {
+ this.put(key, map.get(key));
+ }
+ }
+ }
+
+ /**
+ * 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 which the map uses as its
underlying data source. This behavior for
+ * <code>clear()</code> differs from the Map contract in that the mappings
are not actually removed from the map
+ * (the mappings for a BeanMap are fixed).
+ */
+ @Override
+ public void clear() {
+ if (bean == null) {
+ return;
+ }
+
+ Class<? extends Object> beanClass = null;
+ try {
+ beanClass = bean.getClass();
+ bean = beanClass.newInstance();
+ } catch (final Exception e) {
+ final UnsupportedOperationException uoe = new
UnsupportedOperationException(
+ "Could not create new instance of class: " + beanClass);
+ BeanUtils.initCause(uoe, e);
+ throw uoe;
+ }
+ }
+
+ /**
+ * Returns true if the bean defines a property with the given name.
+ * <p>
+ * The given name must be a <code>String</code>; if not, this method
returns false. This method will also return
+ * false if the bean does not define a property with that name.
+ * <p>
+ * Write-only properties will not be matched as the test operates against
property read methods.
+ *
+ * @param name the name of the property to check
+ * @return false if the given name is null or is not a
<code>String</code>; false if the bean does not define a
+ * property with that name; or true if the bean does define a
property with that name
+ */
+ @Override
+ public boolean containsKey(final Object name) {
+ final Method method = getReadMethod(name);
+ return method != null;
+ }
+
+ /**
+ * Returns true if the bean defines a property whose current value is the
given object.
+ *
+ * @param value the value to check
+ * @return false true if the bean has at least one property whose current
value is that object, false otherwise
+ */
+ @Override
+ public boolean containsValue(final Object value) {
+ // use default implementation
+ return super.containsValue(value);
+ }
+
+ /**
+ * Returns the value of the bean's property with the given name.
+ * <p>
+ * The given name must be a {@link String} and must not be null;
otherwise, this method returns <code>null</code>.
+ * If the bean defines a property with the given name, the value of that
property is returned. Otherwise,
+ * <code>null</code> is returned.
+ * <p>
+ * Write-only properties will not be matched as the test operates against
property read methods.
+ *
+ * @param name the name of the property whose value to return
+ * @return the value of the property with that name
+ */
+ @Override
+ public Object get(final Object name) {
+ if (bean != null) {
+ final Method method = getReadMethod(name);
+ if (method != null) {
+ try {
+ return method.invoke(bean, NULL_ARGUMENTS);
+ } catch (final IllegalAccessException e) {
+ logWarn(e);
+ } catch (final IllegalArgumentException e) {
+ logWarn(e);
+ } catch (final InvocationTargetException e) {
+ logWarn(e);
+ } catch (final NullPointerException e) {
+ logWarn(e);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Sets the bean property with the given name to the given value.
+ *
+ * @param name the name of the property to set
+ * @param value the value to set that property to
+ * @return the previous value of that property
+ * @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 name; or if the bean
property with that name is read-only
+ * @throws ClassCastException if an error occurs creating the method args
+ */
+ @Override
+ public Object put(final Object name, final Object value) throws
IllegalArgumentException, ClassCastException {
+ if (bean != null) {
+ final Object oldValue = get(name);
+ final Method method = getWriteMethod(name);
+ if (method == null) {
+ throw new IllegalArgumentException(
+ "The bean of type: " + bean.getClass().getName() + "
has no property called: " + name);
+ }
+ try {
+ final Object[] arguments = createWriteMethodArguments(method,
value);
+ method.invoke(bean, arguments);
+
+ final Object newValue = get(name);
+ firePropertyChange(name, oldValue, newValue);
+ } catch (final InvocationTargetException e) {
+ final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
+ if (BeanUtils.initCause(iae, e) == false) {
+ logInfo(e);
+ }
+ throw iae;
+ } catch (final IllegalAccessException e) {
+ final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
+ if (BeanUtils.initCause(iae, e) == false) {
+ logInfo(e);
+ }
+ throw iae;
+ }
+ return oldValue;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the number of properties defined by the bean.
+ *
+ * @return the number of properties defined by the bean
+ */
+ @Override
+ public int size() {
+ return readMethods.size();
+ }
+
+ /**
+ * Get the keys for this BeanMap.
+ * <p>
+ * Write-only properties are <b>not</b> included in the returned set of
property names, although it is possible to
+ * set their value and to get their type.
+ *
+ * @return BeanMap keys. The Set returned by this method is not modifiable.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ // The set actually contains strings; however, because it cannot be
+ // modified there is no danger in selling it as Set<Object>
+ @Override
+ public Set<Object> keySet() {
+ return Collections.unmodifiableSet((Set) readMethods.keySet());
+ }
+
+ /**
+ * Gets a Set of MapEntry objects that are the mappings for this BeanMap.
+ * <p>
+ * Each MapEntry can be set but not removed.
+ *
+ * @return the unmodifiable set of mappings
+ */
+ @Override
+ public Set<Map.Entry<Object, Object>> entrySet() {
+ return Collections.unmodifiableSet(new AbstractSet<Map.Entry<Object,
Object>>() {
+ @Override
+ public Iterator<Map.Entry<Object, Object>> iterator() {
+ return entryIterator();
+ }
+
+ @Override
+ public int size() {
+ return BeanMap.this.readMethods.size();
+ }
+ });
+ }
+
+ /**
+ * Returns the values for the BeanMap.
+ *
+ * @return values for the BeanMap. The returned collection is not
modifiable.
+ */
+ @Override
+ public Collection<Object> values() {
+ final ArrayList<Object> answer = new ArrayList<>(readMethods.size());
+ for (final Iterator<Object> iter = valueIterator(); iter.hasNext();) {
+ answer.add(iter.next());
+ }
+ return Collections.unmodifiableList(answer);
+ }
+
+ // Helper methods
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Returns the type of the property with the given name.
+ *
+ * @param name the name of the property
+ * @return the type of the property, or <code>null</code> if no such
property exists
+ */
+ public Class<?> getType(final String name) {
+ return types.get(name);
+ }
+
+ /**
+ * Convenience method for getting an iterator over the keys.
+ * <p>
+ * Write-only properties will not be returned in the iterator.
+ *
+ * @return an iterator over the keys
+ */
+ public Iterator<String> keyIterator() {
+ return readMethods.keySet().iterator();
+ }
+
+ /**
+ * Convenience method for getting an iterator over the values.
+ *
+ * @return an iterator over the values
+ */
+ public Iterator<Object> valueIterator() {
+ final Iterator<?> iter = keyIterator();
+ return new Iterator<Object>() {
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ final Object key = iter.next();
+ return get(key);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove() not
supported for BeanMap");
+ }
+ };
+ }
+
+ /**
+ * Convenience method for getting an iterator over the entries.
+ *
+ * @return an iterator over the entries
+ */
+ public Iterator<Map.Entry<Object, Object>> entryIterator() {
+ final Iterator<String> iter = keyIterator();
+ return new Iterator<Map.Entry<Object, Object>>() {
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public Map.Entry<Object, Object> next() {
+ final Object key = iter.next();
+ final Object value = get(key);
+ // This should not cause any problems; the key is actually a
+ // string, but it does no harm to expose it as Object
+ return new Entry(BeanMap.this, key, value);
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException("remove() not
supported for BeanMap");
+ }
+ };
+ }
+
+ // Properties
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Returns the bean currently being operated on. The return value may be
null if this map is empty.
+ *
+ * @return the bean being operated on by this map
+ */
+ public Object getBean() {
+ return bean;
+ }
+
+ /**
+ * Sets the bean to be operated on by this map. The given value may be
null, in which case this map will be empty.
+ *
+ * @param newBean the new bean to operate on
+ */
+ public void setBean(final Object newBean) {
+ bean = newBean;
+ reinitialise();
+ }
+
+ /**
+ * Returns the accessor for the property with the given name.
+ *
+ * @param name the name of the property
+ * @return the accessor method for the property, or null
+ */
+ public Method getReadMethod(final String name) {
+ return readMethods.get(name);
+ }
+
+ /**
+ * Returns the mutator for the property with the given name.
+ *
+ * @param name the name of the property
+ * @return the mutator method for the property, or null
+ */
+ public Method getWriteMethod(final String name) {
+ return writeMethods.get(name);
+ }
+
+ // Implementation methods
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Returns the accessor for the property with the given name.
+ *
+ * @param name the name of the property
+ * @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
+ */
+ protected Method getReadMethod(final Object name) {
+ return readMethods.get(name);
+ }
+
+ /**
+ * Returns the mutator for the property with the given name.
+ *
+ * @param name the name of the
+ * @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 mutator method for that
property
+ */
+ protected Method getWriteMethod(final Object name) {
+ return writeMethods.get(name);
+ }
+
+ /**
+ * Reinitializes this bean. Called during {@link #setBean(Object)}. Does
introspection to find properties.
+ */
+ protected void reinitialise() {
+ readMethods.clear();
+ writeMethods.clear();
+ types.clear();
+ initialise();
+ }
+
+ private void initialise() {
+ if (getBean() == null) {
+ return;
+ }
+
+ final Class<? extends Object> beanClass = getBean().getClass();
+ try {
+ // BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
+ final BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
+ final PropertyDescriptor[] propertyDescriptors =
beanInfo.getPropertyDescriptors();
+ if (propertyDescriptors != null) {
+ for (final PropertyDescriptor propertyDescriptor :
propertyDescriptors) {
+ if (propertyDescriptor != null) {
+ final String name = propertyDescriptor.getName();
+ final Method readMethod =
propertyDescriptor.getReadMethod();
+ final Method writeMethod =
propertyDescriptor.getWriteMethod();
+ final Class<? extends Object> aType =
propertyDescriptor.getPropertyType();
+
+ if (readMethod != null) {
+ readMethods.put(name, readMethod);
+ }
+ if (writeMethod != null) {
+ writeMethods.put(name, writeMethod);
+ }
+ types.put(name, aType);
+ }
+ }
+ }
+ } catch (final IntrospectionException e) {
+ logWarn(e);
+ }
+ }
+
+ /**
+ * Called during a successful {@link #put(Object,Object)} operation.
Default implementation does nothing. Override
+ * to be notified of property changes in the bean caused by this map.
+ *
+ * @param key the name of the property that changed
+ * @param oldValue the old value for that property
+ * @param newValue the new value for that property
+ */
+ protected void firePropertyChange(final Object key, final Object oldValue,
final Object newValue) {
+ // noop
+ }
+
+ // Implementation classes
+ //
-------------------------------------------------------------------------
+
+ /**
+ * Map entry used by {@link BeanMap}.
+ */
+ protected static class Entry extends AbstractMapEntry<Object, Object> {
+ private final BeanMap owner;
+
+ /**
+ * Constructs a new <code>Entry</code>.
+ *
+ * @param owner the BeanMap this entry belongs to
+ * @param key the key for this entry
+ * @param value the value for this entry
+ */
+ protected Entry(final BeanMap owner, final Object key, final Object
value) {
+ super(key, value);
+ this.owner = owner;
+ }
+
+ /**
+ * Sets the value.
+ *
+ * @param value the new value for the entry
+ * @return the old value for the entry
+ */
+ @Override
+ public Object setValue(final Object value) {
+ final Object key = getKey();
+ final Object oldValue = owner.get(key);
+
+ owner.put(key, value);
+ final Object newValue = owner.get(key);
+ super.setValue(newValue);
+ return oldValue;
+ }
+ }
+
+ /**
+ * 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 converted using {@link
#convertType(Class,Object)}.
+ *
+ * @param method the mutator method
+ * @param value the value to pass to the mutator method
+ * @return an array containing one object that is either the given value
or a transformed value
+ * @throws IllegalAccessException if {@link #convertType(Class,Object)}
raises it
+ * @throws IllegalArgumentException if any other exception is raised by
{@link #convertType(Class,Object)}
+ * @throws ClassCastException if an error occurs creating the method args
+ */
+ protected Object[] createWriteMethodArguments(final Method method, Object
value)
+ throws IllegalAccessException, ClassCastException {
+ try {
+ if (value != null) {
+ final Class<? extends Object>[] parmTypes =
method.getParameterTypes();
+ if (parmTypes != null && parmTypes.length > 0) {
+ final Class<? extends Object> paramType = parmTypes[0];
+ if (!paramType.isAssignableFrom(value.getClass())) {
+ value = convertType(paramType, value);
+ }
+ }
+ }
+ final Object[] answer = { value };
+ return answer;
+ } catch (final InvocationTargetException e) {
+ final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
+ if (BeanUtils.initCause(iae, e) == false) {
+ logInfo(e);
+ }
+ throw iae;
+ } catch (final InstantiationException e) {
+ final IllegalArgumentException iae = new
IllegalArgumentException(e.getMessage());
+ if (BeanUtils.initCause(iae, e) == false) {
+ logInfo(e);
+ }
+ BeanUtils.initCause(iae, e);
+ throw iae;
+ }
+ }
+
+ /**
+ * Converts the given value to the given type. First, reflection is is
used to find a public constructor declared by
+ * the given class that takes one argument, 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
constructor, and the newly constructed
+ * object is returned.
+ * <P>
+ *
+ * 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() toString()} method, and that
string is parsed into the correct
+ * primitive type using, for instance, {@link Integer#valueOf(String)} to
convert the string into an
+ * <code>int</code>.
+ * <P>
+ *
+ * If no special constructor exists and the given type is not a primitive
type, this method returns the original
+ * value.
+ *
+ * @param newType the type to convert the value to
+ * @param value the value to convert
+ * @return the converted value
+ * @throws NumberFormatException if newType is a primitive type, and the
string representation of the given value
+ * cannot be converted to that type
+ * @throws InstantiationException if the constructor found with reflection
raises it
+ * @throws InvocationTargetException if the constructor found with
reflection raises it
+ * @throws IllegalAccessException never
+ * @throws IllegalArgumentException never
+ */
+ protected Object convertType(final Class<?> newType, final Object value)
+ throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
+
+ // try call constructor
+ try {
+ final Constructor<?> constructor =
newType.getConstructor(value.getClass());
+ return constructor.newInstance(new Object[] { value });
+ } catch (final NoSuchMethodException e) {
+ // try using the transformers
+ final Function transformer = getTypeTransformer(newType);
+ if (transformer != null) {
+ return transformer.apply(value);
+ }
+ return value;
+ }
+ }
+
+ /**
+ * Returns a transformer for the given primitive type.
+ *
+ * @param aType the primitive type whose transformer to return
+ * @return a transformer that will convert strings into that type, or null
if the given type is not a primitive type
+ */
+ protected Function getTypeTransformer(final Class<?> aType) {
+ return typeTransformers.get(aType);
+ }
+
+ /**
+ * Logs the given exception to <code>System.out</code>. Used to display
warnings while accessing/mutating the bean.
+ *
+ * @param ex the exception to log
+ */
+ protected void logInfo(final Exception ex) {
+ // Deliberately do not use LOG4J or Commons Logging to avoid
dependencies
+ System.out.println("INFO: Exception: " + ex);
+ }
+
+ /**
+ * Logs the given exception to <code>System.err</code>. Used to display
errors while accessing/mutating the bean.
+ *
+ * @param ex the exception to log
+ */
+ protected void logWarn(final Exception ex) {
+ // Deliberately do not use LOG4J or Commons Logging to avoid
dependencies
+ System.out.println("WARN: Exception: " + ex);
+ ex.printStackTrace();
+ }
+}
diff --git a/src/test/java/org/apache/commons/beanutils2/BeanMapTestCase.java
b/src/test/java/org/apache/commons/beanutils2/BeanMapTestCase.java
index 695d728..e3a1d87 100644
--- a/src/test/java/org/apache/commons/beanutils2/BeanMapTestCase.java
+++ b/src/test/java/org/apache/commons/beanutils2/BeanMapTestCase.java
@@ -1,487 +1,487 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.beanutils2;
-
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.Map;
-
-import org.apache.commons.beanutils2.bugs.other.Jira87BeanFactory;
-import org.apache.commons.collections.BulkTest;
-import org.apache.commons.collections.map.AbstractTestMap;
-
-import junit.framework.Test;
-import junit.textui.TestRunner;
-
-/**
- * Test cases for BeanMap
- *
- */
-@SuppressWarnings("deprecation")
-public class BeanMapTestCase extends AbstractTestMap {
-
- public BeanMapTestCase(final String testName) {
- super(testName);
- }
-
- public static void main(final String[] args) {
- TestRunner.run(suite());
- }
-
- public static Test suite() {
- return BulkTest.makeSuite(BeanMapTestCase.class);
- }
-
-/*
- note to self. The getter and setter methods were generated by copying the
- field declarations and using the following regular expression search and
- replace:
-
- From:
- private \(.*\) some\(.*\);
- To:
- public \1 getSome\2Value() {
- return some\2;
- }
- public void setSome\2Value(\1 value) {
- some\2 = value;
- }
-
- Also note: The sample keys and mappings were generated manually.
-*/
-
-
- public static class BeanWithProperties implements Serializable {
- private int someInt;
- private long someLong;
- private double someDouble;
- private float someFloat;
- private short someShort;
- private byte someByte;
- private char someChar;
- private Integer someInteger;
- private String someString;
- private Object someObject;
-
- public int getSomeIntValue() {
- return someInt;
- }
- public void setSomeIntValue(final int value) {
- someInt = value;
- }
-
- public long getSomeLongValue() {
- return someLong;
- }
- public void setSomeLongValue(final long value) {
- someLong = value;
- }
-
- public double getSomeDoubleValue() {
- return someDouble;
- }
- public void setSomeDoubleValue(final double value) {
- someDouble = value;
- }
-
- public float getSomeFloatValue() {
- return someFloat;
- }
- public void setSomeFloatValue(final float value) {
- someFloat = value;
- }
-
- public short getSomeShortValue() {
- return someShort;
- }
- public void setSomeShortValue(final short value) {
- someShort = value;
- }
-
- public byte getSomeByteValue() {
- return someByte;
- }
- public void setSomeByteValue(final byte value) {
- someByte = value;
- }
-
- public char getSomeCharValue() {
- return someChar;
- }
- public void setSomeCharValue(final char value) {
- someChar = value;
- }
-
- public String getSomeStringValue() {
- return someString;
- }
- public void setSomeStringValue(final String value) {
- someString = value;
- }
-
- public Integer getSomeIntegerValue() {
- return someInteger;
- }
- public void setSomeIntegerValue(final Integer value) {
- someInteger = value;
- }
-
- public Object getSomeObjectValue() {
- return someObject;
- }
- public void setSomeObjectValue(final Object value) {
- someObject = value;
- }
- }
-
- public static class BeanThrowingExceptions extends BeanWithProperties {
- private static final long serialVersionUID = 1L;
- public void setValueThrowingException(final String value) {
- throw new TestException();
- }
- public String getValueThrowingException() {
- throw new TestException();
- }
- }
-
- /**
- * Exception for testing exception handling.
- */
- public static class TestException extends RuntimeException {
- private static final long serialVersionUID = 1L;
- }
-
- // note to self. The Sample keys were generated by copying the field
- // declarations and using the following regular expression search and
replace:
- //
- // From:
- // private \(.*\) some\(.*\);
- // To:
- // "some\2Value",
- //
- // Then, I manually added the "class" key, which is a property that exists
for
- // all beans (and all objects for that matter.
- @Override
- public Object[] getSampleKeys() {
- final Object[] keys = new Object[] {
- "someIntValue",
- "someLongValue",
- "someDoubleValue",
- "someFloatValue",
- "someShortValue",
- "someByteValue",
- "someCharValue",
- "someIntegerValue",
- "someStringValue",
- "someObjectValue",
- "class",
- };
- return keys;
- }
-
- /**
- * An object value that will be stored in the bean map as a value. Need
- * to save this externally so that we can make sure the object instances
- * are equivalent since getSampleValues() would otherwise construct a new
- * and different Object each time.
- **/
- private final Object objectInFullMap = new Object();
-
- // note to self: the sample values were created manually
- @Override
- public Object[] getSampleValues() {
- final Object[] values = new Object[] {
- new Integer(1234),
- new Long(1298341928234L),
- new Double(123423.34),
- new Float(1213332.12f),
- new Short((short)134),
- new Byte((byte)10),
- new Character('a'),
- new Integer(1432),
- "SomeStringValue",
- objectInFullMap,
- BeanWithProperties.class,
- };
- return values;
- }
-
- @Override
- public Object[] getNewSampleValues() {
- final Object[] values = new Object[] {
- new Integer(223),
- new Long(23341928234L),
- new Double(23423.34),
- new Float(213332.12f),
- new Short((short)234),
- new Byte((byte)20),
- new Character('b'),
- new Integer(232),
- "SomeNewStringValue",
- new Object(),
- null,
- };
- return values;
- }
-
- /**
- * Values is a dead copy in BeanMap, so refresh each time.
- */
- @Override
- public void verifyValues() {
- values = map.values();
- super.verifyValues();
- }
-
- /**
- * The mappings in a BeanMap are fixed on the properties the underlying
- * bean has. Adding and removing mappings is not possible, thus this
- * method is overridden to return false.
- */
- @Override
- public boolean isPutAddSupported() {
- return false;
- }
-
- /**
- * The mappings in a BeanMap are fixed on the properties the underlying
- * bean has. Adding and removing mappings is not possible, thus this
- * method is overridden to return false.
- */
- @Override
- public boolean isRemoveSupported() {
- return false;
- }
-
- @Override
- public Map<Object, Object> makeFullMap() {
- // note: These values must match (i.e. .equals() must return true)
- // those returned from getSampleValues().
- final BeanWithProperties bean = new BeanWithProperties();
- bean.setSomeIntValue(1234);
- bean.setSomeLongValue(1298341928234L);
- bean.setSomeDoubleValue(123423.34);
- bean.setSomeFloatValue(1213332.12f);
- bean.setSomeShortValue((short)134);
- bean.setSomeByteValue((byte)10);
- bean.setSomeCharValue('a');
- bean.setSomeIntegerValue(new Integer(1432));
- bean.setSomeStringValue("SomeStringValue");
- bean.setSomeObjectValue(objectInFullMap);
- return new BeanMap(bean);
- }
-
- @Override
- public Map<Object, Object> makeEmptyMap() {
- return new BeanMap();
- }
-
- @Override
- public String[] ignoredTests() {
- // Ignore the serialization tests on collection views.
- return new String[] {
- "TestBeanMap.bulkTestMapEntrySet.testCanonicalEmptyCollectionExists",
- "TestBeanMap.bulkTestMapEntrySet.testCanonicalFullCollectionExists",
- "TestBeanMap.bulkTestMapKeySet.testCanonicalEmptyCollectionExists",
- "TestBeanMap.bulkTestMapKeySet.testCanonicalFullCollectionExists",
- "TestBeanMap.bulkTestMapValues.testCanonicalEmptyCollectionExists",
- "TestBeanMap.bulkTestMapValues.testCanonicalFullCollectionExists",
- "TestBeanMap.bulkTestMapEntrySet.testSimpleSerialization",
- "TestBeanMap.bulkTestMapKeySet.testSimpleSerialization",
- "TestBeanMap.bulkTestMapEntrySet.testSerializeDeserializeThenCompare",
- "TestBeanMap.bulkTestMapKeySet.testSerializeDeserializeThenCompare"
- };
- }
-
- /**
- * Need to override this method because the "clear()" method on the bean
- * map just returns the bean properties to their default states. It does
- * not actually remove the mappings as per the map contract. The default
- * testClear() methods checks that the clear method throws an
- * UnsupportedOperationException since this class is not add/remove
- * modifiable. In our case though, we do not always throw that exception.
- */
- @Override
- public void testMapClear() {
- //TODO: make sure a call to BeanMap.clear returns the bean to its
- //default initialization values.
- }
-
- /**
- * Need to override this method because the "put()" method on the bean
- * doesn't work for this type of Map.
- */
- @Override
- public void testMapPut() {
- // see testBeanMapPutAllWriteable
- }
-
- public void testBeanMapClone() {
- final BeanMap map = (BeanMap)makeFullMap();
- try {
- final BeanMap map2 = (BeanMap)map.clone();
-
- // make sure containsKey is working to verify the bean was cloned
- // ok, and the read methods were properly initialized
- final Object[] keys = getSampleKeys();
- for (final Object key : keys) {
- assertTrue("Cloned BeanMap should contain the same keys",
- map2.containsKey(key));
- }
- } catch (final CloneNotSupportedException exception) {
- fail("BeanMap.clone() should not throw a " +
- "CloneNotSupportedException when clone should succeed.");
- }
- }
-
- public void testBeanMapPutAllWriteable() {
- final BeanMap map1 = (BeanMap)makeFullMap();
- final BeanMap map2 = (BeanMap)makeFullMap();
- map2.put("someIntValue", new Integer(0));
- map1.putAllWriteable(map2);
- assertEquals(map1.get("someIntValue"), new Integer(0));
- }
-
- public void testMethodAccessor() throws Exception {
- final BeanMap map = (BeanMap) makeFullMap();
- final Method method =
BeanWithProperties.class.getDeclaredMethod("getSomeIntegerValue");
- assertEquals(method, map.getReadMethod("someIntegerValue"));
- }
-
- public void testMethodMutator() throws Exception {
- final BeanMap map = (BeanMap) makeFullMap();
- final Method method =
BeanWithProperties.class.getDeclaredMethod("setSomeIntegerValue", new Class[]
{Integer.class});
- assertEquals(method, map.getWriteMethod("someIntegerValue"));
- }
-
- /**
- * Test the default transformers using the getTypeTransformer() method
- */
- public void testGetTypeTransformerMethod() {
- final BeanMap beanMap = new BeanMap();
- assertEquals("Boolean.TYPE", Boolean.TRUE,
beanMap.getTypeTransformer(Boolean.TYPE).transform("true"));
- assertEquals("Character.TYPE", new Character('B'),
beanMap.getTypeTransformer(Character.TYPE).transform("BCD"));
- assertEquals("Byte.TYPE", new Byte((byte)1),
beanMap.getTypeTransformer(Byte.TYPE).transform("1"));
- assertEquals("Short.TYPE", new Short((short)2),
beanMap.getTypeTransformer(Short.TYPE).transform("2"));
- assertEquals("Integer.TYPE", new Integer(3),
beanMap.getTypeTransformer(Integer.TYPE).transform("3"));
- assertEquals("Long.TYPE", new Long(4),
beanMap.getTypeTransformer(Long.TYPE).transform("4"));
- assertEquals("Float.TYPE", new Float("5"),
beanMap.getTypeTransformer(Float.TYPE).transform("5"));
- assertEquals("Double.TYPE", new Double("6"),
beanMap.getTypeTransformer(Double.TYPE).transform("6"));
- }
-
- /**
- * Test that the cause of exception thrown by a clone() is initialised.
- */
- public void testExceptionThrowFromClone() {
-
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("testExceptionThrowFromClone() skipped on pre
1.4 JVM");
- return;
- }
-
- // Test cloning a non-public bean (instantiation exception)
- try {
- final Object bean = Jira87BeanFactory.createMappedPropertyBean();
- final BeanMap map = new BeanMap(bean);
- map.clone();
- fail("Non-public bean clone() - expected
CloneNotSupportedException");
- } catch (final CloneNotSupportedException e) {
- Throwable cause = null;
- try {
- cause = (Throwable)PropertyUtils.getProperty(e, "cause");
- } catch (final Exception e2) {
- fail("Non-public bean - retrieving the cause threw " + e2);
- }
- assertNotNull("Non-public bean cause null", cause);
- assertEquals("Non-public bean cause",
IllegalAccessException.class, cause.getClass());
- }
-
- // Test cloning a bean that throws exception
- try {
- final BeanMap map = new BeanMap(new BeanThrowingExceptions());
- map.clone();
- fail("Setter Exception clone() - expected
CloneNotSupportedException");
- } catch (final CloneNotSupportedException e) {
- Throwable cause = null;
- try {
- cause = (Throwable)PropertyUtils.getProperty(e, "cause");
- } catch (final Exception e2) {
- fail("Setter Exception - retrieving the cause threw " + e2);
- }
- assertNotNull("Setter Exception cause null", cause);
- assertEquals("Setter Exception cause",
IllegalArgumentException.class, cause.getClass());
- }
- }
-
- /**
- * Test that the cause of exception thrown by clear() is initialised.
- */
- public void testExceptionThrowFromClear() {
-
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("testExceptionThrowFromClear() skipped on pre
1.4 JVM");
- return;
- }
-
- try {
- final Object bean = Jira87BeanFactory.createMappedPropertyBean();
- final BeanMap map = new BeanMap(bean);
- map.clear();
- fail("clear() - expected UnsupportedOperationException");
- } catch (final UnsupportedOperationException e) {
- Throwable cause = null;
- try {
- cause = (Throwable)PropertyUtils.getProperty(e, "cause");
- } catch (final Exception e2) {
- fail("Retrieving the cause threw " + e2);
- }
- assertNotNull("Cause null", cause);
- assertEquals("Cause", IllegalAccessException.class,
cause.getClass());
- }
- }
-
- /**
- * Test that the cause of exception thrown by put() is initialized.
- */
- public void testExceptionThrowFromPut() {
-
- if (BeanUtilsTestCase.isPre14JVM()) {
- System.out.println("testExceptionThrowFromPut() skipped on pre 1.4
JVM");
- return;
- }
-
- try {
- final Map<Object, Object> map = new BeanMap(new
BeanThrowingExceptions());
- map.put("valueThrowingException", "value");
- fail("Setter exception - expected IllegalArgumentException");
- } catch (final IllegalArgumentException e) {
- Throwable cause1 = null;
- Throwable cause2 = null;
- try {
- cause1 = (Throwable)PropertyUtils.getProperty(e, "cause");
- cause2 = (Throwable)PropertyUtils.getProperty(e,
"cause.cause");
- } catch (final Exception e2) {
- fail("Setter exception - retrieving the cause threw " + e2);
- }
- assertNotNull("Setter exception cause 1 null", cause1);
- assertEquals("Setter exception cause 1",
InvocationTargetException.class, cause1.getClass());
- assertNotNull("Setter exception cause 2 null", cause2);
- assertEquals("Setter exception cause 2", TestException.class,
cause2.getClass());
- }
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.beanutils2;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import org.apache.commons.beanutils2.bugs.other.Jira87BeanFactory;
+import org.apache.commons.collections.BulkTest;
+import org.apache.commons.collections.map.AbstractTestMap;
+
+import junit.framework.Test;
+import junit.textui.TestRunner;
+
+/**
+ * Test cases for BeanMap
+ *
+ */
+@SuppressWarnings("deprecation")
+public class BeanMapTestCase extends AbstractTestMap {
+
+ public BeanMapTestCase(final String testName) {
+ super(testName);
+ }
+
+ public static void main(final String[] args) {
+ TestRunner.run(suite());
+ }
+
+ public static Test suite() {
+ return BulkTest.makeSuite(BeanMapTestCase.class);
+ }
+
+/*
+ note to self. The getter and setter methods were generated by copying the
+ field declarations and using the following regular expression search and
+ replace:
+
+ From:
+ private \(.*\) some\(.*\);
+ To:
+ public \1 getSome\2Value() {
+ return some\2;
+ }
+ public void setSome\2Value(\1 value) {
+ some\2 = value;
+ }
+
+ Also note: The sample keys and mappings were generated manually.
+*/
+
+
+ public static class BeanWithProperties implements Serializable {
+ private int someInt;
+ private long someLong;
+ private double someDouble;
+ private float someFloat;
+ private short someShort;
+ private byte someByte;
+ private char someChar;
+ private Integer someInteger;
+ private String someString;
+ private Object someObject;
+
+ public int getSomeIntValue() {
+ return someInt;
+ }
+ public void setSomeIntValue(final int value) {
+ someInt = value;
+ }
+
+ public long getSomeLongValue() {
+ return someLong;
+ }
+ public void setSomeLongValue(final long value) {
+ someLong = value;
+ }
+
+ public double getSomeDoubleValue() {
+ return someDouble;
+ }
+ public void setSomeDoubleValue(final double value) {
+ someDouble = value;
+ }
+
+ public float getSomeFloatValue() {
+ return someFloat;
+ }
+ public void setSomeFloatValue(final float value) {
+ someFloat = value;
+ }
+
+ public short getSomeShortValue() {
+ return someShort;
+ }
+ public void setSomeShortValue(final short value) {
+ someShort = value;
+ }
+
+ public byte getSomeByteValue() {
+ return someByte;
+ }
+ public void setSomeByteValue(final byte value) {
+ someByte = value;
+ }
+
+ public char getSomeCharValue() {
+ return someChar;
+ }
+ public void setSomeCharValue(final char value) {
+ someChar = value;
+ }
+
+ public String getSomeStringValue() {
+ return someString;
+ }
+ public void setSomeStringValue(final String value) {
+ someString = value;
+ }
+
+ public Integer getSomeIntegerValue() {
+ return someInteger;
+ }
+ public void setSomeIntegerValue(final Integer value) {
+ someInteger = value;
+ }
+
+ public Object getSomeObjectValue() {
+ return someObject;
+ }
+ public void setSomeObjectValue(final Object value) {
+ someObject = value;
+ }
+ }
+
+ public static class BeanThrowingExceptions extends BeanWithProperties {
+ private static final long serialVersionUID = 1L;
+ public void setValueThrowingException(final String value) {
+ throw new TestException();
+ }
+ public String getValueThrowingException() {
+ throw new TestException();
+ }
+ }
+
+ /**
+ * Exception for testing exception handling.
+ */
+ public static class TestException extends RuntimeException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ // note to self. The Sample keys were generated by copying the field
+ // declarations and using the following regular expression search and
replace:
+ //
+ // From:
+ // private \(.*\) some\(.*\);
+ // To:
+ // "some\2Value",
+ //
+ // Then, I manually added the "class" key, which is a property that exists
for
+ // all beans (and all objects for that matter.
+ @Override
+ public Object[] getSampleKeys() {
+ final Object[] keys = new Object[] {
+ "someIntValue",
+ "someLongValue",
+ "someDoubleValue",
+ "someFloatValue",
+ "someShortValue",
+ "someByteValue",
+ "someCharValue",
+ "someIntegerValue",
+ "someStringValue",
+ "someObjectValue",
+ "class",
+ };
+ return keys;
+ }
+
+ /**
+ * An object value that will be stored in the bean map as a value. Need
+ * to save this externally so that we can make sure the object instances
+ * are equivalent since getSampleValues() would otherwise construct a new
+ * and different Object each time.
+ **/
+ private final Object objectInFullMap = new Object();
+
+ // note to self: the sample values were created manually
+ @Override
+ public Object[] getSampleValues() {
+ final Object[] values = new Object[] {
+ new Integer(1234),
+ new Long(1298341928234L),
+ new Double(123423.34),
+ new Float(1213332.12f),
+ new Short((short)134),
+ new Byte((byte)10),
+ new Character('a'),
+ new Integer(1432),
+ "SomeStringValue",
+ objectInFullMap,
+ BeanWithProperties.class,
+ };
+ return values;
+ }
+
+ @Override
+ public Object[] getNewSampleValues() {
+ final Object[] values = new Object[] {
+ new Integer(223),
+ new Long(23341928234L),
+ new Double(23423.34),
+ new Float(213332.12f),
+ new Short((short)234),
+ new Byte((byte)20),
+ new Character('b'),
+ new Integer(232),
+ "SomeNewStringValue",
+ new Object(),
+ null,
+ };
+ return values;
+ }
+
+ /**
+ * Values is a dead copy in BeanMap, so refresh each time.
+ */
+ @Override
+ public void verifyValues() {
+ values = map.values();
+ super.verifyValues();
+ }
+
+ /**
+ * The mappings in a BeanMap are fixed on the properties the underlying
+ * bean has. Adding and removing mappings is not possible, thus this
+ * method is overridden to return false.
+ */
+ @Override
+ public boolean isPutAddSupported() {
+ return false;
+ }
+
+ /**
+ * The mappings in a BeanMap are fixed on the properties the underlying
+ * bean has. Adding and removing mappings is not possible, thus this
+ * method is overridden to return false.
+ */
+ @Override
+ public boolean isRemoveSupported() {
+ return false;
+ }
+
+ @Override
+ public Map<Object, Object> makeFullMap() {
+ // note: These values must match (i.e. .equals() must return true)
+ // those returned from getSampleValues().
+ final BeanWithProperties bean = new BeanWithProperties();
+ bean.setSomeIntValue(1234);
+ bean.setSomeLongValue(1298341928234L);
+ bean.setSomeDoubleValue(123423.34);
+ bean.setSomeFloatValue(1213332.12f);
+ bean.setSomeShortValue((short)134);
+ bean.setSomeByteValue((byte)10);
+ bean.setSomeCharValue('a');
+ bean.setSomeIntegerValue(new Integer(1432));
+ bean.setSomeStringValue("SomeStringValue");
+ bean.setSomeObjectValue(objectInFullMap);
+ return new BeanMap(bean);
+ }
+
+ @Override
+ public Map<Object, Object> makeEmptyMap() {
+ return new BeanMap();
+ }
+
+ @Override
+ public String[] ignoredTests() {
+ // Ignore the serialization tests on collection views.
+ return new String[] {
+ "TestBeanMap.bulkTestMapEntrySet.testCanonicalEmptyCollectionExists",
+ "TestBeanMap.bulkTestMapEntrySet.testCanonicalFullCollectionExists",
+ "TestBeanMap.bulkTestMapKeySet.testCanonicalEmptyCollectionExists",
+ "TestBeanMap.bulkTestMapKeySet.testCanonicalFullCollectionExists",
+ "TestBeanMap.bulkTestMapValues.testCanonicalEmptyCollectionExists",
+ "TestBeanMap.bulkTestMapValues.testCanonicalFullCollectionExists",
+ "TestBeanMap.bulkTestMapEntrySet.testSimpleSerialization",
+ "TestBeanMap.bulkTestMapKeySet.testSimpleSerialization",
+ "TestBeanMap.bulkTestMapEntrySet.testSerializeDeserializeThenCompare",
+ "TestBeanMap.bulkTestMapKeySet.testSerializeDeserializeThenCompare"
+ };
+ }
+
+ /**
+ * Need to override this method because the "clear()" method on the bean
+ * map just returns the bean properties to their default states. It does
+ * not actually remove the mappings as per the map contract. The default
+ * testClear() methods checks that the clear method throws an
+ * UnsupportedOperationException since this class is not add/remove
+ * modifiable. In our case though, we do not always throw that exception.
+ */
+ @Override
+ public void testMapClear() {
+ //TODO: make sure a call to BeanMap.clear returns the bean to its
+ //default initialization values.
+ }
+
+ /**
+ * Need to override this method because the "put()" method on the bean
+ * doesn't work for this type of Map.
+ */
+ @Override
+ public void testMapPut() {
+ // see testBeanMapPutAllWriteable
+ }
+
+ public void testBeanMapClone() {
+ final BeanMap map = (BeanMap)makeFullMap();
+ try {
+ final BeanMap map2 = (BeanMap)map.clone();
+
+ // make sure containsKey is working to verify the bean was cloned
+ // ok, and the read methods were properly initialized
+ final Object[] keys = getSampleKeys();
+ for (final Object key : keys) {
+ assertTrue("Cloned BeanMap should contain the same keys",
+ map2.containsKey(key));
+ }
+ } catch (final CloneNotSupportedException exception) {
+ fail("BeanMap.clone() should not throw a " +
+ "CloneNotSupportedException when clone should succeed.");
+ }
+ }
+
+ public void testBeanMapPutAllWriteable() {
+ final BeanMap map1 = (BeanMap)makeFullMap();
+ final BeanMap map2 = (BeanMap)makeFullMap();
+ map2.put("someIntValue", new Integer(0));
+ map1.putAllWriteable(map2);
+ assertEquals(map1.get("someIntValue"), new Integer(0));
+ }
+
+ public void testMethodAccessor() throws Exception {
+ final BeanMap map = (BeanMap) makeFullMap();
+ final Method method =
BeanWithProperties.class.getDeclaredMethod("getSomeIntegerValue");
+ assertEquals(method, map.getReadMethod("someIntegerValue"));
+ }
+
+ public void testMethodMutator() throws Exception {
+ final BeanMap map = (BeanMap) makeFullMap();
+ final Method method =
BeanWithProperties.class.getDeclaredMethod("setSomeIntegerValue", new Class[]
{Integer.class});
+ assertEquals(method, map.getWriteMethod("someIntegerValue"));
+ }
+
+ /**
+ * Test the default transformers using the getTypeTransformer() method
+ */
+ public void testGetTypeTransformerMethod() {
+ final BeanMap beanMap = new BeanMap();
+ assertEquals("Boolean.TYPE", Boolean.TRUE,
beanMap.getTypeTransformer(Boolean.TYPE).apply("true"));
+ assertEquals("Character.TYPE", new Character('B'),
beanMap.getTypeTransformer(Character.TYPE).apply("BCD"));
+ assertEquals("Byte.TYPE", new Byte((byte)1),
beanMap.getTypeTransformer(Byte.TYPE).apply("1"));
+ assertEquals("Short.TYPE", new Short((short)2),
beanMap.getTypeTransformer(Short.TYPE).apply("2"));
+ assertEquals("Integer.TYPE", new Integer(3),
beanMap.getTypeTransformer(Integer.TYPE).apply("3"));
+ assertEquals("Long.TYPE", new Long(4),
beanMap.getTypeTransformer(Long.TYPE).apply("4"));
+ assertEquals("Float.TYPE", new Float("5"),
beanMap.getTypeTransformer(Float.TYPE).apply("5"));
+ assertEquals("Double.TYPE", new Double("6"),
beanMap.getTypeTransformer(Double.TYPE).apply("6"));
+ }
+
+ /**
+ * Test that the cause of exception thrown by a clone() is initialised.
+ */
+ public void testExceptionThrowFromClone() {
+
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("testExceptionThrowFromClone() skipped on pre
1.4 JVM");
+ return;
+ }
+
+ // Test cloning a non-public bean (instantiation exception)
+ try {
+ final Object bean = Jira87BeanFactory.createMappedPropertyBean();
+ final BeanMap map = new BeanMap(bean);
+ map.clone();
+ fail("Non-public bean clone() - expected
CloneNotSupportedException");
+ } catch (final CloneNotSupportedException e) {
+ Throwable cause = null;
+ try {
+ cause = (Throwable)PropertyUtils.getProperty(e, "cause");
+ } catch (final Exception e2) {
+ fail("Non-public bean - retrieving the cause threw " + e2);
+ }
+ assertNotNull("Non-public bean cause null", cause);
+ assertEquals("Non-public bean cause",
IllegalAccessException.class, cause.getClass());
+ }
+
+ // Test cloning a bean that throws exception
+ try {
+ final BeanMap map = new BeanMap(new BeanThrowingExceptions());
+ map.clone();
+ fail("Setter Exception clone() - expected
CloneNotSupportedException");
+ } catch (final CloneNotSupportedException e) {
+ Throwable cause = null;
+ try {
+ cause = (Throwable)PropertyUtils.getProperty(e, "cause");
+ } catch (final Exception e2) {
+ fail("Setter Exception - retrieving the cause threw " + e2);
+ }
+ assertNotNull("Setter Exception cause null", cause);
+ assertEquals("Setter Exception cause",
IllegalArgumentException.class, cause.getClass());
+ }
+ }
+
+ /**
+ * Test that the cause of exception thrown by clear() is initialised.
+ */
+ public void testExceptionThrowFromClear() {
+
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("testExceptionThrowFromClear() skipped on pre
1.4 JVM");
+ return;
+ }
+
+ try {
+ final Object bean = Jira87BeanFactory.createMappedPropertyBean();
+ final BeanMap map = new BeanMap(bean);
+ map.clear();
+ fail("clear() - expected UnsupportedOperationException");
+ } catch (final UnsupportedOperationException e) {
+ Throwable cause = null;
+ try {
+ cause = (Throwable)PropertyUtils.getProperty(e, "cause");
+ } catch (final Exception e2) {
+ fail("Retrieving the cause threw " + e2);
+ }
+ assertNotNull("Cause null", cause);
+ assertEquals("Cause", IllegalAccessException.class,
cause.getClass());
+ }
+ }
+
+ /**
+ * Test that the cause of exception thrown by put() is initialized.
+ */
+ public void testExceptionThrowFromPut() {
+
+ if (BeanUtilsTestCase.isPre14JVM()) {
+ System.out.println("testExceptionThrowFromPut() skipped on pre 1.4
JVM");
+ return;
+ }
+
+ try {
+ final Map<Object, Object> map = new BeanMap(new
BeanThrowingExceptions());
+ map.put("valueThrowingException", "value");
+ fail("Setter exception - expected IllegalArgumentException");
+ } catch (final IllegalArgumentException e) {
+ Throwable cause1 = null;
+ Throwable cause2 = null;
+ try {
+ cause1 = (Throwable)PropertyUtils.getProperty(e, "cause");
+ cause2 = (Throwable)PropertyUtils.getProperty(e,
"cause.cause");
+ } catch (final Exception e2) {
+ fail("Setter exception - retrieving the cause threw " + e2);
+ }
+ assertNotNull("Setter exception cause 1 null", cause1);
+ assertEquals("Setter exception cause 1",
InvocationTargetException.class, cause1.getClass());
+ assertNotNull("Setter exception cause 2 null", cause2);
+ assertEquals("Setter exception cause 2", TestException.class,
cause2.getClass());
+ }
+ }
+}