org.springframework.data.util.KotlinReflectionUtils Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2019-2024 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 - ensure to guard usage with {@link KotlinDetector#isKotlinPresent()}.
*
* @author Mark Paluch
* @author Christoph Strobl
* @author Johannes Englmeier
* @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.5.1
*/
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 whether the given {@link KType} is a {@link KClass#isValue() value} class.
*
* @param type the kotlin type to inspect.
* @return {@code true} the type is a value class.
* @since 3.2
*/
public static boolean isValueClass(KType type) {
return type.getClassifier() instanceof KClass> kc && kc.isValue();
}
/**
* Returns whether the given class makes uses Kotlin {@link KClass#isValue() value} classes.
*
* @param type the kotlin type to inspect.
* @return {@code true} when at least one property uses Kotlin value classes.
* @since 3.2
*/
public static boolean hasValueClassProperty(Class> type) {
if (!KotlinDetector.isKotlinType(type)) {
return false;
}
KClass> kotlinClass = JvmClassMappingKt.getKotlinClass(type);
for (KCallable> member : kotlinClass.getMembers()) {
if (member instanceof KProperty> kp && isValueClass(kp.getReturnType())) {
return true;
}
}
return false;
}
/**
* 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> property) {
return Stream.of(property.getGetter(), property.getSetter());
}
if (it instanceof KProperty> property) {
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;
}
}
}