Skip to content

Commit

Permalink
Refine KotlinDetector usages and implementation
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sdeleuze committed Jan 20, 2025
1 parent ffd7b93 commit 1763334
Show file tree
Hide file tree
Showing 15 changed files with 56 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ public static <T> T instantiateClass(Constructor<T> ctor, @Nullable Object... ar
Assert.notNull(ctor, "Constructor must not be null");
try {
ReflectionUtils.makeAccessible(ctor);
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
if (KotlinDetector.isKotlinType(ctor.getDeclaringClass())) {
return KotlinDelegate.instantiateClass(ctor, args);
}
else {
Expand Down Expand Up @@ -279,7 +279,7 @@ else if (ctors.length == 0) {
*/
public static <T> @Nullable Constructor<T> findPrimaryConstructor(Class<T> clazz) {
Assert.notNull(clazz, "Class must not be null");
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(clazz)) {
if (KotlinDetector.isKotlinType(clazz)) {
return KotlinDelegate.findPrimaryConstructor(clazz);
}
if (clazz.isRecord()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -160,7 +160,7 @@ private CodeBlock generateCodeForConstructor(RegisteredBean registeredBean, Cons
registeredBean.getBeanName(), constructor, registeredBean.getBeanClass());

Class<?> publicType = descriptor.publicType();
if (KotlinDetector.isKotlinReflectPresent() && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
if (KotlinDetector.isKotlinType(publicType) && KotlinDelegate.hasConstructorWithOptionalParameter(publicType)) {
return generateCodeForInaccessibleConstructor(descriptor,
hints -> hints.registerType(publicType, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
}
Expand Down Expand Up @@ -408,13 +408,11 @@ private boolean isThrowingCheckedException(Executable executable) {
private static class KotlinDelegate {

public static boolean hasConstructorWithOptionalParameter(Class<?> beanClass) {
if (KotlinDetector.isKotlinType(beanClass)) {
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(beanClass);
for (KFunction<?> constructor : kClass.getConstructors()) {
for (KParameter parameter : constructor.getParameters()) {
if (parameter.isOptional()) {
return true;
}
KClass<?> kClass = JvmClassMappingKt.getKotlinClass(beanClass);
for (KFunction<?> constructor : kClass.getConstructors()) {
for (KParameter parameter : constructor.getParameters()) {
if (parameter.isOptional()) {
return true;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -163,9 +163,7 @@ public boolean isRequired() {

if (this.field != null) {
return !(this.field.getType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(this.field.getDeclaringClass()) &&
KotlinDelegate.isNullable(this.field)));
(KotlinDetector.isKotlinType(this.field.getDeclaringClass()) && KotlinDelegate.isNullable(this.field)));
}
else {
return !obtainMethodParameter().isOptional();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -624,7 +624,7 @@ else if (void.class == factoryMethodToUse.getReturnType()) {
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
factoryClass.getName() + "]: needs to have a non-void return type!");
}
else if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(factoryMethodToUse)) {
else if (KotlinDetector.isSuspendingFunction(factoryMethodToUse)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Invalid factory method '" + mbd.getFactoryMethodName() + "' on class [" +
factoryClass.getName() + "]: suspending functions are not supported!");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1092,7 +1092,7 @@ private class ReactiveCachingHandler {
() -> Mono.from(adapter.toPublisher(invokeOperation(invoker))).toFuture())));
}
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isSuspendingFunction(method)) {
if (KotlinDetector.isSuspendingFunction(method)) {
return Mono.fromFuture(cache.retrieve(key, () -> {
Mono<?> mono = ((Mono<?>) invokeOperation(invoker));
if (mono == null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -81,7 +81,7 @@ abstract class ScheduledAnnotationReactiveSupport {
* Kotlin coroutines bridge are not present at runtime
*/
public static boolean isReactive(Method method) {
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(method)) {
if (KotlinDetector.isSuspendingFunction(method)) {
// Note that suspending functions declared without args have a single Continuation
// parameter in reflective inspection
Assert.isTrue(method.getParameterCount() == 1,
Expand Down Expand Up @@ -138,7 +138,7 @@ public static Runnable createSubscriptionRunnable(Method method, Object targetBe
* to a {@code Flux} with a checkpoint String, allowing for better debugging.
*/
static Publisher<?> getPublisherFor(Method method, Object bean) {
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isSuspendingFunction(method)) {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, bean, (Object[]) method.getParameters());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -24,7 +24,8 @@
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
Expand All @@ -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;

Expand All @@ -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 {
Expand All @@ -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;
}


Expand Down Expand Up @@ -89,21 +100,19 @@ public static boolean isKotlinReflectPresent() {
* 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);
}

/**
* Return {@code true} if the method is a suspending function.
* @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);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,7 @@ private MethodParameter nested(int nestingLevel, @Nullable Integer typeIndex) {
*/
public boolean isOptional() {
return (getParameterType() == Optional.class || hasNullableAnnotation() ||
(KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(getContainingClass()) &&
KotlinDelegate.isOptional(this)));
(KotlinDetector.isKotlinType(getContainingClass()) && KotlinDelegate.isOptional(this)));
}

/**
Expand Down Expand Up @@ -508,8 +506,8 @@ public Type getGenericParameterType() {
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();
Expand All @@ -536,7 +534,7 @@ private Class<?> computeParameterType() {
if (method == null) {
return void.class;
}
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(getContainingClass())) {
if (KotlinDetector.isKotlinType(getContainingClass())) {
return KotlinDelegate.getReturnType(method);
}
return method.getReturnType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ private FactoryInstantiator(Constructor<T> constructor) {

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);
Expand Down Expand Up @@ -408,14 +408,10 @@ static <T> FactoryInstantiator<T> forClass(Class<?> factoryImplementationClass)
}

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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -558,8 +558,7 @@ public PropertyAccessor createOptimalAccessor(EvaluationContext context, @Nullab

private static boolean isKotlinProperty(Method method, String methodSuffix) {
Class<?> clazz = method.getDeclaringClass();
return KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(clazz) &&
return KotlinDetector.isKotlinType(clazz) &&
KotlinDelegate.isKotlinProperty(method, methodSuffix);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -868,8 +868,7 @@ private void registerWellKnownModulesIfAvailable(MultiValueMap<Object, Module> m
// jackson-datatype-jsr310 not available
}

// Kotlin present?
if (KotlinDetector.isKotlinPresent()) {
if (KotlinDetector.isKotlinReflectPresent()) {
try {
Class<? extends Module> kotlinModuleClass = (Class<? extends Module>)
ClassUtils.forName("com.fasterxml.jackson.module.kotlin.KotlinModule", this.moduleClassLoader);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -103,8 +103,7 @@ public AbstractNamedValueMethodArgumentResolver(@Nullable ConfigurableBeanFactor

NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
boolean hasDefaultValue = KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(nestedParameter);

Object resolvedName = resolveEmbeddedValuesAndExpressions(namedValueInfo.name);
Expand Down Expand Up @@ -276,7 +275,7 @@ else if (paramType.isPrimitive()) {

WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
Class<?> parameterType = parameter.getParameterType();
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
if (KotlinDetector.isInlineClass(parameterType)) {
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
if (ctor != null) {
parameterType = ctor.getParameterTypes()[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,11 @@ public void setMethodValidator(@Nullable MethodValidator methodValidator) {
protected @Nullable Object doInvoke(@Nullable Object... args) throws Exception {
Method method = getBridgedMethod();
try {
if (KotlinDetector.isKotlinReflectPresent()) {
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
if (KotlinDetector.isSuspendingFunction(method)) {
return invokeSuspendingFunction(method, getBean(), args);
}
else if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
return KotlinDelegate.invokeFunction(method, getBean(), args);
}
return KotlinDelegate.invokeFunction(method, getBean(), args);
}
return method.invoke(getBean(), args);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -191,12 +191,9 @@ public Mono<HandlerResult> invoke(
Method method = getBridgedMethod();
boolean isSuspendingFunction = KotlinDetector.isSuspendingFunction(method);
try {
if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(method.getDeclaringClass())) {
value = KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange);
}
else {
value = method.invoke(getBean(), args);
}
value = (KotlinDetector.isKotlinType(method.getDeclaringClass()) ?
KotlinDelegate.invokeFunction(method, getBean(), args, isSuspendingFunction, exchange) :
method.invoke(getBean(), args));
}
catch (IllegalArgumentException ex) {
assertTargetBean(getBridgedMethod(), getBean(), args);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -195,7 +195,7 @@ private NamedValueInfo updateNamedValueInfo(MethodParameter parameter, NamedValu

WebDataBinder binder = bindingContext.createDataBinder(exchange, namedValueInfo.name);
Class<?> parameterType = parameter.getParameterType();
if (KotlinDetector.isKotlinPresent() && KotlinDetector.isInlineClass(parameterType)) {
if (KotlinDetector.isInlineClass(parameterType)) {
Constructor<?> ctor = BeanUtils.findPrimaryConstructor(parameterType);
if (ctor != null) {
parameterType = ctor.getParameterTypes()[0];
Expand All @@ -222,8 +222,7 @@ private Mono<Object> getDefaultValue(NamedValueInfo namedValueInfo, String resol

return Mono.fromSupplier(() -> {
Object value = null;
boolean hasDefaultValue = KotlinDetector.isKotlinReflectPresent() &&
KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
boolean hasDefaultValue = KotlinDetector.isKotlinType(parameter.getDeclaringClass()) &&
KotlinDelegate.hasDefaultValue(parameter);
if (namedValueInfo.defaultValue != null) {
value = resolveEmbeddedValuesAndExpressions(namedValueInfo.defaultValue);
Expand Down

0 comments on commit 1763334

Please sign in to comment.