-
Notifications
You must be signed in to change notification settings - Fork 682
/
Copy pathKotlinReflectionUtils.java
234 lines (184 loc) · 7.31 KB
/
KotlinReflectionUtils.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
* Copyright 2019-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.util;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KCallable;
import kotlin.reflect.KClass;
import kotlin.reflect.KFunction;
import kotlin.reflect.KMutableProperty;
import kotlin.reflect.KProperty;
import kotlin.reflect.KType;
import kotlin.reflect.jvm.KTypesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
/**
* Reflection utility methods specific to Kotlin reflection. Requires Kotlin classes to be present to avoid linkage
* errors.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.3
* @see org.springframework.core.KotlinDetector#isKotlinReflectPresent()
*/
public final class KotlinReflectionUtils {
private KotlinReflectionUtils() {}
/**
* Return {@literal true} if the specified class is a supported Kotlin class. Currently supported are only regular
* Kotlin classes. Other class types (synthetic, SAM, lambdas) are not supported via reflection.
*
* @return {@literal true} if {@code type} is a supported Kotlin class.
*/
public static boolean isSupportedKotlinClass(Class<?> type) {
if (!KotlinDetector.isKotlinType(type)) {
return false;
}
return Arrays.stream(type.getDeclaredAnnotations()) //
.filter(annotation -> annotation.annotationType().getName().equals("kotlin.Metadata")) //
.map(annotation -> AnnotationUtils.getValue(annotation, "k")) //
.anyMatch(it -> Integer.valueOf(KotlinClassHeaderKind.CLASS.id).equals(it));
}
/**
* Return {@literal true} if the specified class is a Kotlin data class.
*
* @return {@literal true} if {@code type} is a Kotlin data class.
* @since 2.6
*/
public static boolean isDataClass(Class<?> type) {
if (!KotlinDetector.isKotlinType(type)) {
return false;
}
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(type);
return kotlinClass.isData();
}
/**
* Returns a {@link KFunction} instance corresponding to the given Java {@link Method} instance, or {@code null} if
* this method cannot be represented by a Kotlin function.
*
* @param method the method to look up.
* @return the {@link KFunction} or {@code null} if the method cannot be looked up.
*/
@Nullable
public static KFunction<?> findKotlinFunction(Method method) {
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(method);
// Fallback to own lookup because there's no public Kotlin API for that kind of lookup until
// https://youtrack.jetbrains.com/issue/KT-20768 gets resolved.
return kotlinFunction == null ? findKFunction(method).orElse(null) : kotlinFunction;
}
/**
* Returns whether the {@link Method} is declared as suspend (Kotlin Coroutine).
*
* @param method the method to inspect.
* @return {@literal true} if the method is declared as suspend.
* @see KFunction#isSuspend()
*/
public static boolean isSuspend(Method method) {
KFunction<?> invokedFunction = KotlinDetector.isKotlinType(method.getDeclaringClass()) ? findKotlinFunction(method)
: null;
return invokedFunction != null && invokedFunction.isSuspend();
}
/**
* Returns the {@link Class return type} of a Kotlin {@link Method}. Supports regular and suspended methods.
*
* @param method the method to inspect, typically any synthetic JVM {@link Method}.
* @return return type of the method.
*/
public static Class<?> getReturnType(Method method) {
KFunction<?> kotlinFunction = KotlinReflectionUtils.findKotlinFunction(method);
if (kotlinFunction == null) {
throw new IllegalArgumentException(String.format("Cannot resolve %s to a KFunction!", method));
}
return JvmClassMappingKt.getJavaClass(KTypesJvm.getJvmErasure(kotlinFunction.getReturnType()));
}
/**
* Returns {@literal} whether the given {@link MethodParameter} is nullable. Its declaring method can reference a
* Kotlin function, property or interface property.
*
* @return {@literal true} if {@link MethodParameter} is nullable.
* @since 2.0.1
*/
static boolean isNullable(MethodParameter parameter) {
Method method = parameter.getMethod();
if (method == null) {
throw new IllegalStateException(String.format("Cannot obtain method from parameter %s!", parameter));
}
KFunction<?> kotlinFunction = findKotlinFunction(method);
if (kotlinFunction == null) {
throw new IllegalArgumentException(String.format("Cannot resolve %s to a Kotlin function!", parameter));
}
// Special handling for Coroutines
if (kotlinFunction.isSuspend() && isLast(parameter)) {
return false;
}
// see https://github.com/spring-projects/spring-framework/issues/23991
if (kotlinFunction.getParameters().size() > parameter.getParameterIndex() + 1) {
KType type = parameter.getParameterIndex() == -1 //
? kotlinFunction.getReturnType() //
: kotlinFunction.getParameters().get(parameter.getParameterIndex() + 1).getType();
return type.isMarkedNullable();
}
return true;
}
private static boolean isLast(MethodParameter parameter) {
Method method = parameter.getMethod();
return method != null && parameter.getParameterIndex() == method.getParameterCount() - 1;
}
/**
* Lookup a {@link Method} to a {@link KFunction}.
*
* @param method the JVM {@link Method} to look up.
* @return {@link Optional} wrapping a possibly existing {@link KFunction}.
*/
private static Optional<? extends KFunction<?>> findKFunction(Method method) {
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(method.getDeclaringClass());
return kotlinClass.getMembers() //
.stream() //
.flatMap(KotlinReflectionUtils::toKFunctionStream) //
.filter(it -> isSame(it, method)) //
.findFirst();
}
private static Stream<? extends KFunction<?>> toKFunctionStream(KCallable<?> it) {
if (it instanceof KMutableProperty<?>) {
KMutableProperty<?> property = (KMutableProperty<?>) it;
return Stream.of(property.getGetter(), property.getSetter());
}
if (it instanceof KProperty<?>) {
KProperty<?> property = (KProperty<?>) it;
return Stream.of(property.getGetter());
}
if (it instanceof KFunction<?>) {
return Stream.of((KFunction<?>) it);
}
return Stream.empty();
}
private static boolean isSame(KFunction<?> function, Method method) {
Method javaMethod = ReflectJvmMapping.getJavaMethod(function);
return javaMethod != null && javaMethod.equals(method);
}
private enum KotlinClassHeaderKind {
CLASS(1), FILE(2), SYNTHETIC_CLASS(3), MULTI_FILE_CLASS_FACADE(4), MULTI_FILE_CLASS_PART(5);
int id;
KotlinClassHeaderKind(int val) {
this.id = val;
}
}
}