/*
 * Copyright 2011-2020 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
 *
 *      https://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.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;

Domain service to allow accessing the values of PersistentPropertys on a given bean.
Author:Oliver Gierke, Mark Paluch
/** * Domain service to allow accessing the values of {@link PersistentProperty}s on a given bean. * * @author Oliver Gierke * @author Mark Paluch */
class BeanWrapper<T> implements PersistentPropertyAccessor<T> { private T bean;
Creates a new BeanWrapper for the given bean.
Params:
  • bean – must not be null.
/** * Creates a new {@link BeanWrapper} for the given bean. * * @param bean must not be {@literal null}. */
protected BeanWrapper(T bean) { Assert.notNull(bean, "Bean must not be null!"); this.bean = bean; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#setProperty(org.springframework.data.mapping.PersistentProperty, java.util.Optional) */ @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); } } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#getProperty(org.springframework.data.mapping.PersistentProperty) */ @Nullable public Object getProperty(PersistentProperty<?> property) { return getProperty(property, property.getType()); }
Returns the value of the given PersistentProperty potentially converted to the given type.
Params:
  • property – must not be null.
  • type – can be null.
Type parameters:
  • <S> –
Throws:
Returns:
/** * Returns the value of the given {@link PersistentProperty} potentially converted to the given type. * * @param <S> * @param property must not be {@literal null}. * @param type can be {@literal null}. * @return * @throws MappingException in case an exception occured when accessing the property. */
@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); } } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentPropertyAccessor#getBean() */ public T getBean() { return bean; }
Utility class to leverage Kotlin's copy method for immutable data classes.
Since:2.1
/** * Utility class to leverage Kotlin's copy method for immutable data classes. * * @since 2.1 */
static class KotlinCopyUtil { private static final Map<Class<?>, KCallable<?>> COPY_METHOD_CACHE = new ConcurrentReferenceHashMap<>();
Set a single property by calling copy(…) on a Kotlin data class. Copying creates a new instance that holds all values of the original instance and the newly set PersistentProperty value.
See Also:
/** * 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. * * @see KCallable#callBy(Map) */
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); } } }