Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BEANUTILS-525 - Enhance BeanUtils.copyProperties with Ignore Properties #174

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 30 additions & 2 deletions src/main/java/org/apache/commons/beanutils2/BeanUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}


Expand All @@ -120,6 +120,34 @@ public static void copyProperty(final Object bean, final String name, final Obje
}


/**
* <p>Copy property values from the origin bean to the destination bean
* for all cases where the property names are the same</p>
*
* <p>For more details see {@code BeanUtilsBean}.</p>
*
* @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</code> argument is null or if the <code>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 <K> the key type of the cache
Expand Down
58 changes: 54 additions & 4 deletions src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

/**
* <p>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.</p>
*
* <p>If the origin "bean" is actually a {@code Map}, it is assumed
* to contain String-valued <strong>simple</strong> property names as the keys, pointing at
* the corresponding property values that will be converted (if necessary)
* and set in the destination bean. <strong>Note</strong> 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.</p>
*
* <p>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-&gt;indexed
* or indexed-&gt;scalar manipulations are performed. If the origin property
* is indexed, the destination property must be also.</p>
*
* <p>If you know that no type conversions are required, the
* {@code copyProperties()} method in {@link PropertyUtils} will
* execute faster than this method.</p>
*
* <p><strong>FIXME</strong> - Indexed and mapped properties that do not
* have getter and setter methods for the underlying array or Map are not
* copied by this method.</p>
*
* @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</code> argument is null or if the <code>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
Expand All @@ -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);
}
Expand All @@ -323,7 +373,7 @@ public void copyProperties(final Object dest, final Object orig)
Map<String, Object> propMap = (Map<String, Object>) orig;
for (final Map.Entry<String, Object> 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());
}
}
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> 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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down