Browse Source

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
pull/2881/head
Christoph Strobl 3 years ago
parent
commit
09281c7182
No known key found for this signature in database
GPG Key ID: 8CC1AB53391458C8
  1. 7
      src/main/java/org/springframework/data/mapping/model/BeanWrapper.java
  2. 26
      src/main/java/org/springframework/data/mapping/model/KotlinCopyMethod.java
  3. 37
      src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java
  4. 5
      src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java
  5. 5
      src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java
  6. 3
      src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java
  7. 38
      src/main/java/org/springframework/data/util/KotlinReflectionUtils.java
  8. 3
      src/main/java/org/springframework/data/util/Predicates.java
  9. 19
      src/test/kotlin/org/springframework/data/mapping/model/InlineClasses.kt
  10. 59
      src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt

7
src/main/java/org/springframework/data/mapping/model/BeanWrapper.java

@ -25,6 +25,7 @@ import java.util.LinkedHashMap; @@ -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> { @@ -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> { @@ -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> { @@ -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",

26
src/main/java/org/springframework/data/mapping/model/KotlinCopyMethod.java

@ -30,6 +30,7 @@ import java.lang.reflect.Type; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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;

37
src/main/java/org/springframework/data/mapping/model/KotlinValueUtils.java

@ -31,7 +31,6 @@ import java.util.ArrayList; @@ -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; @@ -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 { @@ -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();
}

5
src/main/java/org/springframework/data/mapping/model/MappingInstantiationException.java

@ -24,6 +24,7 @@ import java.util.ArrayList; @@ -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 { @@ -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 { @@ -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);

5
src/main/java/org/springframework/data/mapping/model/PreferredConstructorDiscoverer.java

@ -29,6 +29,7 @@ import java.util.List; @@ -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 { @@ -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;
}

3
src/main/java/org/springframework/data/repository/core/support/MethodInvocationValidator.java

@ -22,6 +22,7 @@ import java.util.Map; @@ -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 { @@ -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);
}

38
src/main/java/org/springframework/data/util/KotlinReflectionUtils.java

@ -37,7 +37,7 @@ import org.springframework.lang.Nullable; @@ -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 { @@ -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.

3
src/main/java/org/springframework/data/util/Predicates.java

@ -21,6 +21,7 @@ import java.lang.reflect.Method; @@ -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 { @@ -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;

19
src/test/kotlin/org/springframework/data/mapping/model/InlineClasses.kt

@ -89,6 +89,25 @@ data class WithPrimitiveNullableValue( @@ -89,6 +89,25 @@ data class WithPrimitiveNullableValue(
// copy: copy-lcs_1S0$default(WithPrimitiveNullableValue var0, PrimitiveNullableValue var1, PrimitiveNullableValue var2, PrimitiveNullableValue var3, PrimitiveNullableValue var4, int var5, Object var6)
)
@JvmInline
value class PrimitiveArrayValue(val ids: IntArray)
@JvmInline
value class PrimitiveNullableArrayValue(val ids: IntArray?)
data class WithPrimitiveArrays(
// ctor WithPrimitiveArrays(int[], int[], int[], int[], int[], int, DefaultConstructorMarker)
val pa: PrimitiveArrayValue,
val pan: PrimitiveArrayValue?,
val pna: PrimitiveNullableArrayValue,
val pad: PrimitiveArrayValue = PrimitiveArrayValue(intArrayOf(1, 2, 3)),
val pand: PrimitiveArrayValue? = PrimitiveArrayValue(intArrayOf(1, 2, 3))
// copy: copy-NCSWWqw$default(WithPrimitiveArrays var0, int[] var1, int[] var2, PrimitiveNullableArrayValue var3, int[] var4, int[] var5, int var4, Object var5) {
)
@JvmInline
value class PrimitiveValue(val id: Int)

59
src/test/kotlin/org/springframework/data/mapping/model/KotlinValueUtilsUnitTests.kt

@ -145,6 +145,65 @@ class KotlinValueUtilsUnitTests { @@ -145,6 +145,65 @@ class KotlinValueUtilsUnitTests {
assertThat(nvdn.appliesBoxing()).isTrue
}
@Test // GH-1947
internal fun inlinesTypesToPrimitiveArrayCopyRules() {
val copy = KotlinCopyMethod.findCopyMethod(WithPrimitiveArrays::class.java).get();
assertThat(copy.syntheticCopyMethod.toString()).contains("(org.springframework.data.mapping.model.WithPrimitiveArrays,int[],int[],org.springframework.data.mapping.model.PrimitiveNullableArrayValue,int[],int[],int,java.lang.Object)")
val parameters = copy.copyFunction.parameters;
val pa = KotlinValueUtils.getConstructorValueHierarchy(parameters[1]);
assertThat(pa.actualType).isEqualTo(IntArray::class.java)
assertThat(pa.appliesBoxing()).isFalse
val pan = KotlinValueUtils.getConstructorValueHierarchy(parameters[2]);
assertThat(pan.actualType).isEqualTo(IntArray::class.java)
assertThat(pan.appliesBoxing()).isFalse
val pna = KotlinValueUtils.getConstructorValueHierarchy(parameters[3]);
assertThat(pna.actualType).isEqualTo(IntArray::class.java)
assertThat(pna.parameterType).isEqualTo(PrimitiveNullableArrayValue::class.java)
assertThat(pna.appliesBoxing()).isTrue
val pad = KotlinValueUtils.getConstructorValueHierarchy(parameters[4]);
assertThat(pad.actualType).isEqualTo(IntArray::class.java)
assertThat(pad.appliesBoxing()).isFalse
val pand = KotlinValueUtils.getConstructorValueHierarchy(parameters[5]);
assertThat(pand.actualType).isEqualTo(IntArray::class.java)
assertThat(pand.appliesBoxing()).isFalse
}
@Test // GH-1947
internal fun inlinesPrimitiveArrayConstructorRules() {
val ctor = WithPrimitiveArrays::class.constructors.iterator().next();
assertThat(ctor.javaConstructor.toString()).contains("(int[],int[],int[],int[],int[],kotlin.jvm.internal.DefaultConstructorMarker)")
val iterator = ctor.parameters.iterator()
val pa = KotlinValueUtils.getConstructorValueHierarchy(iterator.next());
assertThat(pa.actualType).isEqualTo(IntArray::class.java)
assertThat(pa.appliesBoxing()).isFalse
val pan = KotlinValueUtils.getConstructorValueHierarchy(iterator.next());
assertThat(pan.actualType).isEqualTo(IntArray::class.java)
assertThat(pan.appliesBoxing()).isFalse
val pna = KotlinValueUtils.getConstructorValueHierarchy(iterator.next());
assertThat(pna.actualType).isEqualTo(IntArray::class.java)
assertThat(pna.appliesBoxing()).isFalse
val pad = KotlinValueUtils.getConstructorValueHierarchy(iterator.next());
assertThat(pad.actualType).isEqualTo(IntArray::class.java)
assertThat(pad.appliesBoxing()).isFalse
val pand = KotlinValueUtils.getConstructorValueHierarchy(iterator.next());
assertThat(pand.actualType).isEqualTo(IntArray::class.java)
assertThat(pand.appliesBoxing()).isFalse
}
@Test // GH-1947
internal fun inlinesGenericTypesConstructorRules() {

Loading…
Cancel
Save