org.springframework.data.mapping.model.BeanWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.data.mapping.model.BeanWrapper.java

Source

/*
 * Copyright 2011-2019 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 {@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 {@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 {@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
     */
    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.
         *
         * @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);
        }
    }
}