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:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user