Refine KotlinDetector usages and implementation

This commit refines KotlinDetector usages and implementation in order
to remove preliminary KotlinDetector#isKotlinReflectPresent invocations
and to ensure that KotlinDetector methods are implemented safely and
efficiently for such use case.

Closes gh-34275
This commit is contained in:
Sébastien Deleuze
2025-01-17 16:21:50 +01:00
parent ffd7b93dde
commit 1763334180
15 changed files with 56 additions and 66 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2024 the original author or authors.
* Copyright 2002-2025 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.
@@ -24,7 +24,8 @@ import org.jspecify.annotations.Nullable;
import org.springframework.util.ClassUtils;
/**
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types.
* A common delegate for detecting Kotlin's presence and for identifying Kotlin types. All the methods of this class
* can be safely used without any preliminary classpath checks.
*
* @author Juergen Hoeller
* @author Sebastien Deleuze
@@ -37,6 +38,8 @@ public abstract class KotlinDetector {
private static final @Nullable Class<? extends Annotation> kotlinJvmInline;
private static final @Nullable Class<?> kotlinCoroutineContinuation;
// For ConstantFieldFeature compliance, otherwise could be deduced from kotlinMetadata
private static final boolean kotlinPresent;
@@ -46,6 +49,7 @@ public abstract class KotlinDetector {
ClassLoader classLoader = KotlinDetector.class.getClassLoader();
Class<?> metadata = null;
Class<?> jvmInline = null;
Class<?> coroutineContinuation = null;
try {
metadata = ClassUtils.forName("kotlin.Metadata", classLoader);
try {
@@ -54,14 +58,21 @@ public abstract class KotlinDetector {
catch (ClassNotFoundException ex) {
// JVM inline support not available
}
try {
coroutineContinuation = ClassUtils.forName("kotlin.coroutines.Continuation", classLoader);
}
catch (ClassNotFoundException ex) {
// Coroutines support not available
}
}
catch (ClassNotFoundException ex) {
// Kotlin API not available - no Kotlin support
}
kotlinMetadata = (Class<? extends Annotation>) metadata;
kotlinPresent = (kotlinMetadata != null);
kotlinReflectPresent = kotlinPresent && ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
kotlinReflectPresent = ClassUtils.isPresent("kotlin.reflect.full.KClasses", classLoader);
kotlinJvmInline = (Class<? extends Annotation>) jvmInline;
kotlinCoroutineContinuation = coroutineContinuation;
}
@@ -89,7 +100,7 @@ public abstract class KotlinDetector {
* as invokedynamic has become the default method for lambda generation.
*/
public static boolean isKotlinType(Class<?> clazz) {
return (kotlinMetadata != null && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
return (kotlinPresent && clazz.getDeclaredAnnotation(kotlinMetadata) != null);
}
/**
@@ -97,13 +108,11 @@ public abstract class KotlinDetector {
* @since 5.3
*/
public static boolean isSuspendingFunction(Method method) {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] types = method.getParameterTypes();
if (types.length > 0 && "kotlin.coroutines.Continuation".equals(types[types.length - 1].getName())) {
return true;
}
if (kotlinCoroutineContinuation == null) {
return false;
}
return false;
int parameterCount = method.getParameterCount();
return (parameterCount > 0 && method.getParameterTypes()[parameterCount - 1] == kotlinCoroutineContinuation);
}
/**

View File

@@ -396,9 +396,7 @@ public class MethodParameter {
*/
public boolean isOptional() {
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(getContainingClass()) &&
KotlinDelegate.isOptional(this)));
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this)));
}
/**
@@ -508,8 +506,8 @@ public class MethodParameter {
if (this.parameterIndex < 0) {
Method method = getMethod();
paramType = (method != null ?
(KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
(KotlinDetector.isKotlinType(getContainingClass()) ?
KotlinDelegate.getGenericReturnType(method) : method.getGenericReturnType()) : void.class);
}
else {
Type[] genericParameterTypes = this.executable.getGenericParameterTypes();
@@ -536,7 +534,7 @@ public class MethodParameter {
if (method == null) {
return void.class;
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
if (KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method);
}
return method.getReturnType();

View File

@@ -374,7 +374,7 @@ public class SpringFactoriesLoader {
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
Object[] args = resolveArgs(argumentResolver);
if (isKotlinType(this.constructor.getDeclaringClass())) {
if (KotlinDetector.isKotlinType(this.constructor.getDeclaringClass())) {
return KotlinDelegate.instantiate(this.constructor, args);
}
return this.constructor.newInstance(args);
@@ -408,14 +408,10 @@ public class SpringFactoriesLoader {
}
private static @Nullable Constructor<?> findPrimaryKotlinConstructor(Class<?> factoryImplementationClass) {
return (isKotlinType(factoryImplementationClass) ?
return (KotlinDetector.isKotlinType(factoryImplementationClass) ?
KotlinDelegate.findPrimaryConstructor(factoryImplementationClass) : null);
}
private static boolean isKotlinType(Class<?> factoryImplementationClass) {
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
}
private static @Nullable Constructor<?> findSingleConstructor(Constructor<?>[] constructors) {
return (constructors.length == 1 ? constructors[0] : null);
}