Polishing.

Guard usage of KotlinReflectUtils with type presence check.
Extend tests to cover primitive arrays.
Move methods from KotlinValueUtils to KotlinReflectUtils.
Move copy value cache to KotlinCopyMethod.

Original Pull Request: #2866
This commit is contained in:
Christoph Strobl
2023-06-29 14:14:57 +02:00
parent 80ca2b47ca
commit 09281c7182
10 changed files with 149 additions and 53 deletions

View File

@@ -25,6 +25,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
@@ -74,7 +75,7 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
return;
}
if (KotlinReflectionUtils.isDataClass(property.getOwner().getType())) {
if (KotlinDetector.isKotlinPresent() && KotlinReflectionUtils.isDataClass(property.getOwner().getType())) {
this.bean = (T) KotlinCopyUtil.setProperty(property, bean, value);
return;
@@ -154,8 +155,6 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
*/
static class KotlinCopyUtil {
private static final Map<Class<?>, KCallable<?>> copyMethodCache = new ConcurrentReferenceHashMap<>();
/**
* Set a single property by calling {@code copy(…)} on a Kotlin data class. Copying creates a new instance that
* holds all values of the original instance and the newly set {@link PersistentProperty} value.
@@ -165,7 +164,7 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
static <T> Object setProperty(PersistentProperty<?> property, T bean, @Nullable Object value) {
Class<?> type = property.getOwner().getType();
KCallable<?> copy = copyMethodCache.computeIfAbsent(type, it -> getCopyMethod(it, property));
KCallable<?> copy = getCopyMethod(type, property);
if (copy == null) {
throw new UnsupportedOperationException(String.format("Kotlin class %s has no .copy(…) method for property %s",

View File

@@ -30,6 +30,7 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -38,17 +39,22 @@ import org.springframework.core.ResolvableType;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.util.KotlinReflectionUtils;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentReferenceHashMap;
/**
* Value object to represent a Kotlin {@code copy} method. The lookup requires a {@code copy} method that matches the
* primary constructor of the class regardless of whether the primary constructor is the persistence constructor.
*
* @author Mark Paluch
* @author Christoph Strobl
* @since 2.1
*/
class KotlinCopyMethod {
private static final Map<Class<?>, Optional<KotlinCopyMethod>> COPY_METHOD_CACHE = new ConcurrentReferenceHashMap<>();
private final Method publicCopyMethod;
private final Method syntheticCopyMethod;
private final int parameterCount;
@@ -78,15 +84,17 @@ class KotlinCopyMethod {
Assert.notNull(type, "Type must not be null");
Optional<Method> syntheticCopyMethod = findSyntheticCopyMethod(type);
return COPY_METHOD_CACHE.computeIfAbsent(type, it -> {
if (!syntheticCopyMethod.isPresent()) {
return Optional.empty();
}
Optional<Method> syntheticCopyMethod = findSyntheticCopyMethod(type);
Optional<Method> publicCopyMethod = syntheticCopyMethod.flatMap(KotlinCopyMethod::findPublicCopyMethod);
if (!syntheticCopyMethod.isPresent()) {
return Optional.empty();
}
return publicCopyMethod.map(method -> new KotlinCopyMethod(method, syntheticCopyMethod.get()));
Optional<Method> publicCopyMethod = syntheticCopyMethod.flatMap(KotlinCopyMethod::findPublicCopyMethod);
return publicCopyMethod.map(method -> new KotlinCopyMethod(method, syntheticCopyMethod.get()));
});
}
public Method getPublicCopyMethod() {
@@ -171,7 +179,7 @@ class KotlinCopyMethod {
return Optional.empty();
}
boolean usesValueClasses = KotlinValueUtils.hasValueClassProperty(type);
boolean usesValueClasses = KotlinReflectionUtils.hasValueClassProperty(type);
List<KParameter> constructorArguments = getComponentArguments(primaryConstructor);
Predicate<String> isCopyMethod;
@@ -242,7 +250,7 @@ class KotlinCopyMethod {
return Optional.empty();
}
boolean usesValueClasses = KotlinValueUtils.hasValueClassProperty(type);
boolean usesValueClasses = KotlinReflectionUtils.hasValueClassProperty(type);
Predicate<String> isCopyMethod = usesValueClasses ? (it -> it.startsWith("copy-") && it.endsWith("$default"))
: (it -> it.equals("copy$default"));
@@ -277,7 +285,7 @@ class KotlinCopyMethod {
KParameter kParameter = constructorArguments.get(i);
if (KotlinValueUtils.isValueClass(kParameter.getType())) {
if (KotlinReflectionUtils.isValueClass(kParameter.getType())) {
// sigh. This can require deep unwrapping because the public vs. the synthetic copy methods use different
// parameter types.
continue;

View File

@@ -31,7 +31,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.core.KotlinDetector;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -43,39 +42,6 @@ import org.springframework.util.Assert;
*/
class KotlinValueUtils {
/**
* 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.
*/
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.
*/
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;
}
/**
* Creates a value hierarchy across value types from a given {@link KParameter} for COPY method usage.
*
@@ -122,9 +88,10 @@ class KotlinValueUtils {
public boolean shouldApplyBoxing(KType type, boolean optional, KParameter component) {
Type javaType = ReflectJvmMapping.getJavaType(component.getType());
boolean isPrimitive = javaType instanceof Class<?> c && c.isPrimitive();
if (type.isMarkedNullable() || optional) {
boolean isPrimitive = javaType instanceof Class<?> c && c.isPrimitive();
return (isPrimitive && type.isMarkedNullable()) || component.getType().isMarkedNullable();
}

View File

@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.core.KotlinDetector;
import org.springframework.data.mapping.FactoryMethod;
import org.springframework.data.mapping.InstanceCreatorMetadata;
import org.springframework.data.mapping.PersistentEntity;
@@ -117,7 +118,7 @@ public class MappingInstantiationException extends RuntimeException {
Constructor<?> constructor = preferredConstructor.getConstructor();
if (KotlinReflectionUtils.isSupportedKotlinClass(constructor.getDeclaringClass())) {
if (KotlinDetector.isKotlinPresent() && KotlinReflectionUtils.isSupportedKotlinClass(constructor.getDeclaringClass())) {
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(constructor);
@@ -133,7 +134,7 @@ public class MappingInstantiationException extends RuntimeException {
Method constructor = factoryMethod.getFactoryMethod();
if (KotlinReflectionUtils.isSupportedKotlinClass(constructor.getDeclaringClass())) {
if (KotlinDetector.isKotlinPresent() && KotlinReflectionUtils.isSupportedKotlinClass(constructor.getDeclaringClass())) {
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(constructor);

View File

@@ -29,6 +29,7 @@ import java.util.List;
import java.util.Optional;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.KotlinDetector;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.annotation.PersistenceCreator;
@@ -220,6 +221,10 @@ public interface PreferredConstructorDiscoverer {
* @return the appropriate discoverer for {@code type}.
*/
private static Discoverers findDiscoverer(Class<?> type) {
if(!KotlinDetector.isKotlinPresent()) {
return DEFAULT;
}
return KotlinReflectionUtils.isSupportedKotlinClass(type) ? KOTLIN : DEFAULT;
}

View File

@@ -22,6 +22,7 @@ import java.util.Map;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.KotlinDetector;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -58,7 +59,7 @@ public class MethodInvocationValidator implements MethodInterceptor {
*/
public static boolean supports(Class<?> repositoryInterface) {
return KotlinReflectionUtils.isSupportedKotlinClass(repositoryInterface)
return KotlinDetector.isKotlinPresent() && KotlinReflectionUtils.isSupportedKotlinClass(repositoryInterface)
|| NullableUtils.isNonNull(repositoryInterface, ElementType.METHOD)
|| NullableUtils.isNonNull(repositoryInterface, ElementType.PARAMETER);
}

View File

@@ -37,7 +37,7 @@ import org.springframework.lang.Nullable;
/**
* Reflection utility methods specific to Kotlin reflection. Requires Kotlin classes to be present to avoid linkage
* errors.
* errors - ensure to guard usage with {@link KotlinDetector#isKotlinPresent()}.
*
* @author Mark Paluch
* @author Christoph Strobl
@@ -132,6 +132,42 @@ public final class KotlinReflectionUtils {
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.

View File

@@ -21,6 +21,7 @@ import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Predicate;
import org.springframework.core.KotlinDetector;
import org.springframework.util.Assert;
/**
@@ -45,7 +46,7 @@ public interface Predicates {
Predicate<Member> IS_PUBLIC = member -> Modifier.isPublic(member.getModifiers());
Predicate<Member> IS_SYNTHETIC = Member::isSynthetic;
Predicate<Class<?>> IS_KOTLIN = KotlinReflectionUtils::isSupportedKotlinClass;
Predicate<Class<?>> IS_KOTLIN = KotlinDetector.isKotlinPresent() ? KotlinReflectionUtils::isSupportedKotlinClass : type -> false;
Predicate<Member> IS_STATIC = member -> Modifier.isStatic(member.getModifiers());
Predicate<Method> IS_BRIDGE_METHOD = Method::isBridge;