Skip to content

Commit 32c3907

Browse files
vaadin-botArtur-mshabarov
authored
fix: Include a given property only once in getBeanPropertyDescriptors (#21836) (#21863)
* fix: Include a given property only once in getBeanPropertyDescriptors * format * Skip testing as the interface read method is returned for the class --------- Co-authored-by: Artur <artur@vaadin.com> Co-authored-by: Mikhail Shabarov <61410877+mshabarov@users.noreply.github.com>
1 parent ffcbf3d commit 32c3907

File tree

2 files changed

+106
-2
lines changed

2 files changed

+106
-2
lines changed

flow-server/src/main/java/com/vaadin/flow/internal/BeanUtil.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import java.lang.reflect.Method;
2525
import java.lang.reflect.RecordComponent;
2626
import java.util.ArrayList;
27+
import java.util.LinkedHashMap;
28+
import java.util.LinkedHashSet;
2729
import java.util.List;
2830

2931
import org.slf4j.LoggerFactory;
@@ -67,6 +69,33 @@ private BeanUtil() {
6769
*/
6870
public static List<PropertyDescriptor> getBeanPropertyDescriptors(
6971
final Class<?> beanType) throws IntrospectionException {
72+
List<PropertyDescriptor> descriptorsWithDuplicates = internalGetBeanPropertyDescriptors(
73+
beanType);
74+
75+
// As we scan for default methods, we might get duplicates
76+
// for the same property, so we need to remove them.
77+
// We prefer to keep a class property over an interface
78+
// property.
79+
LinkedHashMap<String, PropertyDescriptor> descriptors = new LinkedHashMap<>();
80+
for (PropertyDescriptor descriptor : descriptorsWithDuplicates) {
81+
String name = descriptor.getName();
82+
if (descriptors.containsKey(name)) {
83+
PropertyDescriptor existing = descriptors.get(name);
84+
// If the existing descriptor is from a class, keep it
85+
// otherwise replace it with the new one.
86+
if (existing.getReadMethod() != null && !existing
87+
.getReadMethod().getDeclaringClass().isInterface()) {
88+
continue;
89+
}
90+
}
91+
descriptors.put(name, descriptor);
92+
}
93+
94+
return new ArrayList<>(descriptors.values());
95+
}
96+
97+
private static List<PropertyDescriptor> internalGetBeanPropertyDescriptors(
98+
Class<?> beanType) throws IntrospectionException {
7099

71100
if (beanType.isRecord()) {
72101
List<PropertyDescriptor> propertyDescriptors = new ArrayList<>();
@@ -75,15 +104,14 @@ public static List<PropertyDescriptor> getBeanPropertyDescriptors(
75104
propertyDescriptors.add(new PropertyDescriptor(
76105
component.getName(), component.getAccessor(), null));
77106
}
78-
79107
return propertyDescriptors;
80108
}
81109
// Introspector does not consider superinterfaces of
82110
// an interface nor does it consider default methods of interfaces.
83111
List<PropertyDescriptor> propertyDescriptors = new ArrayList<>();
84112

85113
for (Class<?> cls : beanType.getInterfaces()) {
86-
propertyDescriptors.addAll(getBeanPropertyDescriptors(cls));
114+
propertyDescriptors.addAll(internalGetBeanPropertyDescriptors(cls));
87115
}
88116

89117
BeanInfo info = Introspector.getBeanInfo(beanType);
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.vaadin.flow.internal;
2+
3+
import java.beans.PropertyDescriptor;
4+
import java.util.List;
5+
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
9+
public class BeanUtilTest {
10+
11+
public interface FirstInterface {
12+
default void setOneInterface(boolean bothInterfaces) {
13+
}
14+
15+
default boolean isOneInterface() {
16+
return true;
17+
}
18+
19+
default void setExistsInAllPlaces(boolean visible) {
20+
}
21+
22+
default boolean isExistsInAllPlaces() {
23+
return true;
24+
}
25+
}
26+
27+
public interface SecondInterface {
28+
default void setExistsInAllPlaces(boolean visible) {
29+
}
30+
31+
default boolean isExistsInAllPlaces() {
32+
return true;
33+
}
34+
}
35+
36+
public class TestSomething implements FirstInterface, SecondInterface {
37+
public void setExistsInAllPlaces(boolean visible) {
38+
}
39+
40+
public boolean isExistsInAllPlaces() {
41+
return true;
42+
}
43+
}
44+
45+
@Test
46+
public void duplicatesAreRemoved() throws Exception {
47+
List<PropertyDescriptor> descriptors = BeanUtil
48+
.getBeanPropertyDescriptors(TestSomething.class);
49+
List<PropertyDescriptor> existsInAllPlacesProperties = descriptors
50+
.stream()
51+
.filter(desc -> desc.getName().equals("existsInAllPlaces"))
52+
.toList();
53+
Assert.assertEquals(
54+
"There should be only one 'existsInAllPlaces' property descriptor",
55+
1, existsInAllPlacesProperties.size());
56+
57+
// The property from the class should be retained
58+
// but we cannot test this as some introspector implementations
59+
// return the read method from the interface when introspecting the
60+
// class
61+
// Assert.assertEquals(TestSomething.class,
62+
// existsInAllPlacesProperties.get(0).getReadMethod().getDeclaringClass());
63+
64+
List<PropertyDescriptor> oneInterfaceProperties = descriptors.stream()
65+
.filter(desc -> desc.getName().equals("oneInterface")).toList();
66+
Assert.assertEquals(
67+
"There should be only one 'oneInterface' property descriptor",
68+
1, oneInterfaceProperties.size());
69+
70+
PropertyDescriptor oneInterfaceProperty = oneInterfaceProperties.get(0);
71+
// The property from thedefault method
72+
Assert.assertEquals(FirstInterface.class,
73+
oneInterfaceProperty.getReadMethod().getDeclaringClass());
74+
75+
}
76+
}

0 commit comments

Comments
 (0)