Skip to content

Commit 2ab871f

Browse files
committed
Merge branch '5.3.x'
2 parents c249b57 + 887389d commit 2ab871f

File tree

2 files changed

+209
-7
lines changed

2 files changed

+209
-7
lines changed

spring-beans/src/main/java/org/springframework/beans/BeanUtils.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -686,7 +686,25 @@ public static boolean isSimpleValueType(Class<?> type) {
686686
* from each other, as long as the properties match. Any bean properties that the
687687
* source bean exposes but the target bean does not will silently be ignored.
688688
* <p>This is just a convenience method. For more complex transfer needs,
689-
* consider using a full BeanWrapper.
689+
* consider using a full {@link BeanWrapper}.
690+
* <p>As of Spring Framework 5.3, this method honors generic type information
691+
* when matching properties in the source and target objects.
692+
* <p>The following table provides a non-exhaustive set of examples of source
693+
* and target property types that can be copied as well as source and target
694+
* property types that cannot be copied.
695+
* <table border="1">
696+
* <tr><th>source property type</th><th>target property type</th><th>copy supported</th></tr>
697+
* <tr><td>{@code Integer}</td><td>{@code Integer}</td><td>yes</td></tr>
698+
* <tr><td>{@code Integer}</td><td>{@code Number}</td><td>yes</td></tr>
699+
* <tr><td>{@code List<Integer>}</td><td>{@code List<Integer>}</td><td>yes</td></tr>
700+
* <tr><td>{@code List<?>}</td><td>{@code List<?>}</td><td>yes</td></tr>
701+
* <tr><td>{@code List<Integer>}</td><td>{@code List<?>}</td><td>yes</td></tr>
702+
* <tr><td>{@code List<Integer>}</td><td>{@code List<? extends Number>}</td><td>yes</td></tr>
703+
* <tr><td>{@code String}</td><td>{@code Integer}</td><td>no</td></tr>
704+
* <tr><td>{@code Number}</td><td>{@code Integer}</td><td>no</td></tr>
705+
* <tr><td>{@code List<Integer>}</td><td>{@code List<Long>}</td><td>no</td></tr>
706+
* <tr><td>{@code List<Integer>}</td><td>{@code List<Number>}</td><td>no</td></tr>
707+
* </table>
690708
* @param source the source bean
691709
* @param target the target bean
692710
* @throws BeansException if the copying failed
@@ -703,7 +721,10 @@ public static void copyProperties(Object source, Object target) throws BeansExce
703721
* from each other, as long as the properties match. Any bean properties that the
704722
* source bean exposes but the target bean does not will silently be ignored.
705723
* <p>This is just a convenience method. For more complex transfer needs,
706-
* consider using a full BeanWrapper.
724+
* consider using a full {@link BeanWrapper}.
725+
* <p>As of Spring Framework 5.3, this method honors generic type information
726+
* when matching properties in the source and target objects. See the
727+
* documentation for {@link #copyProperties(Object, Object)} for details.
707728
* @param source the source bean
708729
* @param target the target bean
709730
* @param editable the class (or interface) to restrict property setting to
@@ -721,7 +742,10 @@ public static void copyProperties(Object source, Object target, Class<?> editabl
721742
* from each other, as long as the properties match. Any bean properties that the
722743
* source bean exposes but the target bean does not will silently be ignored.
723744
* <p>This is just a convenience method. For more complex transfer needs,
724-
* consider using a full BeanWrapper.
745+
* consider using a full {@link BeanWrapper}.
746+
* <p>As of Spring Framework 5.3, this method honors generic type information
747+
* when matching properties in the source and target objects. See the
748+
* documentation for {@link #copyProperties(Object, Object)} for details.
725749
* @param source the source bean
726750
* @param target the target bean
727751
* @param ignoreProperties array of property names to ignore
@@ -738,7 +762,8 @@ public static void copyProperties(Object source, Object target, String... ignore
738762
* from each other, as long as the properties match. Any bean properties that the
739763
* source bean exposes but the target bean does not will silently be ignored.
740764
* <p>As of Spring Framework 5.3, this method honors generic type information
741-
* when matching properties in the source and target objects.
765+
* when matching properties in the source and target objects. See the
766+
* documentation for {@link #copyProperties(Object, Object)} for details.
742767
* @param source the source bean
743768
* @param target the target bean
744769
* @param editable the class (or interface) to restrict property setting to

spring-beans/src/test/java/org/springframework/beans/BeanUtilsTests.java

Lines changed: 179 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -203,8 +203,25 @@ void copyPropertiesWithDifferentTypes2() throws Exception {
203203
assertThat(tb2.getTouchy().equals(tb.getTouchy())).as("Touchy copied").isTrue();
204204
}
205205

206+
/**
207+
* {@code Integer} can be copied to {@code Number}.
208+
*/
206209
@Test
207-
void copyPropertiesHonorsGenericTypeMatches() {
210+
void copyPropertiesFromSubTypeToSuperType() {
211+
IntegerHolder integerHolder = new IntegerHolder();
212+
integerHolder.setNumber(42);
213+
NumberHolder numberHolder = new NumberHolder();
214+
215+
BeanUtils.copyProperties(integerHolder, numberHolder);
216+
assertThat(integerHolder.getNumber()).isEqualTo(42);
217+
assertThat(numberHolder.getNumber()).isEqualTo(42);
218+
}
219+
220+
/**
221+
* {@code List<Integer>} can be copied to {@code List<Integer>}.
222+
*/
223+
@Test
224+
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToInteger() {
208225
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
209226
integerListHolder1.getList().add(42);
210227
IntegerListHolder2 integerListHolder2 = new IntegerListHolder2();
@@ -214,6 +231,68 @@ void copyPropertiesHonorsGenericTypeMatches() {
214231
assertThat(integerListHolder2.getList()).containsOnly(42);
215232
}
216233

234+
/**
235+
* {@code List<?>} can be copied to {@code List<?>}.
236+
*/
237+
@Test
238+
void copyPropertiesHonorsGenericTypeMatchesFromWildcardToWildcard() {
239+
List<?> list = Arrays.asList("foo", 42);
240+
WildcardListHolder1 wildcardListHolder1 = new WildcardListHolder1();
241+
wildcardListHolder1.setList(list);
242+
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
243+
assertThat(wildcardListHolder2.getList()).isEmpty();
244+
245+
BeanUtils.copyProperties(wildcardListHolder1, wildcardListHolder2);
246+
assertThat(wildcardListHolder1.getList()).isEqualTo(list);
247+
assertThat(wildcardListHolder2.getList()).isEqualTo(list);
248+
}
249+
250+
/**
251+
* {@code List<Integer>} can be copied to {@code List<?>}.
252+
*/
253+
@Test
254+
void copyPropertiesHonorsGenericTypeMatchesFromIntegerToWildcard() {
255+
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
256+
integerListHolder1.getList().add(42);
257+
WildcardListHolder2 wildcardListHolder2 = new WildcardListHolder2();
258+
259+
BeanUtils.copyProperties(integerListHolder1, wildcardListHolder2);
260+
assertThat(integerListHolder1.getList()).containsOnly(42);
261+
assertThat(wildcardListHolder2.getList()).isEqualTo(Arrays.asList(42));
262+
}
263+
264+
/**
265+
* {@code List<Integer>} can be copied to {@code List<? extends Number>}.
266+
*/
267+
@Test
268+
void copyPropertiesHonorsGenericTypeMatchesForUpperBoundedWildcard() {
269+
IntegerListHolder1 integerListHolder1 = new IntegerListHolder1();
270+
integerListHolder1.getList().add(42);
271+
NumberUpperBoundedWildcardListHolder numberListHolder = new NumberUpperBoundedWildcardListHolder();
272+
273+
BeanUtils.copyProperties(integerListHolder1, numberListHolder);
274+
assertThat(integerListHolder1.getList()).containsOnly(42);
275+
assertThat(numberListHolder.getList()).hasSize(1);
276+
assertThat(numberListHolder.getList().contains(Integer.valueOf(42))).isTrue();
277+
}
278+
279+
/**
280+
* {@code Number} can NOT be copied to {@code Integer}.
281+
*/
282+
@Test
283+
void copyPropertiesDoesNotCopyeFromSuperTypeToSubType() {
284+
NumberHolder numberHolder = new NumberHolder();
285+
numberHolder.setNumber(Integer.valueOf(42));
286+
IntegerHolder integerHolder = new IntegerHolder();
287+
288+
BeanUtils.copyProperties(numberHolder, integerHolder);
289+
assertThat(numberHolder.getNumber()).isEqualTo(42);
290+
assertThat(integerHolder.getNumber()).isNull();
291+
}
292+
293+
/**
294+
* {@code List<Integer>} can NOT be copied to {@code List<Long>}.
295+
*/
217296
@Test
218297
void copyPropertiesDoesNotHonorGenericTypeMismatches() {
219298
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
@@ -225,6 +304,20 @@ void copyPropertiesDoesNotHonorGenericTypeMismatches() {
225304
assertThat(longListHolder.getList()).isEmpty();
226305
}
227306

307+
/**
308+
* {@code List<Integer>} can NOT be copied to {@code List<Number>}.
309+
*/
310+
@Test
311+
void copyPropertiesDoesNotHonorGenericTypeMismatchesFromSubTypeToSuperType() {
312+
IntegerListHolder1 integerListHolder = new IntegerListHolder1();
313+
integerListHolder.getList().add(42);
314+
NumberListHolder numberListHolder = new NumberListHolder();
315+
316+
BeanUtils.copyProperties(integerListHolder, numberListHolder);
317+
assertThat(integerListHolder.getList()).containsOnly(42);
318+
assertThat(numberListHolder.getList()).isEmpty();
319+
}
320+
228321
@Test // gh-26531
229322
void copyPropertiesIgnoresGenericsIfSourceOrTargetHasUnresolvableGenerics() throws Exception {
230323
Order original = new Order("test", Arrays.asList("foo", "bar"));
@@ -413,6 +506,90 @@ private void assertSignatureEquals(Method desiredMethod, String signature) {
413506
}
414507

415508

509+
@SuppressWarnings("unused")
510+
private static class NumberHolder {
511+
512+
private Number number;
513+
514+
public Number getNumber() {
515+
return number;
516+
}
517+
518+
public void setNumber(Number number) {
519+
this.number = number;
520+
}
521+
}
522+
523+
@SuppressWarnings("unused")
524+
private static class IntegerHolder {
525+
526+
private Integer number;
527+
528+
public Integer getNumber() {
529+
return number;
530+
}
531+
532+
public void setNumber(Integer number) {
533+
this.number = number;
534+
}
535+
}
536+
537+
@SuppressWarnings("unused")
538+
private static class WildcardListHolder1 {
539+
540+
private List<?> list = new ArrayList<>();
541+
542+
public List<?> getList() {
543+
return list;
544+
}
545+
546+
public void setList(List<?> list) {
547+
this.list = list;
548+
}
549+
}
550+
551+
@SuppressWarnings("unused")
552+
private static class WildcardListHolder2 {
553+
554+
private List<?> list = new ArrayList<>();
555+
556+
public List<?> getList() {
557+
return list;
558+
}
559+
560+
public void setList(List<?> list) {
561+
this.list = list;
562+
}
563+
}
564+
565+
@SuppressWarnings("unused")
566+
private static class NumberUpperBoundedWildcardListHolder {
567+
568+
private List<? extends Number> list = new ArrayList<>();
569+
570+
public List<? extends Number> getList() {
571+
return list;
572+
}
573+
574+
public void setList(List<? extends Number> list) {
575+
this.list = list;
576+
}
577+
}
578+
579+
@SuppressWarnings("unused")
580+
private static class NumberListHolder {
581+
582+
private List<Number> list = new ArrayList<>();
583+
584+
public List<Number> getList() {
585+
return list;
586+
}
587+
588+
public void setList(List<Number> list) {
589+
this.list = list;
590+
}
591+
}
592+
416593
@SuppressWarnings("unused")
417594
private static class IntegerListHolder1 {
418595

0 commit comments

Comments
 (0)