Skip to content

Commit 9ec5341

Browse files
mp911demipo256
authored andcommitted
Specialize Kotlin property accessors in KotlinBeanInfoFactory.
We now attempt to detect property accessors for properties declared in Kotlin that do not have a Kotlin-style accessor but one that instead comes from an interface. Also, we specialize accessor methods that are inherited from a Java superclass but override accessors in the Kotlin realm. Closes spring-projects#3140 Closes spring-projects#3146
1 parent 466a7b9 commit 9ec5341

File tree

3 files changed

+111
-10
lines changed

3 files changed

+111
-10
lines changed

src/main/java/org/springframework/data/util/KotlinBeanInfoFactory.java

+39-7
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,19 @@
2929
import java.beans.SimpleBeanInfo;
3030
import java.lang.reflect.Method;
3131
import java.lang.reflect.Modifier;
32-
import java.util.Arrays;
32+
import java.lang.reflect.Type;
3333
import java.util.Collection;
34-
import java.util.LinkedHashSet;
35-
import java.util.Set;
34+
import java.util.LinkedHashMap;
35+
import java.util.Map;
3636

3737
import org.springframework.beans.BeanInfoFactory;
3838
import org.springframework.beans.BeanUtils;
3939
import org.springframework.core.KotlinDetector;
4040
import org.springframework.core.Ordered;
41+
import org.springframework.lang.Nullable;
42+
import org.springframework.util.ClassUtils;
43+
import org.springframework.util.ReflectionUtils;
44+
import org.springframework.util.StringUtils;
4145

