diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java index dddcbca0b..3d8b0e8c0 100644 --- a/src/main/java/org/apache/commons/beanutils2/BeanUtils.java +++ b/src/main/java/org/apache/commons/beanutils2/BeanUtils.java @@ -110,6 +110,23 @@ public static void copyProperty(final Object bean, final String name, final Obje BeanUtilsBean.getInstance().copyProperty(bean, name, value); } + /** + *
+ * Copy non-null 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 + * @throws IllegalAccessException if the caller does not have access to the property accessor method + * @throws InvocationTargetException if the property accessor method throws an exception + * @see BeanUtilsBean#copyNonNullProperties + */ + public static void copyNonNullProperties(final Object dest, final Object orig) throws IllegalAccessException, InvocationTargetException { + BeanUtilsBean.getInstance().copyNonNullProperties(dest, orig); + } + /** * Create a cache. * diff --git a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java index 63a028422..895b73b34 100644 --- a/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java +++ b/src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java @@ -921,4 +921,28 @@ public void setProperty(final Object bean, String name, final Object value) thro throw new InvocationTargetException(e, "Cannot set " + propName); } } + + + public void copyNonNullProperties(Object dest, Object orig) { + Objects.requireNonNull(dest, "dest"); + Objects.requireNonNull(orig, "orig"); + if (LOG.isDebugEnabled()) { + LOG.debug("BeanUtils.copyNonNullProperties(" + dest + ", " + orig + ")"); + } + try { + final PropertyDescriptor[] origDescriptors = getPropertyUtils().getPropertyDescriptors(orig); + for (PropertyDescriptor origDescriptor : origDescriptors) { + final String name = origDescriptor.getName(); + if ("class".equals(name)) continue; + + if (getPropertyUtils().isReadable(orig, name) && getPropertyUtils().isWriteable(dest, name)) { + final Object value = getPropertyUtils().getSimpleProperty(orig, name); + if (null == value) continue; + setProperty(dest, name, value); + } + } + } catch (final IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + // NoSuchMethodException occurs because the property is primitive and doesn't have a getter + } + } } diff --git a/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTest.java b/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTest.java index b549499c8..5d38cacbc 100644 --- a/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTest.java +++ b/src/test/java/org/apache/commons/beanutils2/BeanUtilsBeanTest.java @@ -17,6 +17,7 @@ package org.apache.commons.beanutils2; +import static org.apache.commons.beanutils2.BeanUtils.copyNonNullProperties; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -24,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.InvocationTargetException; import java.util.Calendar; import java.util.HashMap; import java.util.Locale; @@ -291,7 +293,7 @@ public void testCopyPropertiesStandard() throws Exception { orig.setLongProperty(3333); orig.setShortProperty((short) 33); orig.setStringArray(new String[] { "New 0", "New 1" }); - orig.setStringProperty("Custom string"); + orig.setStringProperty(null); // Copy the origin bean to our destination test bean BeanUtils.copyProperties(bean, orig); @@ -326,6 +328,29 @@ public void testCopyPropertiesStandard() throws Exception { } + /** + * Test the copyNonNullProperties from a standard JavaBean. + */ + @Test + public void testNonNullPropertiesStandard() throws InvocationTargetException, IllegalAccessException { + final TestBean orig = new TestBean(); + orig.setBooleanProperty(false); + orig.setByteProperty((byte) 111); + orig.setDoubleProperty(333.33); + orig.setDupProperty(new String[] { "New 0", "New 1", "New 2" }); + orig.setIntArray(new int[] { 100, 200, 300 }); + orig.setIntProperty(333); + orig.setLongProperty(3333); + orig.setShortProperty((short) 33); + orig.setStringArray(new String[] { "New 0", "New 1" }); + orig.setStringProperty(null); + + copyNonNullProperties(bean, orig); + + final String stringProperty=bean.getStringProperty(); + assertNotNull(stringProperty, "copy without null properties"); + } + /** * Test narrowing and widening conversions on byte. */ diff --git a/src/test/java/org/apache/commons/beanutils2/TestBean.java b/src/test/java/org/apache/commons/beanutils2/TestBean.java index 1377deae3..e9893e9e6 100644 --- a/src/test/java/org/apache/commons/beanutils2/TestBean.java +++ b/src/test/java/org/apache/commons/beanutils2/TestBean.java @@ -17,10 +17,7 @@ package org.apache.commons.beanutils2; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** * General purpose test bean for JUnit tests for the "beanutils" component. @@ -574,4 +571,39 @@ public void setWriteOnlyProperty(final String writeOnlyProperty) { this.writeOnlyProperty = writeOnlyProperty; } + @Override + public String toString() { + return "TestBean{" + + "booleanProperty=" + booleanProperty + + ", booleanSecond=" + booleanSecond + + ", byteProperty=" + byteProperty + + ", dateProperty=" + dateProperty + + ", dateArrayProperty=" + Arrays.toString(dateArrayProperty) + + ", doubleProperty=" + doubleProperty + + ", dupProperty=" + Arrays.toString(dupProperty) + + ", floatProperty=" + floatProperty + + ", intArray=" + Arrays.toString(intArray) + + ", intIndexed=" + Arrays.toString(intIndexed) + + ", intProperty=" + intProperty + + ", listIndexed=" + listIndexed + + ", longProperty=" + longProperty + + ", mapProperty=" + mapProperty + + ", mappedObjects=" + mappedObjects + + ", mappedProperty=" + mappedProperty + + ", mappedIntProperty=" + mappedIntProperty + + ", nested=" + nested + + ", anotherNested=" + anotherNested + + ", nestedDynaBean=" + nestedDynaBean + + ", mappedNested=" + mappedNested + + ", nullProperty='" + nullProperty + '\'' + + ", readOnlyProperty='" + readOnlyProperty + '\'' + + ", shortProperty=" + shortProperty + + ", stringArray=" + Arrays.toString(stringArray) + + ", stringIndexed=" + Arrays.toString(stringIndexed) + + ", string2dArray=" + Arrays.toString(string2dArray) + + ", stringProperty='" + stringProperty + '\'' + + ", writeOnlyProperty='" + writeOnlyProperty + '\'' + + ", invalidBoolean=" + invalidBoolean + + '}'; + } }