Skip to content

Commit e143291

Browse files
committed
Add Kotlin support
1 parent 6e91c1f commit e143291

File tree

10 files changed

+687
-52
lines changed

10 files changed

+687
-52
lines changed

src/main/java/org/springframework/data/core/MemberDescriptor.java

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.core;
1717

18+
import kotlin.reflect.KProperty1;
19+
import kotlin.reflect.jvm.ReflectJvmMapping;
20+
1821
import java.lang.invoke.SerializedLambda;
1922
import java.lang.reflect.Field;
2023
import java.lang.reflect.Member;
@@ -31,8 +34,7 @@
3134
* @author Mark Paluch
3235
* @since 4.1
3336
*/
34-
sealed interface MemberDescriptor
35-
permits MemberDescriptor.MethodDescriptor.FieldDescriptor, MemberDescriptor.MethodDescriptor {
37+
interface MemberDescriptor {
3638

3739
/**
3840
* @return class owning the member, can be the declaring class or a subclass.
@@ -69,7 +71,7 @@ static MethodDescriptor ofMethod(ClassLoader classLoader, String ownerClassName,
6971
/**
7072
* Create {@link MethodDescriptor.FieldDescriptor} from owner type, field name and field type.
7173
*/
72-
public static MethodDescriptor.FieldDescriptor ofField(ClassLoader classLoader, String ownerClassName, String name,
74+
static MethodDescriptor.FieldDescriptor ofField(ClassLoader classLoader, String ownerClassName, String name,
7375
String fieldType) throws ClassNotFoundException {
7476

7577
Class<?> owner = ClassUtils.forName(ownerClassName, classLoader);
@@ -80,9 +82,6 @@ public static MethodDescriptor.FieldDescriptor ofField(ClassLoader classLoader,
8082

8183
/**
8284
* Value object describing a {@link Method} in the context of an owning class.
83-
*
84-
* @param owner
85-
* @param method
8685
*/
8786
record MethodDescriptor(Class<?> owner, Method method) implements MemberDescriptor {
8887

@@ -113,9 +112,6 @@ public ResolvableType getType() {
113112

114113
/**
115114
* Value object describing a {@link Field} in the context of an owning class.
116-
*
117-
* @param owner
118-
* @param field
119115
*/
120116
record FieldDescriptor(Class<?> owner, Field field) implements MemberDescriptor {
121117

@@ -144,4 +140,50 @@ public ResolvableType getType() {
144140
}
145141

146142
}
143+
144+
/**
145+
* Value object describing a Kotlin property in the context of an owning class.
146+
*/
147+
record KPropertyReferenceDescriptor(Class<?> owner, KProperty1<?, ?> property) implements MemberDescriptor {
148+
149+
static KPropertyReferenceDescriptor create(Class<?> owner, KProperty1<?, ?> property) {
150+
return new KPropertyReferenceDescriptor(owner, property);
151+
}
152+
153+
@Override
154+
public Class<?> getOwner() {
155+
return owner();
156+
}
157+
158+
@Override
159+
public Member getMember() {
160+
161+
Method javaGetter = ReflectJvmMapping.getJavaGetter(property());
162+
if (javaGetter != null) {
163+
return javaGetter;
164+
}
165+
166+
Field javaField = ReflectJvmMapping.getJavaField(property());
167+
168+
if (javaField != null) {
169+
return javaField;
170+
}
171+
172+
throw new IllegalStateException("Cannot resolve member for property '%s'".formatted(property().getName()));
173+
}
174+
175+
@Override
176+
public ResolvableType getType() {
177+
178+
Member member = getMember();
179+
180+
if (member instanceof Method m) {
181+
return ResolvableType.forMethodReturnType(m, owner());
182+
}
183+
184+
return ResolvableType.forField((Field) member, owner());
185+
}
186+
187+
}
188+
147189
}

src/main/java/org/springframework/data/core/SerializableLambdaReader.java

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
*/
1616
package org.springframework.data.core;
1717

18+
import kotlin.jvm.JvmClassMappingKt;
19+
import kotlin.jvm.internal.PropertyReference;
20+
import kotlin.reflect.KClass;
21+
import kotlin.reflect.KProperty1;
22+
1823
import java.io.IOException;
1924
import java.io.InputStream;
2025
import java.lang.invoke.MethodHandleInfo;
@@ -33,16 +38,17 @@
3338
import org.apache.commons.logging.Log;
3439
import org.apache.commons.logging.LogFactory;
3540
import org.jspecify.annotations.Nullable;
36-
3741
import org.springframework.asm.ClassReader;
3842
import org.springframework.asm.ClassVisitor;
3943
import org.springframework.asm.Label;
4044
import org.springframework.asm.MethodVisitor;
4145
import org.springframework.asm.Opcodes;
4246
import org.springframework.asm.SpringAsmInfo;
4347
import org.springframework.asm.Type;
48+
import org.springframework.core.KotlinDetector;
4449
import org.springframework.core.SpringProperties;
4550
import org.springframework.dao.InvalidDataAccessApiUsageException;
51+
import org.springframework.data.core.MemberDescriptor.KPropertyReferenceDescriptor;
4652
import org.springframework.util.ClassUtils;
4753
import org.springframework.util.ObjectUtils;
4854
import org.springframework.util.StringUtils;
@@ -104,8 +110,8 @@ class SerializableLambdaReader {
104110
public static final String INCLUDE_SUPPRESSED_EXCEPTIONS = "spring.data.lambda-reader.include-suppressed-exceptions";
105111

106112
private static final Log LOGGER = LogFactory.getLog(SerializableLambdaReader.class);
107-
private static final boolean filterStackTrace = isEnabled(FILTER_STACK_TRACE, true);
108-
private static final boolean includeSuppressedExceptions = isEnabled(INCLUDE_SUPPRESSED_EXCEPTIONS, false);
113+
private static final boolean filterStackTrace = isEnabled(FILTER_STACK_TRACE, false);
114+
private static final boolean includeSuppressedExceptions = isEnabled(INCLUDE_SUPPRESSED_EXCEPTIONS, true);
109115

110116
private final List<Class<?>> entryPoints;
111117

@@ -132,6 +138,18 @@ private static boolean isEnabled(String property, boolean defaultValue) {
132138
public MemberDescriptor read(Object lambdaObject) {
133139

134140
SerializedLambda lambda = serialize(lambdaObject);
141+
142+
if (isKotlinPropertyReference(lambda)) {
143+
144+
Object captured = lambda.getCapturedArg(0);
145+
if (captured != null //
146+
&& captured instanceof PropertyReference propRef //
147+
&& propRef.getOwner() instanceof KClass<?> owner //
148+
&& captured instanceof KProperty1<?, ?> kProperty) {
149+
return new KPropertyReferenceDescriptor(JvmClassMappingKt.getJavaClass(owner), kProperty);
150+
}
151+
}
152+
135153
assertNotConstructor(lambda);
136154

137155
try {
@@ -156,6 +174,11 @@ public MemberDescriptor read(Object lambdaObject) {
156174
+ ". The given value is not a lambda or method reference.");
157175
}
158176

177+
public static boolean isKotlinPropertyReference(SerializedLambda lambda) {
178+
return KotlinDetector.isKotlinReflectPresent() && lambda.getCapturedArgCount() == 1
179+
&& lambda.getCapturedArg(0) != null && KotlinDetector.isKotlinType(lambda.getCapturedArg(0).getClass());
180+
}
181+
159182
private void assertNotConstructor(SerializedLambda lambda) {
160183

161184
if (lambda.getImplMethodKind() == MethodHandleInfo.REF_newInvokeSpecial
@@ -192,7 +215,7 @@ private MemberDescriptor getMemberDescriptor(Object lambdaObject, SerializedLamb
192215
}
193216
}
194217

195-
private static SerializedLambda serialize(Object lambda) {
218+
static SerializedLambda serialize(Object lambda) {
196219

197220
try {
198221
Method method = lambda.getClass().getDeclaredMethod("writeReplace");

src/main/java/org/springframework/data/core/TypedPropertyPath.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public interface TypedPropertyPath<T, P> extends PropertyPath, Serializable {
7272
* @param <P> property type.
7373
* @return the typed property path.
7474
*/
75-
static <T, P> TypedPropertyPath<T, P> of(TypedPropertyPath<T, P> propertyPath) {
75+
static <T, P extends @Nullable Object> TypedPropertyPath<T, P> of(TypedPropertyPath<T, P> propertyPath) {
7676
return TypedPropertyPaths.of(propertyPath);
7777
}
7878

@@ -124,7 +124,7 @@ default Iterator<PropertyPath> iterator() {
124124
* @param <N> the new property value type.
125125
* @return a new composed {@code TypedPropertyPath}.
126126
*/
127-
default <N> TypedPropertyPath<T, N> then(TypedPropertyPath<P, N> next) {
127+
default <N extends @Nullable Object> TypedPropertyPath<T, N> then(TypedPropertyPath<P, N> next) {
128128
return TypedPropertyPaths.compose(this, of(next));
129129
}
130130

0 commit comments

Comments
 (0)