DATACMNS-1391 - Use prefered constructors to discover Kotlin's copy method.
We now use the primary constructor of Kotlin classes to discover the copy method. We use the primary constructor args to compare signatures and only use the copy method that takes all parameters in the constructor args order. This allows to find the appropriate copy method in case the class declares multiple copy methods. We now also cache the copy method (KCallable) to reduce lookups in BeanWrapper. Original pull request: #312.
This commit is contained in:
committed by
Oliver Gierke
parent
a31ac40c03
commit
7eacab6a34
@@ -15,9 +15,7 @@
|
||||
*/
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.reflect.KCallable;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.KParameter.Kind;
|
||||
|
||||
@@ -32,6 +30,7 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ConcurrentReferenceHashMap;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
@@ -165,6 +164,8 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
|
||||
*/
|
||||
static class KotlinCopyUtil {
|
||||
|
||||
private static final Map<Class<?>, KCallable<?>> COPY_METHOD_CACHE = 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.
|
||||
@@ -174,12 +175,11 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
|
||||
static <T> Object setProperty(PersistentProperty<?> property, T bean, @Nullable Object value) {
|
||||
|
||||
Class<?> type = property.getOwner().getType();
|
||||
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(type);
|
||||
KCallable<?> copy = getCopyMethod(kotlinClass);
|
||||
KCallable<?> copy = COPY_METHOD_CACHE.computeIfAbsent(type, it -> getCopyMethod(it, property));
|
||||
|
||||
if (copy == null) {
|
||||
throw new UnsupportedOperationException(String.format(
|
||||
"Kotlin class %s has no .copy(…) method. Cannot set property %s!", type.getName(), property.getName()));
|
||||
"Kotlin class %s has no .copy(…) method for property %s!", type.getName(), property.getName()));
|
||||
}
|
||||
|
||||
return copy.callBy(getCallArgs(copy, property, bean, value));
|
||||
@@ -198,7 +198,8 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
|
||||
args.put(parameter, bean);
|
||||
}
|
||||
|
||||
if (parameter.getKind() == Kind.VALUE && parameter.getName().equals(property.getName())) {
|
||||
if (parameter.getKind() == Kind.VALUE && parameter.getName() != null
|
||||
&& parameter.getName().equals(property.getName())) {
|
||||
args.put(parameter, value);
|
||||
}
|
||||
}
|
||||
@@ -206,16 +207,9 @@ class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KCallable<?> getCopyMethod(KClass<?> kotlinClass) {
|
||||
|
||||
for (KCallable<?> kCallable : kotlinClass.getMembers()) {
|
||||
|
||||
if (kCallable.getName().equals("copy")) {
|
||||
return kCallable;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
private static KCallable<?> getCopyMethod(Class<?> type, PersistentProperty<?> property) {
|
||||
return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property))
|
||||
.map(KotlinCopyMethod::getCopyFunction).orElse(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,6 @@ package org.springframework.data.mapping.model;
|
||||
import static org.springframework.asm.Opcodes.*;
|
||||
import static org.springframework.data.mapping.model.BytecodeUtil.*;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.KParameter.Kind;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@@ -34,7 +28,6 @@ import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
@@ -44,10 +37,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.asm.ClassWriter;
|
||||
import org.springframework.asm.Label;
|
||||
@@ -60,6 +50,7 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.SimpleAssociationHandler;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.data.mapping.model.KotlinCopyMethod.KotlinCopyByProperty;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -1010,7 +1001,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
|
||||
visitWithProperty(entity, property, mv, internalClassName, wither);
|
||||
}
|
||||
|
||||
if (hasKotlinCopyMethod(entity.getType())) {
|
||||
if (hasKotlinCopyMethod(property)) {
|
||||
visitKotlinCopy(entity, property, mv, internalClassName);
|
||||
}
|
||||
|
||||
@@ -1079,31 +1070,36 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
|
||||
private static void visitKotlinCopy(PersistentEntity<?, ?> entity, PersistentProperty<?> property, MethodVisitor mv,
|
||||
String internalClassName) {
|
||||
|
||||
KotlinCopyMethod kotlinCopyMethod = KotlinCopyMethod.findCopyMethod(entity.getType())
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
String.format("No usable .copy(…) method found in entity %s", entity.getType().getName())));
|
||||
|
||||
// this. <- for later PUTFIELD
|
||||
mv.visitVarInsn(ALOAD, 0);
|
||||
|
||||
Optional<Method> publicCopy = findPublicCopyMethod(entity.getType());
|
||||
Optional<Method> defaultedCopy = findDefaultCopyMethod(entity.getType());
|
||||
if (kotlinCopyMethod.shouldUsePublicCopyMethod(entity)) {
|
||||
|
||||
if (publicCopy.filter(it -> it.getParameterCount() == 1 && !Modifier.isStatic(it.getModifiers())).isPresent()) {
|
||||
|
||||
// PersonWithId.copy$default..(bean, object, MASK, null)
|
||||
// PersonWithId.copy$(value)
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
|
||||
visitInvokeMethodSingleArg(mv, publicCopy.get());
|
||||
visitInvokeMethodSingleArg(mv, kotlinCopyMethod.getPublicCopyMethod());
|
||||
} else {
|
||||
|
||||
Method copy = defaultedCopy.get();
|
||||
Method copy = kotlinCopyMethod.getSyntheticCopyMethod();
|
||||
Class<?>[] parameterTypes = copy.getParameterTypes();
|
||||
// PersonWithId.copy$default...(object)
|
||||
|
||||
// PersonWithId.copy$default..(bean, object, MASK, null)
|
||||
mv.visitVarInsn(ALOAD, 3);
|
||||
|
||||
KotlinDefaultCopyMethod defaultMethod = new KotlinDefaultCopyMethod(entity.getType());
|
||||
KotlinCopyByProperty kotlinCopyByProperty = defaultMethod.forProperty(property);
|
||||
for (int i = 1; i < defaultMethod.getParameterCount(); i++) {
|
||||
KotlinCopyByProperty copyByProperty = kotlinCopyMethod.forProperty(property)
|
||||
.orElseThrow(() -> new IllegalStateException(
|
||||
String.format("No usable .copy(…) method found for property %s", property)));
|
||||
|
||||
if (kotlinCopyByProperty.getParameterPosition() == i) {
|
||||
for (int i = 1; i < kotlinCopyMethod.getParameterCount(); i++) {
|
||||
|
||||
if (copyByProperty.getParameterPosition() == i) {
|
||||
|
||||
mv.visitVarInsn(ALOAD, 2);
|
||||
|
||||
@@ -1116,7 +1112,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
|
||||
visitDefaultValue(parameterTypes[i], mv);
|
||||
}
|
||||
|
||||
kotlinCopyByProperty.getDefaultMask().forEach(i -> {
|
||||
copyByProperty.getDefaultMask().forEach(i -> {
|
||||
mv.visitIntInsn(Opcodes.SIPUSH, i);
|
||||
});
|
||||
|
||||
@@ -1414,7 +1410,7 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
|
||||
return true;
|
||||
}
|
||||
|
||||
if (hasKotlinCopyMethod(property.getOwner().getType())) {
|
||||
if (hasKotlinCopyMethod(property)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1424,175 +1420,20 @@ public class ClassGeneratingPropertyAccessorFactory implements PersistentPropert
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the Kotlin {@literal copy} method or {@literal copy} method with parameter defaulting.
|
||||
* Check whether the owning type of {@link PersistentProperty} declares a {@literal copy} method or {@literal copy}
|
||||
* method with parameter defaulting.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
static Method findCopyMethod(Class<?> type) {
|
||||
private static boolean hasKotlinCopyMethod(PersistentProperty<?> property) {
|
||||
|
||||
Optional<Method> singleCopy = findPublicCopyMethod(type);
|
||||
|
||||
return singleCopy.orElseGet(() -> findDefaultCopyMethod(type).orElse(null));
|
||||
}
|
||||
|
||||
private static Optional<Method> findPublicCopyMethod(Class<?> type) {
|
||||
|
||||
return Stream.concat(Arrays.stream(type.getDeclaredMethods()), Arrays.stream(type.getMethods()))
|
||||
.filter(it -> it.getName().equals("copy") //
|
||||
&& !it.isSynthetic() //
|
||||
&& !it.isBridge() //
|
||||
&& !Modifier.isStatic(it.getModifiers()) //
|
||||
&& it.getReturnType().equals(type))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
private static Optional<Method> findDefaultCopyMethod(Class<?> type) {
|
||||
|
||||
return Stream.concat(Arrays.stream(type.getDeclaredMethods()), Arrays.stream(type.getMethods()))
|
||||
.filter(it -> it.getName().equals("copy$default") //
|
||||
&& it.isSynthetic() //
|
||||
&& it.isBridge() //
|
||||
&& Modifier.isStatic(it.getModifiers()) //
|
||||
&& it.getReturnType().equals(type))
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@link Class} declares a {@literal copy} method or {@literal copy} method with parameter
|
||||
* defaulting.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private static boolean hasKotlinCopyMethod(Class<?> type) {
|
||||
Class<?> type = property.getOwner().getType();
|
||||
|
||||
if (isAccessible(type) && org.springframework.data.util.ReflectionUtils.isKotlinClass(type)) {
|
||||
return findCopyMethod(type) != null;
|
||||
return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property)).isPresent();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to represent a Kotlin default method.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Getter
|
||||
static class KotlinDefaultCopyMethod {
|
||||
|
||||
private final int parameterCount;
|
||||
private final KFunction<?> copyFunction;
|
||||
|
||||
public KotlinDefaultCopyMethod(Class<?> type) {
|
||||
|
||||
Method copyMethod = findPublicCopyMethod(type)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Cannot resolve public Kotlin copy() method!"));
|
||||
|
||||
this.copyFunction = ReflectJvmMapping.getKotlinFunction(copyMethod);
|
||||
this.parameterCount = copyFunction.getParameters().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create metadata for {@literal copy$default} invocation.
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
public KotlinCopyByProperty forProperty(PersistentProperty<?> property) {
|
||||
return new KotlinCopyByProperty(copyFunction, property);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to represent Kotlin {@literal copy$default} invocation metadata.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Getter
|
||||
static class KotlinCopyByProperty {
|
||||
|
||||
private final int parameterPosition;
|
||||
private final int parameterCount;
|
||||
private final KotlinDefaultMask defaultMask;
|
||||
|
||||
KotlinCopyByProperty(KFunction<?> copyFunction, PersistentProperty<?> property) {
|
||||
|
||||
this.parameterPosition = findIndex(copyFunction, property.getName());
|
||||
this.parameterCount = copyFunction.getParameters().size();
|
||||
this.defaultMask = KotlinDefaultMask.from(copyFunction, it -> property.getName().equals(it.getName()));
|
||||
}
|
||||
|
||||
private static int findIndex(KFunction<?> function, String parameterName) {
|
||||
|
||||
for (KParameter parameter : function.getParameters()) {
|
||||
if (parameterName.equals(parameter.getName())) {
|
||||
return parameter.getIndex();
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot resolve parameter name %s to a index in method %s!",
|
||||
parameterName, function.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object representing defaulting masks used for Kotlin methods applying parameter defaulting.
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
static class KotlinDefaultMask {
|
||||
|
||||
private final int[] defaulting;
|
||||
|
||||
/**
|
||||
* Callback method to notify {@link IntConsumer} for each defaulting mask.
|
||||
*
|
||||
* @param maskCallback must not be {@literal null}.
|
||||
*/
|
||||
public void forEach(IntConsumer maskCallback) {
|
||||
|
||||
for (int i : defaulting) {
|
||||
maskCallback.accept(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates defaulting mask(s) used to invoke Kotlin {@literal default} methods that conditionally apply parameter
|
||||
* values.
|
||||
*
|
||||
* @param function the {@link KFunction} that should be invoked.
|
||||
* @param isPresent {@link Predicate} for the presence/absence of parameters.
|
||||
* @return {@link KotlinDefaultMask}.
|
||||
*/
|
||||
public static KotlinDefaultMask from(KFunction<?> function, Predicate<KParameter> isPresent) {
|
||||
|
||||
List<Integer> masks = new ArrayList<>();
|
||||
int index = 0;
|
||||
int mask = 0;
|
||||
|
||||
List<KParameter> parameters = function.getParameters();
|
||||
|
||||
for (KParameter parameter : parameters) {
|
||||
|
||||
if (index != 0 && index % Integer.SIZE == 0) {
|
||||
masks.add(mask);
|
||||
mask = 0;
|
||||
}
|
||||
|
||||
if (parameter.isOptional() && !isPresent.test(parameter)) {
|
||||
mask = mask | (1 << (index % Integer.SIZE));
|
||||
}
|
||||
|
||||
if (parameter.getKind() == Kind.VALUE) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
masks.add(mask);
|
||||
|
||||
return new KotlinDefaultMask(masks.stream().mapToInt(i -> i).toArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import kotlin.jvm.JvmClassMappingKt;
|
||||
import kotlin.reflect.KClass;
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.KParameter.Kind;
|
||||
import kotlin.reflect.full.KClasses;
|
||||
import kotlin.reflect.jvm.ReflectJvmMapping;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.SimplePropertyHandler;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object to represent a Kotlin {@code copy} method.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.1
|
||||
*/
|
||||
@Getter
|
||||
class KotlinCopyMethod {
|
||||
|
||||
private final Method publicCopyMethod;
|
||||
private final Method syntheticCopyMethod;
|
||||
private final int parameterCount;
|
||||
private final KFunction<?> copyFunction;
|
||||
|
||||
/**
|
||||
* @param publicCopyMethod Compiler-generated public {@code copy} method accepting all properties.
|
||||
* @param syntheticCopyMethod Compiler-generated synthetic {@code copy$default} variant of the copy method accepting
|
||||
* the original instance and defaulting masks.
|
||||
*/
|
||||
private KotlinCopyMethod(Method publicCopyMethod, Method syntheticCopyMethod) {
|
||||
|
||||
this.publicCopyMethod = publicCopyMethod;
|
||||
this.syntheticCopyMethod = syntheticCopyMethod;
|
||||
this.copyFunction = ReflectJvmMapping.getKotlinFunction(publicCopyMethod);
|
||||
this.parameterCount = copyFunction.getParameters().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to lookup the Kotlin {@code copy} method. Lookup happens in two stages: Find the synthetic copy method and
|
||||
* then attempt to resolve its public variant.
|
||||
*
|
||||
* @param type the class.
|
||||
* @return {@link Optional} {@link KotlinCopyMethod}.
|
||||
*/
|
||||
public static Optional<KotlinCopyMethod> findCopyMethod(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
Optional<Method> syntheticCopyMethod = findSyntheticCopyMethod(type);
|
||||
|
||||
if (!syntheticCopyMethod.isPresent()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
Optional<Method> publicCopyMethod = syntheticCopyMethod.flatMap(KotlinCopyMethod::findPublicCopyMethod);
|
||||
|
||||
return publicCopyMethod.map(method -> new KotlinCopyMethod(method, syntheticCopyMethod.get()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the {@link PersistentProperty} is accepted as part of the copy method.
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
boolean supportsProperty(PersistentProperty<?> property) {
|
||||
return forProperty(property).isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create metadata for {@literal copy$default} invocation.
|
||||
*
|
||||
* @param property
|
||||
* @return
|
||||
*/
|
||||
Optional<KotlinCopyByProperty> forProperty(PersistentProperty<?> property) {
|
||||
|
||||
int index = KotlinCopyByProperty.findIndex(copyFunction, property.getName());
|
||||
|
||||
if (index == -1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(new KotlinCopyByProperty(copyFunction, property));
|
||||
}
|
||||
|
||||
boolean shouldUsePublicCopyMethod(PersistentEntity<?, ?> entity) {
|
||||
|
||||
List<PersistentProperty<?>> persistentProperties = new ArrayList<>();
|
||||
entity.doWithProperties((SimplePropertyHandler) persistentProperties::add);
|
||||
|
||||
if (persistentProperties.size() > 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (publicCopyMethod.getParameterCount() != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Modifier.isStatic(publicCopyMethod.getModifiers())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes = publicCopyMethod.getParameterTypes();
|
||||
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
if (!parameterTypes[i].equals(persistentProperties.get(i).getType())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Optional<Method> findPublicCopyMethod(Method defaultKotlinMethod) {
|
||||
|
||||
Class<?> type = defaultKotlinMethod.getDeclaringClass();
|
||||
KClass<?> kotlinClass = JvmClassMappingKt.getKotlinClass(type);
|
||||
|
||||
KFunction<?> primaryConstructor = KClasses.getPrimaryConstructor(kotlinClass);
|
||||
|
||||
if (primaryConstructor == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
List<KParameter> constructorArguments = primaryConstructor.getParameters() //
|
||||
.stream() //
|
||||
.filter(it -> it.getKind() == Kind.VALUE) //
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return Arrays.stream(type.getDeclaredMethods()).filter(it -> it.getName().equals("copy") //
|
||||
&& !it.isSynthetic() //
|
||||
&& !it.isBridge() //
|
||||
&& !Modifier.isStatic(it.getModifiers()) //
|
||||
&& it.getReturnType().equals(type) //
|
||||
&& it.getParameterCount() == constructorArguments.size()) //
|
||||
.filter(it -> {
|
||||
|
||||
KFunction<?> kotlinFunction = ReflectJvmMapping.getKotlinFunction(it);
|
||||
|
||||
if (kotlinFunction == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parameterMatches(constructorArguments, kotlinFunction);
|
||||
}).findFirst();
|
||||
}
|
||||
|
||||
private static boolean parameterMatches(List<KParameter> constructorArguments, KFunction<?> kotlinFunction) {
|
||||
|
||||
boolean foundInstance = false;
|
||||
int constructorArgIndex = 0;
|
||||
|
||||
for (KParameter parameter : kotlinFunction.getParameters()) {
|
||||
|
||||
if (parameter.getKind() == Kind.INSTANCE) {
|
||||
foundInstance = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (constructorArguments.size() <= constructorArgIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KParameter constructorParameter = constructorArguments.get(constructorArgIndex);
|
||||
|
||||
if (!constructorParameter.getName().equals(parameter.getName())
|
||||
|| !constructorParameter.getType().equals(parameter.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructorArgIndex++;
|
||||
}
|
||||
|
||||
return foundInstance;
|
||||
}
|
||||
|
||||
private static Optional<Method> findSyntheticCopyMethod(Class<?> type) {
|
||||
|
||||
return Arrays.stream(type.getDeclaredMethods()) //
|
||||
.filter(it -> it.getName().equals("copy$default") //
|
||||
&& Modifier.isStatic(it.getModifiers()) //
|
||||
&& it.getReturnType().equals(type))
|
||||
.filter(Method::isSynthetic) //
|
||||
.filter(Method::isBridge) //
|
||||
.findFirst();
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to represent Kotlin {@literal copy$default} invocation metadata.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Getter
|
||||
static class KotlinCopyByProperty {
|
||||
|
||||
private final int parameterPosition;
|
||||
private final int parameterCount;
|
||||
private final KotlinDefaultMask defaultMask;
|
||||
|
||||
KotlinCopyByProperty(KFunction<?> copyFunction, PersistentProperty<?> property) {
|
||||
|
||||
this.parameterPosition = findIndex(copyFunction, property.getName());
|
||||
this.parameterCount = copyFunction.getParameters().size();
|
||||
this.defaultMask = KotlinDefaultMask.from(copyFunction, it -> property.getName().equals(it.getName()));
|
||||
}
|
||||
|
||||
static int findIndex(KFunction<?> function, String parameterName) {
|
||||
|
||||
for (KParameter parameter : function.getParameters()) {
|
||||
if (parameterName.equals(parameter.getName())) {
|
||||
return parameter.getIndex();
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2018 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.mapping.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.IntConsumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import kotlin.reflect.KFunction;
|
||||
import kotlin.reflect.KParameter;
|
||||
import kotlin.reflect.KParameter.Kind;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Value object representing defaulting masks used for Kotlin methods applying parameter defaulting.
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
class KotlinDefaultMask {
|
||||
|
||||
private final int[] defaulting;
|
||||
|
||||
/**
|
||||
* Callback method to notify {@link IntConsumer} for each defaulting mask.
|
||||
*
|
||||
* @param maskCallback must not be {@literal null}.
|
||||
*/
|
||||
public void forEach(IntConsumer maskCallback) {
|
||||
|
||||
for (int i : defaulting) {
|
||||
maskCallback.accept(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates defaulting mask(s) used to invoke Kotlin {@literal default} methods that conditionally apply parameter
|
||||
* values.
|
||||
*
|
||||
* @param function the {@link KFunction} that should be invoked.
|
||||
* @param isPresent {@link Predicate} for the presence/absence of parameters.
|
||||
* @return {@link KotlinDefaultMask}.
|
||||
*/
|
||||
public static KotlinDefaultMask from(KFunction<?> function, Predicate<KParameter> isPresent) {
|
||||
|
||||
List<Integer> masks = new ArrayList<>();
|
||||
int index = 0;
|
||||
int mask = 0;
|
||||
|
||||
List<KParameter> parameters = function.getParameters();
|
||||
|
||||
for (KParameter parameter : parameters) {
|
||||
|
||||
if (index != 0 && index % Integer.SIZE == 0) {
|
||||
masks.add(mask);
|
||||
mask = 0;
|
||||
}
|
||||
|
||||
if (parameter.isOptional() && !isPresent.test(parameter)) {
|
||||
mask = mask | (1 << (index % Integer.SIZE));
|
||||
}
|
||||
|
||||
if (parameter.getKind() == Kind.VALUE) {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
masks.add(mask);
|
||||
|
||||
return new KotlinDefaultMask(masks.stream().mapToInt(i -> i).toArray());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user