Return-Path:
Copy the specified property value to the specified destination bean,
* performing any type conversion that is required. If the specified
- * bean does not have a property of the specified name, return without
+ * bean does not have a property of the specified name, or the property
+ * is read only on the destination bean, return without
* doing anything. If you have custom destination property types, register
* {@link Converter}s for them by calling the FIXME - Indexed and mapped properties that do not
- * have getter and setter methods for the underlying array or Map are not
- * copied by this method. IMPLEMENTATION RESTRICTIONS:register()
* method of {@link ConvertUtils}.
+ *
*
* @param bean Bean on which setting is to be performed
- * @param name Simple property name of the property to be set
- * @param value Property value to be set
+ * @param name Property name (can be nested/indexed/mapped/combo)
+ * @param value Value to be set
*
* @exception IllegalAccessException if the caller does not have
* access to the property accessor method
@@ -296,6 +306,7 @@
public static void copyProperty(Object bean, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
+ // Trace logging (if enabled)
if (log.isTraceEnabled()) {
StringBuffer sb = new StringBuffer(" copyProperty(");
sb.append(bean);
@@ -323,55 +334,123 @@
log.trace(sb.toString());
}
- if (bean instanceof DynaBean) {
- DynaProperty propDescriptor =
- ((DynaBean) bean).getDynaClass().getDynaProperty(name);
- if (propDescriptor != null) {
- Converter converter =
- ConvertUtils.lookup(propDescriptor.getType());
- if (converter != null) {
- value = converter.convert(propDescriptor.getType(), value);
- }
- try {
- PropertyUtils.setSimpleProperty(bean, name, value);
- } catch (NoSuchMethodException e) {
- log.error("-->Should not have happened", e);
- ; // Silently ignored
+ // Resolve any nested expression to get the actual target bean
+ Object target = bean;
+ int delim = name.lastIndexOf(PropertyUtils.NESTED_DELIM);
+ if (delim >= 0) {
+ try {
+ target =
+ PropertyUtils.getProperty(bean, name.substring(0, delim));
+ } catch (NoSuchMethodException e) {
+ return; // Skip this property setter
+ }
+ name = name.substring(delim + 1);
+ if (log.isTraceEnabled()) {
+ log.trace(" Target bean = " + target);
+ log.trace(" Target name = " + name);
+ }
+ }
+
+ // Declare local variables we will require
+ String propName = null; // Simple name of target property
+ Class type = null; // Java type of target property
+ int index = -1; // Indexed subscript value (if any)
+ String key = null; // Mapped key value (if any)
+
+ // Calculate the target property name, index, and key values
+ propName = name;
+ int i = propName.indexOf(PropertyUtils.INDEXED_DELIM);
+ if (i >= 0) {
+ int k = propName.indexOf(PropertyUtils.INDEXED_DELIM2);
+ try {
+ index =
+ Integer.parseInt(propName.substring(i + 1, k));
+ } catch (NumberFormatException e) {
+ ;
+ }
+ propName = propName.substring(0, i);
+ }
+ int j = propName.indexOf(PropertyUtils.MAPPED_DELIM);
+ if (j >= 0) {
+ int k = propName.indexOf(PropertyUtils.MAPPED_DELIM2);
+ try {
+ key = propName.substring(j + 1, k);
+ } catch (IndexOutOfBoundsException e) {
+ ;
+ }
+ propName = propName.substring(0, j);
+ }
+
+ // Calculate the target property type
+ if (target instanceof DynaBean) {
+ DynaClass dynaClass = ((DynaBean) target).getDynaClass();
+ DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
+ if (dynaProperty == null) {
+ return; // Skip this property setter
+ }
+ type = dynaProperty.getType();
+ } else {
+ PropertyDescriptor descriptor = null;
+ try {
+ descriptor =
+ PropertyUtils.getPropertyDescriptor(target, name);
+ if (descriptor == null) {
+ return; // Skip this property setter
}
- } else {
+ } catch (NoSuchMethodException e) {
+ return; // Skip this property setter
+ }
+ type = descriptor.getPropertyType();
+ if (type == null) {
+ // Most likely an indexed setter on a POJB only
if (log.isTraceEnabled()) {
- log.trace("-->No setter on 'to' DynaBean, skipping");
+ log.trace(" target type for property '" +
+ propName + "' is null, so skipping ths setter");
}
+ return;
+ }
+ }
+ if (log.isTraceEnabled()) {
+ log.trace(" target propName=" + propName + ", type=" +
+ type + ", index=" + index + ", key=" + key);
+ }
+
+ // Convert the specified value to the required type and store it
+ if (index >= 0) { // Destination must be indexed
+ Converter converter = ConvertUtils.lookup(type.getComponentType());
+ if (converter != null) {
+ log.trace(" USING CONVERTER " + converter);
+ value = converter.convert(type, value);
}
- } else /* if (!(bean instanceof DynaBean)) */ {
- PropertyDescriptor propDescriptor = null;
try {
- propDescriptor =
- PropertyUtils.getPropertyDescriptor(bean, name);
+ PropertyUtils.setIndexedProperty(target, propName,
+ index, value);
} catch (NoSuchMethodException e) {
- propDescriptor = null;
+ throw new InvocationTargetException
+ (e, "Cannot set " + propName);
}
- if ((propDescriptor != null) &&
- (propDescriptor.getWriteMethod() == null)) {
- propDescriptor = null;
- }
- if (propDescriptor != null) {
- Converter converter =
- ConvertUtils.lookup(propDescriptor.getPropertyType());
- if (converter != null) {
- value = converter.convert
- (propDescriptor.getPropertyType(), value);
- }
- try {
- PropertyUtils.setSimpleProperty(bean, name, value);
- } catch (NoSuchMethodException e) {
- log.error("-->Should not have happened", e);
- ; // Silently ignored
- }
- } else {
- if (log.isTraceEnabled()) {
- log.trace("-->No setter on JavaBean, skipping");
- }
+ } else if (key != null) { // Destination must be mapped
+ // Maps do not know what the preferred data type is,
+ // so perform no conversions at all
+ // FIXME - should we create or support a TypedMap?
+ try {
+ PropertyUtils.setMappedProperty(target, propName,
+ key, value);
+ } catch (NoSuchMethodException e) {
+ throw new InvocationTargetException
+ (e, "Cannot set " + propName);
+ }
+ } else { // Destination must be simple
+ Converter converter = ConvertUtils.lookup(type);
+ if (converter != null) {
+ log.trace(" USING CONVERTER " + converter);
+ value = converter.convert(type, value);
+ }
+ try {
+ PropertyUtils.setSimpleProperty(target, propName, value);
+ } catch (NoSuchMethodException e) {
+ throw new InvocationTargetException
+ (e, "Cannot set " + propName);
}
}
@@ -392,7 +471,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static Map describe(Object bean)
throws IllegalAccessException, InvocationTargetException,
@@ -441,7 +520,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String[] getArrayProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
@@ -499,7 +578,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getIndexedProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
@@ -525,7 +604,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getIndexedProperty(Object bean,
String name, int index)
@@ -554,7 +633,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getMappedProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
@@ -580,7 +659,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getMappedProperty(Object bean,
String name, String key)
@@ -607,7 +686,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getNestedProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
@@ -632,7 +711,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
@@ -655,7 +734,7 @@
* @exception InvocationTargetException if the property accessor method
* throws an exception
* @exception NoSuchMethodException if an accessor method for this
- * propety cannot be found
+ * property cannot be found
*/
public static String getSimpleProperty(Object bean, String name)
throws IllegalAccessException, InvocationTargetException,
@@ -747,6 +826,12 @@
* to meet the needs of populate()
, and is probably not what
* you want for general property copying with type conversion. For that
* purpose, check out the copyProperty()
method instead.
WARNING - PLEASE do not modify the behavior of this + * method without consulting with the Struts developer community. There + * are some subtleties to its functionality that are not documented in the + * Javadoc description above, yet are vital to the way that Struts utilizes + * this method.
* * @param bean Bean on which setting is to be performed * @param name Property name (can be nested/indexed/mapped/combo) 1.20 +125 -4 jakarta-commons/beanutils/src/test/org/apache/commons/beanutils/BeanUtilsTestCase.java Index: BeanUtilsTestCase.java =================================================================== RCS file: /home/cvs/jakarta-commons/beanutils/src/test/org/apache/commons/beanutils/BeanUtilsTestCase.java,v retrieving revision 1.19 retrieving revision 1.20 diff -u -r1.19 -r1.20 --- BeanUtilsTestCase.java 1 Feb 2003 08:14:33 -0000 1.19 +++ BeanUtilsTestCase.java 4 Feb 2003 07:28:14 -0000 1.20 @@ -64,6 +64,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; import junit.framework.Test; @@ -1112,6 +1113,126 @@ BeanUtils.copyProperty(bean, "shortProperty", new Short((short) 123)); assertEquals((short) 123, bean.getShortProperty()); + } + + + /** + * Test copying a property using a nested indexed array expression, + * with and without conversions. + */ + public void testCopyPropertyNestedIndexedArray() throws Exception { + + int origArray[] = { 0, 10, 20, 30, 40 }; + int intArray[] = { 0, 0, 0 }; + bean.getNested().setIntArray(intArray); + int intChanged[] = { 0, 0, 0 }; + + // No conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", new Integer(1)); + checkIntArray(bean.getIntArray(), origArray); + intChanged[1] = 1; + checkIntArray(bean.getNested().getIntArray(), intChanged); + + // Widening conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", new Byte((byte) 2)); + checkIntArray(bean.getIntArray(), origArray); + intChanged[1] = 2; + checkIntArray(bean.getNested().getIntArray(), intChanged); + + // Narrowing conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", new Long((long) 3)); + checkIntArray(bean.getIntArray(), origArray); + intChanged[1] = 3; + checkIntArray(bean.getNested().getIntArray(), intChanged); + + // String conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", "4"); + checkIntArray(bean.getIntArray(), origArray); + intChanged[1] = 4; + checkIntArray(bean.getNested().getIntArray(), intChanged); + + } + + + /** + * Test copying a property using a nested mapped map property. + */ + public void testCopyPropertyNestedMappedMap() throws Exception { + + Map origMap = new HashMap(); + origMap.put("First Key", "First Value"); + origMap.put("Second Key", "Second Value"); + Map changedMap = new HashMap(); + changedMap.put("First Key", "First Value"); + changedMap.put("Second Key", "Second Value"); + + // No conversion required + BeanUtils.copyProperty(bean, "nested.mapProperty(Second Key)", + "New Second Value"); + checkMap(bean.getMapProperty(), origMap); + changedMap.put("Second Key", "New Second Value"); + checkMap(bean.getNested().getMapProperty(), changedMap); + + } + + + /** + * Test copying a property using a nested simple expression, with and + * without conversions. + */ + public void testCopyPropertyNestedSimple() throws Exception { + + bean.setIntProperty(0); + bean.getNested().setIntProperty(0); + + // No conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", new Integer(1)); + assertNotNull(bean.getNested()); + assertEquals(0, bean.getIntProperty()); + assertEquals(1, bean.getNested().getIntProperty()); + + // Widening conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", new Byte((byte) 2)); + assertNotNull(bean.getNested()); + assertEquals(0, bean.getIntProperty()); + assertEquals(2, bean.getNested().getIntProperty()); + + // Narrowing conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", new Long((long) 3)); + assertNotNull(bean.getNested()); + assertEquals(0, bean.getIntProperty()); + assertEquals(3, bean.getNested().getIntProperty()); + + // String conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", "4"); + assertNotNull(bean.getNested()); + assertEquals(0, bean.getIntProperty()); + assertEquals(4, bean.getNested().getIntProperty()); + + } + + + // Ensure that the actual int[] matches the expected int[] + protected void checkIntArray(int actual[], int expected[]) { + assertNotNull("actual array not null", actual); + assertEquals("actual array length", expected.length, actual.length); + for (int i = 0; i < actual.length; i++) { + assertEquals("actual array value[" + i + "]", + expected[i], actual[i]); + } + } + + + // Ensure that the actual Map matches the expected Map + protected void checkMap(Map actual, Map expected) { + assertNotNull("actual map not null", actual); + assertEquals("actual map size", expected.size(), actual.size()); + Iterator keys = expected.keySet().iterator(); + while (keys.hasNext()) { + Object key = keys.next(); + assertEquals("actual map value(" + key + ")", + expected.get(key), actual.get(key)); + } } 1.17 +131 -4 jakarta-commons/beanutils/src/test/org/apache/commons/beanutils/DynaBeanUtilsTestCase.java Index: DynaBeanUtilsTestCase.java =================================================================== RCS file: /home/cvs/jakarta-commons/beanutils/src/test/org/apache/commons/beanutils/DynaBeanUtilsTestCase.java,v retrieving revision 1.16 retrieving revision 1.17 diff -u -r1.16 -r1.17 --- DynaBeanUtilsTestCase.java 1 Feb 2003 08:14:33 -0000 1.16 +++ DynaBeanUtilsTestCase.java 4 Feb 2003 07:28:14 -0000 1.17 @@ -65,6 +65,7 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -113,6 +114,7 @@ "intProperty", "listIndexed", "longProperty", + "mapProperty", "mappedProperty", "mappedIntProperty", "nested", @@ -173,6 +175,10 @@ listIndexed.add("String 4"); bean.set("listIndexed", listIndexed); bean.set("longProperty", new Long((long) 321)); + HashMap mapProperty = new HashMap(); + mapProperty.put("First Key", "First Value"); + mapProperty.put("Second Key", "Second Value"); + bean.set("mapProperty", mapProperty); HashMap mappedProperty = new HashMap(); mappedProperty.put("First Key", "First Value"); mappedProperty.put("Second Key", "Second Value"); @@ -1037,9 +1043,129 @@ } + /** + * Test copying a property using a nested indexed array expression, + * with and without conversions. + */ + public void testCopyPropertyNestedIndexedArray() throws Exception { + + int origArray[] = { 0, 10, 20, 30, 40}; + int intArray[] = { 0, 0, 0 }; + ((TestBean) bean.get("nested")).setIntArray(intArray); + int intChanged[] = { 0, 0, 0 }; + + // No conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", new Integer(1)); + checkIntArray((int[]) bean.get("intArray"), origArray); + intChanged[1] = 1; + checkIntArray(((TestBean) bean.get("nested")).getIntArray(), + intChanged); + + // Widening conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", new Byte((byte) 2)); + checkIntArray((int[]) bean.get("intArray"), origArray); + intChanged[1] = 2; + checkIntArray(((TestBean) bean.get("nested")).getIntArray(), + intChanged); + + // Narrowing conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", new Long((long) 3)); + checkIntArray((int[]) bean.get("intArray"), origArray); + intChanged[1] = 3; + checkIntArray(((TestBean) bean.get("nested")).getIntArray(), + intChanged); + + // String conversion required + BeanUtils.copyProperty(bean, "nested.intArray[1]", "4"); + checkIntArray((int[]) bean.get("intArray"), origArray); + intChanged[1] = 4; + checkIntArray(((TestBean) bean.get("nested")).getIntArray(), + intChanged); + + } + + + /** + * Test copying a property using a nested mapped map property. + */ + public void testCopyPropertyNestedMappedMap() throws Exception { + + Map origMap = new HashMap(); + origMap.put("First Key", "First Value"); + origMap.put("Second Key", "Second Value"); + Map changedMap = new HashMap(); + changedMap.put("First Key", "First Value"); + changedMap.put("Second Key", "Second Value"); + + // No conversion required + BeanUtils.copyProperty(bean, "nested.mapProperty(Second Key)", + "New Second Value"); + checkMap((Map) bean.get("mapProperty"), origMap); + changedMap.put("Second Key", "New Second Value"); + checkMap(((TestBean) bean.get("nested")).getMapProperty(), changedMap); + + } + + + /** + * Test copying a property using a nested simple expression, with and + * without conversions. + */ + public void testCopyPropertyNestedSimple() throws Exception { + + bean.set("intProperty", new Integer(0)); + nested.setIntProperty(0); + + // No conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", new Integer(1)); + assertEquals(0, ((Integer) bean.get("intProperty")).intValue()); + assertEquals(1, nested.getIntProperty()); + + // Widening conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", new Byte((byte) 2)); + assertEquals(0, ((Integer) bean.get("intProperty")).intValue()); + assertEquals(2, nested.getIntProperty()); + + // Narrowing conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", new Long((long) 3)); + assertEquals(0, ((Integer) bean.get("intProperty")).intValue()); + assertEquals(3, nested.getIntProperty()); + + // String conversion required + BeanUtils.copyProperty(bean, "nested.intProperty", "4"); + assertEquals(0, ((Integer) bean.get("intProperty")).intValue()); + assertEquals(4, nested.getIntProperty()); + + } + + // ------------------------------------------------------ Protected Methods + // Ensure that the nested intArray matches the specified values + protected void checkIntArray(int actual[], int expected[]) { + assertNotNull("actual array not null", actual); + assertEquals("actual array length", expected.length, actual.length); + for (int i = 0; i < actual.length; i++) { + assertEquals("actual array value[" + i + "]", + expected[i], actual[i]); + } + } + + + // Ensure that the actual Map matches the expected Map + protected void checkMap(Map actual, Map expected) { + assertNotNull("actual map not null", actual); + assertEquals("actual map size", expected.size(), actual.size()); + Iterator keys = expected.keySet().iterator(); + while (keys.hasNext()) { + Object key = keys.next(); + assertEquals("actual map value(" + key + ")", + expected.get(key), actual.get(key)); + } + } + + /** * Create and return aDynaClass
instance for our test
* DynaBean
.
@@ -1063,6 +1189,7 @@
new DynaProperty("intProperty", Integer.TYPE),
new DynaProperty("listIndexed", List.class),
new DynaProperty("longProperty", Long.TYPE),
+ new DynaProperty("mapProperty", Map.class),
new DynaProperty("mappedProperty", Map.class),
new DynaProperty("mappedIntProperty", Map.class),
new DynaProperty("nested", TestBean.class),
---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org