StoredPropertyMetaFactory.java

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

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.Optional;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import com.github.mygreen.sqlmapper.core.annotation.Column;
import com.github.mygreen.sqlmapper.core.annotation.In;
import com.github.mygreen.sqlmapper.core.annotation.InOut;
import com.github.mygreen.sqlmapper.core.annotation.Out;
import com.github.mygreen.sqlmapper.core.annotation.ResultSet;
import com.github.mygreen.sqlmapper.core.naming.NamingRule;
import com.github.mygreen.sqlmapper.core.type.ValueType;
import com.github.mygreen.sqlmapper.core.type.ValueTypeRegistry;
import com.github.mygreen.sqlmapper.core.util.ClassUtils;
import com.github.mygreen.sqlmapper.core.util.NameUtils;

import lombok.Getter;
import lombok.Setter;

/**
 * ストアドプロシージャ/ファンクション
 *
 * @since 0.3
 * @author T.TSUCHIE
 *
 */
public class StoredPropertyMetaFactory {

    @Getter
    @Setter
    @Autowired
    private ValueTypeRegistry valueTypeRegistry;

    @Getter
    @Setter
    @Autowired
    private NamingRule namingRule;

    /**
     * フィールド情報を元に、ストアド用のエンティティ形式のパラメータのプロパティ情報を作成します。
     *
     * @param field フィールド情報
     * @return プロパティ情報
     */
    public StoredPropertyMeta create(final Field field) {

        final StoredPropertyMeta propertyMeta = new StoredPropertyMeta(field.getName(), field.getType());
        doField(propertyMeta, field);

        // フィールドに対するgetter/setterメソッドを設定します。
        final Class<?> declaringClass = field.getDeclaringClass();
        for(Method method : declaringClass.getMethods()) {
            ReflectionUtils.makeAccessible(method);

            int modifiers = method.getModifiers();
            if(Modifier.isStatic(modifiers)) {
                continue;
            }

            if(ClassUtils.isSetterMethod(method)) {
                doSetterMethod(propertyMeta, method);

            } else if(ClassUtils.isGetterMethod(method) || ClassUtils.isBooleanGetterMethod(method)) {
                doGetterMethod(propertyMeta, method);
            }
        }

        if(propertyMeta.isIn() || propertyMeta.isOut() || propertyMeta.isInOut()) {

            // プロパティに対する型変換を設定します。
            ValueType<?> valueType = valueTypeRegistry.findValueType(propertyMeta);

            //TODO: Oracleなどの純粋なタイプ
            propertyMeta.setValueType(valueType);
            propertyMeta.setSingleValue(true);

            // パラメータ名の設定
            Optional<String> paramName = propertyMeta.getAnnotation(In.class).map(a -> a.name())
                    .or(() -> propertyMeta.getAnnotation(Out.class).map(a -> a.name()))
                    .or(() -> propertyMeta.getAnnotation(InOut.class).map(a -> a.name()));
            propertyMeta.setParamName(paramName.map(p -> StringUtils.hasLength(p) ? p : namingRule.propertyToStoredParam(propertyMeta.getName()))
                    .orElseGet(() -> namingRule.propertyToStoredParam(propertyMeta.getName())));


        } else if(propertyMeta.isResultSet()) {
            // ResultSetの場合
            if(valueTypeRegistry.isRegisteredType(propertyMeta.getPropertyType())) {
                // 通常の値である場合
                ValueType<?> valueType = valueTypeRegistry.findValueType(propertyMeta);

                //TODO: Oracleなどの純粋なタイプ
                propertyMeta.setValueType(valueType);
                propertyMeta.setSingleValue(true);

            } else {
                // JavaBeanの場合
                // JavaBeanのプロパティの抽出は、StoredParamMetaFactoryで行う。
                propertyMeta.setSingleValue(false);

                // 総称型の設定
                doComponentType(propertyMeta, field);
            }

            // パラメータ名の設定
            Optional<String> paramName = propertyMeta.getAnnotation(ResultSet.class).map(a -> a.name());
            propertyMeta.setParamName(paramName.map(p -> StringUtils.hasLength(p) ? p : namingRule.propertyToStoredParam(propertyMeta.getName()))
                    .orElseGet(() -> namingRule.propertyToStoredParam(propertyMeta.getName())));


        } else {
            // ResultSetのネストしたプロパティの場合
            ValueType<?> valueType = valueTypeRegistry.findValueType(propertyMeta);
            propertyMeta.setValueType(valueType);
            propertyMeta.setSingleValue(true);

            // パラメータ名の設定
            Optional<String> paramName = propertyMeta.getAnnotation(Column.class).map(a -> a.name());
            propertyMeta.setParamName(paramName.map(p -> StringUtils.hasLength(p) ? p : namingRule.propertyToColumn(propertyMeta.getName()))
                    .orElseGet(() -> namingRule.propertyToColumn(propertyMeta.getName())));

        }

        return propertyMeta;

    }

