PropertyValueInvoker.java

package com.github.mygreen.sqlmapper.core.meta;

import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;

import org.springframework.beans.BeanUtils;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

/**
 * 埋め込み型のプロパティ値にアクセスするためのクラス。
 *
 *
 * @author T.TSUCHIE
 *
 */
@Slf4j
public class PropertyValueInvoker {

    /**
     * このプロパティに対して値を設定する。
     * @param propertyMeta 取得対象のプロパティ情報
     * @param entityObject 親のオブジェクト
     * @param propertyValue 設定するプロパティの値
     * @throws NullPointerException 引数{@literal entityObject}がnullの場合
     */
    public static void setPropertyValue(final @NonNull PropertyMeta propertyMeta,
            final @NonNull Object entityObject, final Object propertyValue) {

        if(propertyMeta.getWriteMethod().isPresent()) {
            try {
                propertyMeta.getWriteMethod().get().invoke(entityObject, propertyValue);
            } catch (IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {
                //TODO: 例外を見直す
                throw new RuntimeException("Fail set property value for writer method.", e);
            }
        } else if(propertyMeta.getField().isPresent()) {
            try {
                propertyMeta.getField().get().set(entityObject, propertyValue);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                //TODO: 例外を見直す
                throw new RuntimeException("Fail set property value for field.", e);
            }
        } else {
            log.warn("Not found saving method or field with property value in {}#{}",
                    entityObject.getClass().getName(), propertyMeta.getName());
            //TODO: フラグでquietlyをつける
            throw new IllegalStateException();
        }

    }

    /**
     * このプロパティの値を取得する。
     * @param propertyMeta 取得対象のプロパティ情報
     * @param entityObject ルートとなるエンティティオブジェクト
     * @return プロパティの値。
     * @throws NullPointerException 引数がnullのとき
     * @throws IllegalStateException 取得対象のフィールドやメソッドがない場合
     */
    public static Object getPropertyValue(final @NonNull PropertyMeta propertyMeta,
            final @NonNull Object entityObject) {

        // ルート直下のプロパティの場合
        if(propertyMeta.getReadMethod().isPresent()) {
            try {
                return propertyMeta.getReadMethod().get().invoke(entityObject);
            } catch (IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {
                //TODO: 例外を見直す
                throw new RuntimeException("Fail get property value for reader method.", e);
            }

        } else if(propertyMeta.getField().isPresent()) {
            try {
                return propertyMeta.getField().get().get(entityObject);
            } catch (IllegalArgumentException | IllegalAccessException e) {
                //TODO: 例外を見直す
                throw new RuntimeException("Fail get property value for field.", e);
            }
        } else {
            log.warn("Not found reading method or field with property value in {}#{}",
                    entityObject.getClass().getName(), propertyMeta.getName());
            throw new IllegalStateException();
        }

    }

    /**
     * 埋め込みプロパティを考慮して値を取得する。
     * 埋め込みオブジェクトの場合は、親のオブジェクトをたどり設定していく。
     * その際に、親のオブジェクトがnullのときは、そこでたどるのを中止してnullを返す。
     *
     * @param propertyMeta 取得対象のプロパティ情報
     * @param entityObject 取得元となるルートのエンティティオブジェクト。
     * @return 取得対象のプロパティの値
     * @throws NullPointerException {@literal propertyMeta == null or entityObject == null.}
     */
    public static Object getEmbeddedPropertyValue(final @NonNull PropertyMeta propertyMeta,
            final @NonNull Object entityObject) {

        if(!propertyMeta.hasParent()) {
            return getPropertyValue(propertyMeta, entityObject);
        }

        final LinkedList<PropertyMeta> parentStack = new LinkedList<>();
        setupParentStack(propertyMeta, parentStack);

        Object parentObject = entityObject;

        // スタックの最上部(ルート)から下にたどる
        for(PropertyMeta parent : parentStack) {
            Object propertyValue = getPropertyValue(parent, parentObject);

            // 親がnullのとき、子もnullとして返す。
            if(propertyValue == null) {
                return null;
            }

            parentObject = propertyValue;
        }

        return getPropertyValue(propertyMeta, parentObject);
    }

    /**
     * 埋め込みプロパティを考慮して値を設定する。
     * 埋め込みオブジェクトの場合は、親のオブジェクトをたどり設定していく。
     * その際に、親のオブジェクトがnullのときはインスタンスを生成して設定する。
     *
     * @param propertyMeta 設定対象のプロパティ情報
     * @param entityObject ルートとなるエンティティオブジェクト。
     * @param propertyValue 設定対象のプロパティの値
     * @throws NullPointerException {@literal propertyMeta == null || entityObject == null.}
     */
    public static void setEmbeddedPropertyValue(final @NonNull PropertyMeta propertyMeta,
            final @NonNull Object entityObject, final Object propertyValue) {

        if(!propertyMeta.hasParent()) {
            setPropertyValue(propertyMeta, entityObject, propertyValue);
            return;
        }

        final LinkedList<PropertyMeta> parentStack = new LinkedList<>();
        setupParentStack(propertyMeta, parentStack);

        Object parentObject = entityObject;

        // スタックの最上部(ルート)から下にたどっていく
        for(PropertyMeta parent : parentStack) {
            Object parentPropertyValue = getPropertyValue(parent, parentObject);

            // 親のインスタンスがない場合は作成する
            if(parentPropertyValue == null) {
                parentPropertyValue = BeanUtils.instantiateClass(parent.getPropertyType());
                setPropertyValue(parent, parentObject, parentPropertyValue);
            }

            parentObject = parentPropertyValue;
        }

        setPropertyValue(propertyMeta, parentObject, propertyValue);

    }

    /**
     * 埋め込みプロパティの親をたどるためのスタックを設定する。
     * @param propertyMeta 子のプロパティ情報
     * @param parentStack 親へのスタック
     */
    private static void setupParentStack(final PropertyMeta propertyMeta,
            final LinkedList<PropertyMeta> parentStack) {

        if(propertyMeta.hasParent()) {
            parentStack.push(propertyMeta.getParent());
            setupParentStack(propertyMeta.getParent(), parentStack);
        }

    }

}