|
|
|
|
@ -34,6 +34,7 @@ import java.util.Locale;
@@ -34,6 +34,7 @@ import java.util.Locale;
|
|
|
|
|
import java.util.UUID; |
|
|
|
|
|
|
|
|
|
import org.jspecify.annotations.Nullable; |
|
|
|
|
import org.junit.jupiter.api.Nested; |
|
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
|
import org.junit.jupiter.params.ParameterizedTest; |
|
|
|
|
import org.junit.jupiter.params.provider.ValueSource; |
|
|
|
|
@ -67,14 +68,14 @@ class BeanUtilsTests {
@@ -67,14 +68,14 @@ class BeanUtilsTests {
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void instantiateClassGivenInterface() { |
|
|
|
|
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> |
|
|
|
|
BeanUtils.instantiateClass(List.class)); |
|
|
|
|
assertThatExceptionOfType(FatalBeanException.class) |
|
|
|
|
.isThrownBy(() -> BeanUtils.instantiateClass(List.class)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void instantiateClassGivenClassWithoutDefaultConstructor() { |
|
|
|
|
assertThatExceptionOfType(FatalBeanException.class).isThrownBy(() -> |
|
|
|
|
BeanUtils.instantiateClass(CustomDateEditor.class)); |
|
|
|
|
assertThatExceptionOfType(FatalBeanException.class) |
|
|
|
|
.isThrownBy(() -> BeanUtils.instantiateClass(CustomDateEditor.class)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-22531
|
|
|
|
|
@ -91,16 +92,16 @@ class BeanUtilsTests {
@@ -91,16 +92,16 @@ class BeanUtilsTests {
|
|
|
|
|
void instantiateClassWithFewerArgsThanParameters() throws NoSuchMethodException { |
|
|
|
|
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor(); |
|
|
|
|
|
|
|
|
|
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() -> |
|
|
|
|
BeanUtils.instantiateClass(constructor, null, null, "foo")); |
|
|
|
|
assertThatExceptionOfType(BeanInstantiationException.class) |
|
|
|
|
.isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, "foo")); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-22531
|
|
|
|
|
void instantiateClassWithMoreArgsThanParameters() throws NoSuchMethodException { |
|
|
|
|
Constructor<BeanWithPrimitiveTypes> constructor = getBeanWithPrimitiveTypesConstructor(); |
|
|
|
|
|
|
|
|
|
assertThatExceptionOfType(BeanInstantiationException.class).isThrownBy(() -> |
|
|
|
|
BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null)); |
|
|
|
|
assertThatExceptionOfType(BeanInstantiationException.class) |
|
|
|
|
.isThrownBy(() -> BeanUtils.instantiateClass(constructor, null, null, null, null, null, null, null, null, "foo", null)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-22531, gh-27390
|
|
|
|
|
@ -157,267 +158,6 @@ class BeanUtilsTests {
@@ -157,267 +158,6 @@ class BeanUtilsTests {
|
|
|
|
|
assertThat(BeanUtils.findEditorByConvention(Resource.class).getClass()).isEqualTo(ResourceEditor.class); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyProperties() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
tb.setName("rod"); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("touchy"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
assertThat(tb2.getName()).as("Name empty").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
BeanUtils.copyProperties(tb, tb2); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithDifferentTypes1() throws Exception { |
|
|
|
|
DerivedTestBean tb = new DerivedTestBean(); |
|
|
|
|
tb.setName("rod"); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("touchy"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
assertThat(tb2.getName()).as("Name empty").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
BeanUtils.copyProperties(tb, tb2); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithDifferentTypes2() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
tb.setName("rod"); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("touchy"); |
|
|
|
|
DerivedTestBean tb2 = new DerivedTestBean(); |
|
|
|
|
assertThat(tb2.getName()).as("Name empty").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
BeanUtils.copyProperties(tb, tb2); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code Integer} can be copied to {@code Number}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesFromSubTypeToSuperType() { |
|
|
|
|
IntegerHolder integerHolder = new IntegerHolder(); |
|
|
|
|
integerHolder.setNumber(42); |
|
|
|
|
NumberHolder numberHolder = new NumberHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerHolder, numberHolder); |
|
|
|
|
assertThat(integerHolder.getNumber()).isEqualTo(42); |
|
|
|
|
assertThat(numberHolder.getNumber()).isEqualTo(42); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can be copied to {@code List<Integer>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() { |
|
|
|
|
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); |
|
|
|
|
integerListHolder1.getList().add(42); |
|
|
|
|
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder1, integerListHolder2); |
|
|
|
|
assertThat(integerListHolder1.getList()).containsExactly(42); |
|
|
|
|
assertThat(integerListHolder2.getList()).containsExactly(42); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<?>} can be copied to {@code List<?>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() { |
|
|
|
|
List<?> list = List.of("foo", 42); |
|
|
|
|
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1(); |
|
|
|
|
wildcardListHolder1.setList(list); |
|
|
|
|
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); |
|
|
|
|
assertThat(wildcardListHolder2.getList()).isEmpty(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2); |
|
|
|
|
assertThat(wildcardListHolder1.getList()).isEqualTo(list); |
|
|
|
|
assertThat(wildcardListHolder2.getList()).isEqualTo(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can be copied to {@code List<?>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() { |
|
|
|
|
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); |
|
|
|
|
integerListHolder1.getList().add(42); |
|
|
|
|
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2); |
|
|
|
|
assertThat(integerListHolder1.getList()).containsExactly(42); |
|
|
|
|
assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can be copied to {@code List<? extends Number>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() { |
|
|
|
|
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); |
|
|
|
|
integerListHolder1.getList().add(42); |
|
|
|
|
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder1, numberListHolder); |
|
|
|
|
assertThat(integerListHolder1.getList()).containsExactly(42); |
|
|
|
|
assertThat(numberListHolder.getList()).isEqualTo(List.of(42)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code Number} can NOT be copied to {@code Integer}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() { |
|
|
|
|
NumberHolder numberHolder = new NumberHolder(); |
|
|
|
|
numberHolder.setNumber(42); |
|
|
|
|
IntegerHolder integerHolder = new IntegerHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(numberHolder, integerHolder); |
|
|
|
|
assertThat(numberHolder.getNumber()).isEqualTo(42); |
|
|
|
|
assertThat(integerHolder.getNumber()).isNull(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can NOT be copied to {@code List<Long>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesDoesNotHonorGenericTypeMismatches() { |
|
|
|
|
IntegerListHolder1 integerListHolder = new IntegerListHolder1(); |
|
|
|
|
integerListHolder.getList().add(42); |
|
|
|
|
LongListHolder longListHolder = new LongListHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder, longListHolder); |
|
|
|
|
assertThat(integerListHolder.getList()).containsExactly(42); |
|
|
|
|
assertThat(longListHolder.getList()).isEmpty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can NOT be copied to {@code List<Number>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() { |
|
|
|
|
IntegerListHolder1 integerListHolder = new IntegerListHolder1(); |
|
|
|
|
integerListHolder.getList().add(42); |
|
|
|
|
NumberListHolder numberListHolder = new NumberListHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder, numberListHolder); |
|
|
|
|
assertThat(integerListHolder.getList()).containsExactly(42); |
|
|
|
|
assertThat(numberListHolder.getList()).isEmpty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-26531
|
|
|
|
|
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception { |
|
|
|
|
Order original = new Order("test", List.of("foo", "bar")); |
|
|
|
|
|
|
|
|
|
// Create a Proxy that loses the generic type information for the getLineItems() method.
|
|
|
|
|
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), |
|
|
|
|
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original)); |
|
|
|
|
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString()) |
|
|
|
|
.contains("java.util.List<java.lang.String>"); |
|
|
|
|
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) |
|
|
|
|
.contains("java.util.List") |
|
|
|
|
.doesNotContain("<java.lang.String>"); |
|
|
|
|
|
|
|
|
|
// Ensure that our custom Proxy works as expected.
|
|
|
|
|
assertThat(proxy.getId()).isEqualTo("test"); |
|
|
|
|
assertThat(proxy.getLineItems()).containsExactly("foo", "bar"); |
|
|
|
|
|
|
|
|
|
// Copy from proxy to target.
|
|
|
|
|
Order target = new Order(); |
|
|
|
|
BeanUtils.copyProperties(proxy, target); |
|
|
|
|
assertThat(target.getId()).isEqualTo("test"); |
|
|
|
|
assertThat(target.getLineItems()).containsExactly("foo", "bar"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-32888
|
|
|
|
|
public void copyPropertiesWithGenericCglibClass() { |
|
|
|
|
Enhancer enhancer = new Enhancer(); |
|
|
|
|
enhancer.setSuperclass(User.class); |
|
|
|
|
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args)); |
|
|
|
|
User user = (User) enhancer.create(); |
|
|
|
|
user.setId(1); |
|
|
|
|
user.setName("proxy"); |
|
|
|
|
user.setAddress("addr"); |
|
|
|
|
|
|
|
|
|
User target = new User(); |
|
|
|
|
BeanUtils.copyProperties(user, target); |
|
|
|
|
assertThat(target.getId()).isEqualTo(user.getId()); |
|
|
|
|
assertThat(target.getName()).isEqualTo(user.getName()); |
|
|
|
|
assertThat(target.getAddress()).isEqualTo(user.getAddress()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithEditable() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
assertThat(tb.getName()).as("Name empty").isNull(); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("bla"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
tb2.setName("rod"); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
|
|
|
|
|
// "touchy" should not be copied: it's not defined in ITestBean
|
|
|
|
|
BeanUtils.copyProperties(tb, tb2, ITestBean.class); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithIgnore() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
assertThat(tb.getName()).as("Name empty").isNull(); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("bla"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
tb2.setName("rod"); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
|
|
|
|
|
// "spouse", "touchy", "age" should not be copied
|
|
|
|
|
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age"); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithIgnoredNonExistingProperty() { |
|
|
|
|
NameAndSpecialProperty source = new NameAndSpecialProperty(); |
|
|
|
|
source.setName("name"); |
|
|
|
|
TestBean target = new TestBean(); |
|
|
|
|
BeanUtils.copyProperties(source, target, "specialProperty"); |
|
|
|
|
assertThat(target.getName()).isEqualTo("name"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithInvalidProperty() { |
|
|
|
|
InvalidProperty source = new InvalidProperty(); |
|
|
|
|
source.setName("name"); |
|
|
|
|
source.setFlag1(true); |
|
|
|
|
source.setFlag2(true); |
|
|
|
|
InvalidProperty target = new InvalidProperty(); |
|
|
|
|
BeanUtils.copyProperties(source, target); |
|
|
|
|
assertThat(target.getName()).isEqualTo("name"); |
|
|
|
|
assertThat((boolean) target.getFlag1()).isTrue(); |
|
|
|
|
assertThat(target.getFlag2()).isTrue(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveSimpleSignature() throws Exception { |
|
|
|
|
Method desiredMethod = MethodSignatureBean.class.getMethod("doSomething"); |
|
|
|
|
@ -427,14 +167,14 @@ class BeanUtilsTests {
@@ -427,14 +167,14 @@ class BeanUtilsTests {
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveInvalidSignatureEndParen() { |
|
|
|
|
assertThatIllegalArgumentException().isThrownBy(() -> |
|
|
|
|
BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class)); |
|
|
|
|
assertThatIllegalArgumentException() |
|
|
|
|
.isThrownBy(() -> BeanUtils.resolveSignature("doSomething(", MethodSignatureBean.class)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void resolveInvalidSignatureStartParen() { |
|
|
|
|
assertThatIllegalArgumentException().isThrownBy(() -> |
|
|
|
|
BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class)); |
|
|
|
|
assertThatIllegalArgumentException() |
|
|
|
|
.isThrownBy(() -> BeanUtils.resolveSignature("doSomething)", MethodSignatureBean.class)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
@ -538,6 +278,272 @@ class BeanUtilsTests {
@@ -538,6 +278,272 @@ class BeanUtilsTests {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Nested |
|
|
|
|
class CopyPropertiesTests { |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyProperties() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
tb.setName("rod"); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("touchy"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
assertThat(tb2.getName()).as("Name empty").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
BeanUtils.copyProperties(tb, tb2); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithDifferentTypes1() throws Exception { |
|
|
|
|
DerivedTestBean tb = new DerivedTestBean(); |
|
|
|
|
tb.setName("rod"); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("touchy"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
assertThat(tb2.getName()).as("Name empty").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
BeanUtils.copyProperties(tb, tb2); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithDifferentTypes2() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
tb.setName("rod"); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("touchy"); |
|
|
|
|
DerivedTestBean tb2 = new DerivedTestBean(); |
|
|
|
|
assertThat(tb2.getName()).as("Name empty").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
BeanUtils.copyProperties(tb, tb2); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isEqualTo(tb.getName()); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(tb.getAge()); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy copied").isEqualTo(tb.getTouchy()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code Integer} can be copied to {@code Number}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesFromSubTypeToSuperType() { |
|
|
|
|
IntegerHolder integerHolder = new IntegerHolder(); |
|
|
|
|
integerHolder.setNumber(42); |
|
|
|
|
NumberHolder numberHolder = new NumberHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerHolder, numberHolder); |
|
|
|
|
assertThat(integerHolder.getNumber()).isEqualTo(42); |
|
|
|
|
assertThat(numberHolder.getNumber()).isEqualTo(42); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can be copied to {@code List<Integer>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() { |
|
|
|
|
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); |
|
|
|
|
integerListHolder1.getList().add(42); |
|
|
|
|
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder1, integerListHolder2); |
|
|
|
|
assertThat(integerListHolder1.getList()).containsExactly(42); |
|
|
|
|
assertThat(integerListHolder2.getList()).containsExactly(42); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<?>} can be copied to {@code List<?>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() { |
|
|
|
|
List<?> list = List.of("foo", 42); |
|
|
|
|
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1(); |
|
|
|
|
wildcardListHolder1.setList(list); |
|
|
|
|
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); |
|
|
|
|
assertThat(wildcardListHolder2.getList()).isEmpty(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2); |
|
|
|
|
assertThat(wildcardListHolder1.getList()).isEqualTo(list); |
|
|
|
|
assertThat(wildcardListHolder2.getList()).isEqualTo(list); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can be copied to {@code List<?>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() { |
|
|
|
|
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); |
|
|
|
|
integerListHolder1.getList().add(42); |
|
|
|
|
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2); |
|
|
|
|
assertThat(integerListHolder1.getList()).containsExactly(42); |
|
|
|
|
assertThat(wildcardListHolder2.getList()).isEqualTo(List.of(42)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can be copied to {@code List<? extends Number>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() { |
|
|
|
|
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1(); |
|
|
|
|
integerListHolder1.getList().add(42); |
|
|
|
|
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder1, numberListHolder); |
|
|
|
|
assertThat(integerListHolder1.getList()).containsExactly(42); |
|
|
|
|
assertThat(numberListHolder.getList()).isEqualTo(List.of(42)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code Number} can NOT be copied to {@code Integer}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesDoesNotCopyFromSuperTypeToSubType() { |
|
|
|
|
NumberHolder numberHolder = new NumberHolder(); |
|
|
|
|
numberHolder.setNumber(42); |
|
|
|
|
IntegerHolder integerHolder = new IntegerHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(numberHolder, integerHolder); |
|
|
|
|
assertThat(numberHolder.getNumber()).isEqualTo(42); |
|
|
|
|
assertThat(integerHolder.getNumber()).isNull(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can NOT be copied to {@code List<Long>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesDoesNotHonorGenericTypeMismatches() { |
|
|
|
|
IntegerListHolder1 integerListHolder = new IntegerListHolder1(); |
|
|
|
|
integerListHolder.getList().add(42); |
|
|
|
|
LongListHolder longListHolder = new LongListHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder, longListHolder); |
|
|
|
|
assertThat(integerListHolder.getList()).containsExactly(42); |
|
|
|
|
assertThat(longListHolder.getList()).isEmpty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* {@code List<Integer>} can NOT be copied to {@code List<Number>}. |
|
|
|
|
*/ |
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() { |
|
|
|
|
IntegerListHolder1 integerListHolder = new IntegerListHolder1(); |
|
|
|
|
integerListHolder.getList().add(42); |
|
|
|
|
NumberListHolder numberListHolder = new NumberListHolder(); |
|
|
|
|
|
|
|
|
|
BeanUtils.copyProperties(integerListHolder, numberListHolder); |
|
|
|
|
assertThat(integerListHolder.getList()).containsExactly(42); |
|
|
|
|
assertThat(numberListHolder.getList()).isEmpty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-26531
|
|
|
|
|
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception { |
|
|
|
|
Order original = new Order("test", List.of("foo", "bar")); |
|
|
|
|
|
|
|
|
|
// Create a Proxy that loses the generic type information for the getLineItems() method.
|
|
|
|
|
OrderSummary proxy = (OrderSummary) Proxy.newProxyInstance(getClass().getClassLoader(), |
|
|
|
|
new Class<?>[] {OrderSummary.class}, new OrderInvocationHandler(original)); |
|
|
|
|
assertThat(OrderSummary.class.getDeclaredMethod("getLineItems").toGenericString()) |
|
|
|
|
.contains("java.util.List<java.lang.String>"); |
|
|
|
|
assertThat(proxy.getClass().getDeclaredMethod("getLineItems").toGenericString()) |
|
|
|
|
.contains("java.util.List") |
|
|
|
|
.doesNotContain("<java.lang.String>"); |
|
|
|
|
|
|
|
|
|
// Ensure that our custom Proxy works as expected.
|
|
|
|
|
assertThat(proxy.getId()).isEqualTo("test"); |
|
|
|
|
assertThat(proxy.getLineItems()).containsExactly("foo", "bar"); |
|
|
|
|
|
|
|
|
|
// Copy from proxy to target.
|
|
|
|
|
Order target = new Order(); |
|
|
|
|
BeanUtils.copyProperties(proxy, target); |
|
|
|
|
assertThat(target.getId()).isEqualTo("test"); |
|
|
|
|
assertThat(target.getLineItems()).containsExactly("foo", "bar"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test // gh-32888
|
|
|
|
|
void copyPropertiesWithGenericCglibClass() { |
|
|
|
|
Enhancer enhancer = new Enhancer(); |
|
|
|
|
enhancer.setSuperclass(User.class); |
|
|
|
|
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> proxy.invokeSuper(obj, args)); |
|
|
|
|
User user = (User) enhancer.create(); |
|
|
|
|
user.setId(1); |
|
|
|
|
user.setName("proxy"); |
|
|
|
|
user.setAddress("addr"); |
|
|
|
|
|
|
|
|
|
User target = new User(); |
|
|
|
|
BeanUtils.copyProperties(user, target); |
|
|
|
|
assertThat(target.getId()).isEqualTo(user.getId()); |
|
|
|
|
assertThat(target.getName()).isEqualTo(user.getName()); |
|
|
|
|
assertThat(target.getAddress()).isEqualTo(user.getAddress()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithEditable() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
assertThat(tb.getName()).as("Name empty").isNull(); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("bla"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
tb2.setName("rod"); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
|
|
|
|
|
// "touchy" should not be copied: it's not defined in ITestBean
|
|
|
|
|
BeanUtils.copyProperties(tb, tb2, ITestBean.class); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age copied").isEqualTo(32); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithIgnore() throws Exception { |
|
|
|
|
TestBean tb = new TestBean(); |
|
|
|
|
assertThat(tb.getName()).as("Name empty").isNull(); |
|
|
|
|
tb.setAge(32); |
|
|
|
|
tb.setTouchy("bla"); |
|
|
|
|
TestBean tb2 = new TestBean(); |
|
|
|
|
tb2.setName("rod"); |
|
|
|
|
assertThat(tb2.getAge()).as("Age empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy empty").isNull(); |
|
|
|
|
|
|
|
|
|
// "spouse", "touchy", "age" should not be copied
|
|
|
|
|
BeanUtils.copyProperties(tb, tb2, "spouse", "touchy", "age"); |
|
|
|
|
assertThat(tb2.getName()).as("Name copied").isNull(); |
|
|
|
|
assertThat(tb2.getAge()).as("Age still empty").isEqualTo(0); |
|
|
|
|
assertThat(tb2.getTouchy()).as("Touchy still empty").isNull(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithIgnoredNonExistingProperty() { |
|
|
|
|
NameAndSpecialProperty source = new NameAndSpecialProperty(); |
|
|
|
|
source.setName("name"); |
|
|
|
|
TestBean target = new TestBean(); |
|
|
|
|
BeanUtils.copyProperties(source, target, "specialProperty"); |
|
|
|
|
assertThat(target.getName()).isEqualTo("name"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
void copyPropertiesWithInvalidProperty() { |
|
|
|
|
InvalidProperty source = new InvalidProperty(); |
|
|
|
|
source.setName("name"); |
|
|
|
|
source.setFlag1(true); |
|
|
|
|
source.setFlag2(true); |
|
|
|
|
InvalidProperty target = new InvalidProperty(); |
|
|
|
|
BeanUtils.copyProperties(source, target); |
|
|
|
|
assertThat(target.getName()).isEqualTo("name"); |
|
|
|
|
assertThat((boolean) target.getFlag1()).isTrue(); |
|
|
|
|
assertThat(target.getFlag2()).isTrue(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public record RecordWithMultiplePublicConstructors(String value, String name) { |
|
|
|
|
@SuppressWarnings("unused") |
|
|
|
|
public RecordWithMultiplePublicConstructors(String value) { |
|
|
|
|
|