4246
/**
4347
* {@link BeanInfoFactory} specific to Kotlin types using Kotlin reflection to determine bean properties.
@@ -62,7 +66,7 @@ public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
6266

6367
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(beanClass);
6468
Collection<KCallable<?>> members = kotlinClass.getMembers();
65-
Set<PropertyDescriptor> pds = new LinkedHashSet<>(members.size());
69+
Map<String, PropertyDescriptor> descriptors = new LinkedHashMap<>(members.size(), 1.f);
6670

6771
for (KCallable<?> member : members) {
6872

@@ -71,6 +75,16 @@ public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
7175
Method getter = ReflectJvmMapping.getJavaGetter(property);
7276
Method setter = property instanceof KMutableProperty<?> kmp ? ReflectJvmMapping.getJavaSetter(kmp) : null;
7377

78+
if (getter == null) {
79+
Type javaType = ReflectJvmMapping.getJavaType(property.getReturnType());
80+
getter = ReflectionUtils.findMethod(beanClass,
81+
javaType == Boolean.TYPE ? "is" : "get" + StringUtils.capitalize(property.getName()));
82+
}
83+
84+
if (getter != null) {
85+
getter = ClassUtils.getMostSpecificMethod(getter, beanClass);
86+
}
87+
7488
if (getter != null && (Modifier.isStatic(getter.getModifiers()) || getter.getParameterCount() != 0)) {
7589
continue;
7690
}
@@ -82,7 +96,7 @@ public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
8296
}
8397
}
8498

85-
pds.add(new PropertyDescriptor(property.getName(), getter, setter));
99+
descriptors.put(property.getName(), new PropertyDescriptor(property.getName(), getter, setter));
86100
}
87101
}
88102

@@ -95,9 +109,17 @@ public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
95109
if (javaClass != Object.class) {
96110

97111
PropertyDescriptor[] javaPropertyDescriptors = BeanUtils.getPropertyDescriptors(javaClass);
98-
pds.addAll(Arrays.asList(javaPropertyDescriptors));
112+
113+
for (PropertyDescriptor descriptor : javaPropertyDescriptors) {
114+
115+
descriptor = new PropertyDescriptor(descriptor.getName(), specialize(beanClass, descriptor.getReadMethod()),
116+
specialize(beanClass, descriptor.getWriteMethod()));
117+
descriptors.put(descriptor.getName(), descriptor);
118+
}
99119
}
100120

121+
PropertyDescriptor[] propertyDescriptors = descriptors.values().toArray(new PropertyDescriptor[0]);
122+
101123
return new SimpleBeanInfo() {
102124
@Override
103125
public BeanDescriptor getBeanDescriptor() {
@@ -106,11 +128,21 @@ public BeanDescriptor getBeanDescriptor() {
106128

107129
@Override
108130
public PropertyDescriptor[] getPropertyDescriptors() {
109-
return pds.toArray(new PropertyDescriptor[0]);
131+
return propertyDescriptors;
110132
}
111133
};
112134
}
113135

136+
@Nullable
137+
private static Method specialize(Class<?> beanClass, @Nullable Method method) {
138+
139+
if (method == null) {
140+
return method;
141+
}
142+
143+
return ClassUtils.getMostSpecificMethod(method, beanClass);
144+
}
145+
114146
@Override
115147
public int getOrder() {
116148
return LOWEST_PRECEDENCE - 10; // leave some space for customizations.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.util;
17+
18+
public class Animal {
19+
20+
private String name;
21+
22+
public String getName() {
23+
return name;
24+
}
25+
26+
public void setName(String name) {
27+
this.name = name;
28+
}
29+
}

src/test/kotlin/org/springframework/data/util/KotlinBeanInfoFactoryUnitTests.kt

+43-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package org.springframework.data.util
1818
import org.assertj.core.api.Assertions.assertThat
1919
import org.junit.jupiter.api.Test
2020
import org.springframework.beans.BeanUtils
21+
import org.springframework.data.annotation.Id
22+
import org.springframework.data.domain.Persistable
2123
import org.springframework.data.repository.Repository
2224
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport
2325
import org.springframework.data.repository.core.support.RepositoryFactorySupport
@@ -95,9 +97,11 @@ class KotlinBeanInfoFactoryUnitTests {
9597
@Test // GH-2994
9698
internal fun includesPropertiesFromJavaSupertypes() {
9799

98-
val pds = BeanUtils.getPropertyDescriptors(MyRepositoryFactoryBeanImpl::class.java)
100+
val pds =
101+
BeanUtils.getPropertyDescriptors(MyRepositoryFactoryBeanImpl::class.java)
99102

100-
assertThat(pds).extracting("name").contains("myQueryLookupStrategyKey", "repositoryBaseClass")
103+
assertThat(pds).extracting("name")
104+
.contains("myQueryLookupStrategyKey", "repositoryBaseClass")
101105
}
102106

103107
@Test // GH-2993
@@ -110,6 +114,22 @@ class KotlinBeanInfoFactoryUnitTests {
110114
assertThat(pds[0].readMethod).isNotNull()
111115
}
112116

117+
@Test // GH-3140
118+
internal fun specializesBeanMethods() {
119+
120+
var pds = BeanUtils.getPropertyDescriptors(Entity::class.java)
121+
122+
assertThat(pds.find { it.name == "id" }!!.readMethod!!.declaringClass).isEqualTo(
123+
Entity::class.java
124+
)
125+
126+
pds = BeanUtils.getPropertyDescriptors(DogEntity::class.java)
127+
128+
assertThat(pds.find { it.name == "name" }!!.readMethod!!.declaringClass).isEqualTo(
129+
DogEntity::class.java
130+
)
131+
}
132+
113133
data class SimpleDataClass(val id: String, var name: String)
114134

115135
@JvmInline
@@ -127,7 +147,8 @@ class KotlinBeanInfoFactoryUnitTests {
127147
Foo, Bar
128148
}
129149

130-
class MyRepositoryFactoryBeanImpl<R, E, I>(repository: Class<R>) : RepositoryFactoryBeanSupport<R, E, I>(repository)
150+
class MyRepositoryFactoryBeanImpl<R, E, I>(repository: Class<R>) :
151+
RepositoryFactoryBeanSupport<R, E, I>(repository)
131152
where R : Repository<E, I>, E : Any, I : Any {
132153

133154
private var myQueryLookupStrategyKey: String
@@ -149,4 +170,23 @@ class KotlinBeanInfoFactoryUnitTests {
149170
override var end: Long = -1L
150171
protected set
151172
}
173+
174+
class Entity(
175+
private val id: Long? = null,
176+
177+
val name: String
178+
) : Persistable<Long> {
179+
180+
override fun getId(): Long? = id
181+
182+
override fun isNew(): Boolean = id == null
183+
}
184+
185+
open class DogEntity : Animal() {
186+
187+
@Id
188+
override fun getName(): String {
189+
return super.getName()
190+
}
191+
}
152192
}

0 commit comments

Comments
 (0)