package org.springframework.data.mapping.model;
import kotlin.reflect.KCallable;
import kotlin.reflect.KParameter;
import kotlin.reflect.KParameter.Kind;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.data.mapping.MappingException;
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;
class BeanWrapper<T> implements PersistentPropertyAccessor<T> {
private T bean;
protected BeanWrapper(T bean) {
Assert.notNull(bean, "Bean must not be null!");
this.bean = bean;
}
@SuppressWarnings("unchecked")
public void setProperty(PersistentProperty<?> property, @Nullable Object value) {
Assert.notNull(property, "PersistentProperty must not be null!");
try {
if (property.isImmutable()) {
Method wither = property.getWither();
if (wither != null) {
ReflectionUtils.makeAccessible(wither);
this.bean = (T) ReflectionUtils.invokeMethod(wither, bean, value);
return;
}
if (org.springframework.data.util.ReflectionUtils.isKotlinClass(property.getOwner().getType())) {
this.bean = (T) KotlinCopyUtil.setProperty(property, bean, value);
return;
}
throw new UnsupportedOperationException(
String.format("Cannot set immutable property %s.%s!", property.getOwner().getName(), property.getName()));
}
if (!property.usePropertyAccess()) {
Field field = property.getRequiredField();
ReflectionUtils.makeAccessible(field);
ReflectionUtils.setField(field, bean, value);
return;
}
Method setter = property.getRequiredSetter();
ReflectionUtils.makeAccessible(setter);
ReflectionUtils.invokeMethod(setter, bean, value);
} catch (IllegalStateException e) {
throw new MappingException("Could not set object property!", e);
}
}
@Nullable
public Object getProperty(PersistentProperty<?> property) {
return getProperty(property, property.getType());
}
@Nullable
public <S> Object getProperty(PersistentProperty<?> property, Class<? extends S> type) {
Assert.notNull(property, "PersistentProperty must not be null!");
try {
if (!property.usePropertyAccess()) {
Field field = property.getRequiredField();
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, bean);
}
Method getter = property.getRequiredGetter();
ReflectionUtils.makeAccessible(getter);
return ReflectionUtils.invokeMethod(getter, bean);
} catch (IllegalStateException e) {
throw new MappingException(
String.format("Could not read property %s of %s!", property.toString(), bean.toString()), e);
}
}
public T getBean() {
return bean;
}
static class KotlinCopyUtil {
private static final Map<Class<?>, KCallable<?>> COPY_METHOD_CACHE = new ConcurrentReferenceHashMap<>();
static <T> Object setProperty(PersistentProperty<?> property, T bean, @Nullable Object value) {
Class<?> type = property.getOwner().getType();
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 for property %s!", type.getName(), property.getName()));
}
return copy.callBy(getCallArgs(copy, property, bean, value));
}
private static <T> Map<KParameter, Object> getCallArgs(KCallable<?> callable, PersistentProperty<?> property,
T bean, @Nullable Object value) {
Map<KParameter, Object> args = new LinkedHashMap<>(2, 1);
List<KParameter> parameters = callable.getParameters();
for (KParameter parameter : parameters) {
if (parameter.getKind() == Kind.INSTANCE) {
args.put(parameter, bean);
}
if (parameter.getKind() == Kind.VALUE && parameter.getName() != null
&& parameter.getName().equals(property.getName())) {
args.put(parameter, value);
}
}
return args;
}
@Nullable
private static KCallable<?> getCopyMethod(Class<?> type, PersistentProperty<?> property) {
return KotlinCopyMethod.findCopyMethod(type).filter(it -> it.supportsProperty(property))
.map(KotlinCopyMethod::getCopyFunction).orElse(null);
}
}
}