diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java index 14e79764b..84ecb7951 100644 --- a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java +++ b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java @@ -88,12 +88,12 @@ public static Object cloneBean(final Object bean) * converter has not been registered. * @throws InvocationTargetException if the property accessor method * throws an exception - * @see BeanUtilsBean#copyProperties + * @see BeanUtilsBean#copyProperties(Object, Object, String...) */ public static void copyProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException { - BeanUtilsBean.getInstance().copyProperties(dest, orig); + copyProperties(dest, orig, (String) null); } @@ -120,6 +120,34 @@ public static void copyProperty(final Object bean, final String name, final Obje } + /** + *

Copy property values from the origin bean to the destination bean + * for all cases where the property names are the same

+ * + *

For more details see {@code BeanUtilsBean}.

+ * + * @param dest Destination bean whose properties are modified + * @param orig Origin bean whose properties are retrieved + * @param ignore list of properties to ignore, may be null + * + * @throws IllegalAccessException if the caller does not have + * access to the property accessor method + * @throws IllegalArgumentException if the {@code dest} or + * {@code orig argument is null or if the dest} + * property type is different from the source type and the relevant + * converter has not been registered. + * @throws InvocationTargetException if the property accessor method + * throws an exception + * @see BeanUtilsBean#copyProperties(Object, Object, String...) + * @since 2.0 + */ + public static void copyProperties(final Object dest, final Object orig, final String... ignore) + throws IllegalAccessException, InvocationTargetException { + + BeanUtilsBean.getInstance().copyProperties(dest, orig, ignore); + } + + /** * Create a cache. * @param the key type of the cache diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java index ca9ff0370..9e58ca757 100644 --- a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java @@ -22,6 +22,7 @@ import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -288,7 +289,56 @@ private Object convertForCopy(final Object value, final Class type) { * throws an exception */ public void copyProperties(final Object dest, final Object orig) - throws IllegalAccessException, InvocationTargetException { + throws IllegalAccessException, InvocationTargetException { + copyProperties(dest, orig, (String) null); + } + + /** + *

Copy property values from the origin bean to the destination bean + * for all cases where the property names are the same. For each + * property, a conversion is attempted as necessary. All combinations of + * standard JavaBeans and DynaBeans as origin and destination are + * supported. Properties that exist in the origin bean, but do not exist + * in the destination bean (or are read-only in the destination bean) are + * silently ignored.

+ * + *

If the origin "bean" is actually a {@code Map}, it is assumed + * to contain String-valued simple property names as the keys, pointing at + * the corresponding property values that will be converted (if necessary) + * and set in the destination bean. Note that this method + * is intended to perform a "shallow copy" of the properties and so complex + * properties (for example, nested ones) will not be copied.

+ * + *

This method differs from {@code populate()}, which + * was primarily designed for populating JavaBeans from the map of request + * parameters retrieved on an HTTP request, is that no scalar->indexed + * or indexed->scalar manipulations are performed. If the origin property + * is indexed, the destination property must be also.

+ * + *

If you know that no type conversions are required, the + * {@code copyProperties()} method in {@link PropertyUtils} will + * execute faster than this method.

+ * + *

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.

+ * + * @param dest Destination bean whose properties are modified + * @param orig Origin bean whose properties are retrieved + * @param ignore list of properties to ignore, may be null + * + * @throws IllegalAccessException if the caller does not have + * access to the property accessor method + * @throws IllegalArgumentException if the {@code dest} or + * {@code orig
argument is null or if the dest} + * property type is different from the source type and the relevant + * converter has not been registered. + * @throws InvocationTargetException if the property accessor method + * throws an exception + * @since 2.0 + */ + public void copyProperties(final Object dest, final Object orig, final String... ignore) + throws IllegalAccessException, InvocationTargetException { // Validate existence of the specified beans if (dest == null) { throw new IllegalArgumentException @@ -311,7 +361,7 @@ public void copyProperties(final Object dest, final Object orig) // Need to check isReadable() for WrapDynaBean // (see Jira issue# BEANUTILS-61) if (getPropertyUtils().isReadable(orig, name) && - getPropertyUtils().isWriteable(dest, name)) { + getPropertyUtils().isWriteable(dest, name) && !Arrays.asList(ignore).contains(name)) { final Object value = ((DynaBean) orig).get(name); copyProperty(dest, name, value); } @@ -323,7 +373,7 @@ public void copyProperties(final Object dest, final Object orig) Map propMap = (Map) orig; for (final Map.Entry entry : propMap.entrySet()) { final String k = entry.getKey(); - if (getPropertyUtils().isWriteable(dest, k)) { + if (getPropertyUtils().isWriteable(dest, k) && !Arrays.asList(ignore).contains(k)) { copyProperty(dest, k, entry.getValue()); } } @@ -336,7 +386,7 @@ public void copyProperties(final Object dest, final Object orig) continue; // No point in trying to set an object's class } if (getPropertyUtils().isReadable(orig, name) && - getPropertyUtils().isWriteable(dest, name)) { + getPropertyUtils().isWriteable(dest, name) && !Arrays.asList(ignore).contains(name)) { try { final Object value = getPropertyUtils().getSimpleProperty(orig, name); diff --git a/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTestCase.java b/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTestCase.java index e4a29a907..04f56ee95 100644 --- a/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTestCase.java +++ b/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTestCase.java @@ -377,6 +377,43 @@ public void testCopyPropertiesStandard() { } + /** + * Test the copyProperties() method from a standard JavaBean. + */ + public void testCopyPropertiesStandardIgnore() { + // Set up an origin bean with customized properties + final TestBean orig = new TestBean(); + orig.setBooleanProperty(false); + orig.setStringProperty("Ignore Property"); + + + // Copy the origin bean to our destination test bean + try { + BeanUtils.copyProperties(bean, orig, "stringProperty"); + } catch (final Exception e) { + fail("Threw exception: " + e); + } + assertEquals("Not Copied array property", + "This is a string", + bean.getStringProperty()); + + final Map map = new HashMap<>(); + map.put("booleanProperty", "false"); + map.put("byteProperty", "111"); + map.put("stringProperty", "Ignore Property"); + + try { + BeanUtils.copyProperties(bean, map, "stringProperty"); + } catch (final Throwable t) { + fail("Threw " + t.toString()); + } + + assertEquals("Not Copied array property", + "This is a string", + bean.getStringProperty()); + + } + /** * Test narrowing and widening conversions on byte. */ diff --git a/src/test/java/org/apache/commons/beanutils2/BeanUtilsBenchCase.java b/src/test/java/org/apache/commons/beanutils2/BeanUtilsBenchCase.java index 6cf346fb8..3e907baef 100644 --- a/src/test/java/org/apache/commons/beanutils2/BeanUtilsBenchCase.java +++ b/src/test/java/org/apache/commons/beanutils2/BeanUtilsBenchCase.java @@ -163,6 +163,17 @@ public void testCopyPropertiesBean() throws Exception { stopMillis = System.currentTimeMillis(); System.err.println("BU.copyProperties(dyna,bean), count=" + counter + ", time=" + (stopMillis - startMillis)); + final String[] ignore = new String[] { "booleanProperty", "floatProperty", null, ""}; + + startMillis = System.currentTimeMillis(); + for (long i = 0; i < counter; i++) { + bu.copyProperties(outDyna, inBean, ignore); + } + stopMillis = System.currentTimeMillis(); + + System.err.println("BU.copyProperties(dyna,bean, ignore), count=" + counter + + ", time=" + (stopMillis - startMillis)); + } // Time copyProperties() from a DynaBean