    /**
     * プロパティのメタ情報に対する処理を実行します。
     * @param propertyMeta プロパティのメタ情報
     * @param field フィールド情報
     */
    private void doField(final StoredPropertyMeta propertyMeta, final Field field) {

        propertyMeta.setField(field);

        final Annotation[] annos = field.getAnnotations();
        for(Annotation anno : annos) {
            if(!isSupportedAnnotation(anno)) {
                continue;
            }

            final Class<? extends Annotation> annoClass = anno.annotationType();
            propertyMeta.addAnnotation(annoClass, anno);
        }
    }

    /**
     * サポートするアノテーションか判定する。
     * <p>確実に重複するJava標準のアノテーションは除外するようにします。</p>
     *
     * @param anno 判定対象のアノテーション
     * @return tureのときサポートします。
     */
    private boolean isSupportedAnnotation(final Annotation anno) {

        final String name = anno.annotationType().getName();
        if(name.startsWith("java.lang.annotation.")) {
            return false;
        }

        return true;
    }

    /**
     * setterメソッドの情報を処理する。
     * @param propertyMeta プロパティのメタ情報
     * @param method setterメソッド
     */
    private void doSetterMethod(final StoredPropertyMeta propertyMeta, final Method method) {

        final String methodName = method.getName();
        final String propertyName = NameUtils.uncapitalize(methodName.substring(3));

        if(!propertyMeta.getName().equals(propertyName)) {
            // プロパティ名が一致しない場合はスキップする
            return;
        }

        propertyMeta.setWriteMethod(method);

    }

    /**
     * getterメソッドの情報を処理する。
     * @param propertyMeta プロパティのメタ情報
     * @param method getterメソッド
     */
    private void doGetterMethod(final StoredPropertyMeta propertyMeta, final Method method) {

        final String methodName = method.getName();
        final String propertyName;
        if(methodName.startsWith("get")) {
            propertyName = NameUtils.uncapitalize(methodName.substring(3));
        } else {
            // 「is」から始まる場合
            propertyName = NameUtils.uncapitalize(methodName.substring(2));
        }

        if(!propertyMeta.getName().equals(propertyName)) {
            // プロパティ名が一致しない場合はスキップする
            return;
        }

        propertyMeta.setReadMethod(method);

    }

    /**
     * JavaBean型の場合、総称型を設定します。
     * @param propertyMeta プロパティのメタ情報
     * @param field フィールド情報
     */
    private void doComponentType(final StoredPropertyMeta propertyMeta, final Field field) {

        Class<?> fieldType = field.getType();
        if(Collection.class.isAssignableFrom(fieldType)) {
            ParameterizedType type = (ParameterizedType) field.getGenericType();
            propertyMeta.setComponentType((Class<?>)type.getActualTypeArguments()[0]);

        } else if(fieldType.isArray()) {
            propertyMeta.setComponentType(fieldType.getComponentType());

        }

    }
}