FieldAccessorFactory.java

package com.gh.mygreen.xlsmapper.fieldaccessor;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gh.mygreen.xlsmapper.annotation.XlsArrayCells;
import com.gh.mygreen.xlsmapper.annotation.XlsArrayColumns;
import com.gh.mygreen.xlsmapper.annotation.XlsIterateTables;
import com.gh.mygreen.xlsmapper.annotation.XlsLabelledArrayCells;
import com.gh.mygreen.xlsmapper.annotation.XlsMapColumns;
import com.gh.mygreen.xlsmapper.localization.MessageBuilder;
import com.gh.mygreen.xlsmapper.util.ArgUtils;
import com.gh.mygreen.xlsmapper.util.ClassUtils;
import com.gh.mygreen.xlsmapper.util.Utils;
import com.gh.mygreen.xlsmapper.xml.AnnotationReader;

/**
 * {@link FieldAccessor}のインスタンスを作成するクラス。
 *
 * @version 2.1
 * @since 2.0
 * @author T.TSUCHIE
 *
 */
public class FieldAccessorFactory {

    private static Logger log = LoggerFactory.getLogger(FieldAccessorFactory.class);

    private final AnnotationReader annoReader;

    private PositionSetterFactory positionSetterFactory = new PositionSetterFactory();
    private PositionGetterFactory positionGetterFactory = new PositionGetterFactory();
    private MapPositionSetterFactory mapPositionSetterFactory = new MapPositionSetterFactory();
    private ArrayPositionSetterFactory arrayPositionSetterFactory = new ArrayPositionSetterFactory();

    private LabelSetterFactory labelSetterFactory = new LabelSetterFactory();
    private LabelGetterFactory labelGetterFactory = new LabelGetterFactory();
    private MapLabelSetterFactory mapLabelSetterFactory = new MapLabelSetterFactory();
    private ArrayLabelSetterFactory arrayLabelSetterFactory = new ArrayLabelSetterFactory();
    
    private CommentSetterFactory commentSetterFactory = new CommentSetterFactory();
    private CommentGetterFactory commentGetterFactory = new CommentGetterFactory();
    
    private MapCommentSetterFactory mapCommentSetterFactory = new MapCommentSetterFactory();
    private MapCommentGetterFactory mapCommentGetterFactory = new MapCommentGetterFactory();
    
    private ArrayCommentSetterFactory arrayCommentSetterFactory = new ArrayCommentSetterFactory();
    private ArrayCommentGetterFactory arrayCommentGetterFactory = new ArrayCommentGetterFactory();

    
    /**
     * コンストラクタ
     * @param annoReader XMLで定義したアノテーション情報を提供するクラス。
     * @throws IllegalArgumentException {@literal annoReader == null.}
     */
    public FieldAccessorFactory(final AnnotationReader annoReader) {
        ArgUtils.notNull(annoReader, "annoReader");

        this.annoReader = annoReader;
    }

    /**
     * フィールド情報を元にインスタンスを作成する。
     * @param field フィールド
     * @return フィールド情報を元に組み立てられたインスタンス。
     * @throws IllegalArgumentException {@literal field == null.}
     */
    public FieldAccessor create(final Field field) {

        ArgUtils.notNull(field, "field");

        final FieldAccessor accessor = new FieldAccessor();

        // 共通情報の設定
        accessor.name = field.getName();
        accessor.targetType = field.getType();
        accessor.declaringClass = field.getDeclaringClass();

        // フィールド情報の設定
        setupWithFiled(accessor, field);

        // getter情報の設定
        if(ClassUtils.isPrimitiveBoolean(accessor.getType())) {
            ClassUtils.extractBooleanGetter(accessor.getDeclaringClass(), accessor.getName())
                    .ifPresent(getter -> setupWithGetter(accessor, getter));

        } else {
            ClassUtils.extractGetter(accessor.getDeclaringClass(), accessor.getName(), accessor.getType())
                    .ifPresent(getter -> setupWithGetter(accessor, getter));
        }

        // setter情報の設定
        ClassUtils.extractSetter(accessor.getDeclaringClass(), accessor.getName(), accessor.getType())
                .ifPresent(setter -> setupWithSetter(accessor, setter));

        // コンポーネントタイプの設定
        if(Collection.class.isAssignableFrom(accessor.getType())) {
            ParameterizedType type = (ParameterizedType) field.getGenericType();
            accessor.componentType = Optional.of((Class<?>) type.getActualTypeArguments()[0]);

        } else if(accessor.getType().isArray()) {
            accessor.componentType = Optional.of(accessor.getType().getComponentType());

        } else if(Map.class.isAssignableFrom(accessor.getType())) {
            ParameterizedType type = (ParameterizedType) field.getGenericType();
            accessor.componentType = Optional.of((Class<?>) type.getActualTypeArguments()[1]);
        }

        // 位置・ラベル情報のアクセッサの設定
        if(accessor.hasAnnotation(XlsMapColumns.class)) {
            // マップ形式の場合
            accessor.mapPositionSetter = mapPositionSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.mapLabelSetter = mapLabelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            
            accessor.mapCommentSetter = mapCommentSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.mapCommentGetter = mapCommentGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            

        } else if(accessor.hasAnnotation(XlsArrayColumns.class)
                || accessor.hasAnnotation(XlsArrayCells.class)
                || accessor.hasAnnotation(XlsLabelledArrayCells.class)
                || accessor.hasAnnotation(XlsIterateTables.class)){
            // リストや配列形式の場合
            accessor.arrayPositionSetter = arrayPositionSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.arrayLabelSetter = arrayLabelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            
            accessor.arrayCommentSetter = arrayCommentSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.arrayCommentGetter = arrayCommentGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());

            if(accessor.hasAnnotation(XlsArrayColumns.class)
                    || accessor.hasAnnotation(XlsLabelledArrayCells.class)) {
                // インデックスが付いていないラベルの設定
                accessor.labelSetter = labelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            }

        } else {
            // リストやMapではない通常のプロパティの場合
            accessor.positionSetter = positionSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.positionGetter = positionGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());

            accessor.labelSetter = labelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.labelGetter = labelGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            
            accessor.commentSetter = commentSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.commentGetter = commentGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            

        }

        return accessor;
    }

    /**
     * メソッド情報を元にインスタンスを作成する。
     * @param method メソッド情報
     * @return メソッド情報を元に組み立てられたインスタンス。
     * @throws IllegalArgumentException {@literal method == null.}
     * @throws IllegalArgumentException {@literal methodの名称がsetterまたはgetterの書式でない場合。}
     */
    public FieldAccessor create(final Method method) {

        ArgUtils.notNull(method, "method");

        final FieldAccessor accessor = new FieldAccessor();

        final String methodName = method.getName();
        if(ClassUtils.isGetterMethod(method) || ClassUtils.isBooleanGetterMethod(method)) {
            final String propertyName;
            if(methodName.startsWith("get")) {
                propertyName = Utils.uncapitalize(methodName.substring(3));
            } else {
                propertyName = Utils.uncapitalize(methodName.substring(2));
            }

            // 共通情報の設定
            accessor.name = propertyName;
            accessor.targetType = method.getReturnType();
            accessor.declaringClass = method.getDeclaringClass();

            // getter情報の設定
            setupWithGetter(accessor, method);

            // フィールド情報の設定
            ClassUtils.extractField(accessor.getDeclaringClass(), accessor.getName(), accessor.getType())
                    .ifPresent(field -> setupWithFiled(accessor, field));

            // setter情報の設定
            ClassUtils.extractSetter(accessor.getDeclaringClass(), accessor.getName(), accessor.getType())
                    .ifPresent(setter -> setupWithSetter(accessor, setter));

            // コンポーネントタイプの設定
            if(Collection.class.isAssignableFrom(accessor.getType())) {
                ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
                accessor.componentType = Optional.of((Class<?>) type.getActualTypeArguments()[0]);

            } else if(accessor.getType().isArray()) {
                accessor.componentType = Optional.of(accessor.getType().getComponentType());

            } else if(Map.class.isAssignableFrom(accessor.getType())) {
                ParameterizedType type = (ParameterizedType) method.getGenericReturnType();
                accessor.componentType = Optional.of((Class<?>) type.getActualTypeArguments()[1]);
            }

        } else if(ClassUtils.isSetterMethod(method)) {
            final String propertyName = Utils.uncapitalize(methodName.substring(3));

            // 共通情報の設定
            accessor.name = propertyName;
            accessor.targetType = method.getParameterTypes()[0];
            accessor.declaringClass = method.getDeclaringClass();

            // setter情報の設定
            setupWithSetter(accessor, method);

            // フィールド情報の設定
            ClassUtils.extractField(accessor.getDeclaringClass(), accessor.getName(), accessor.getType())
                    .ifPresent(field -> setupWithFiled(accessor, field));

            // getter情報の設定
            if(ClassUtils.isPrimitiveBoolean(accessor.getType())) {
                ClassUtils.extractBooleanGetter(accessor.getDeclaringClass(), accessor.getName())
                        .ifPresent(getter -> setupWithGetter(accessor, getter));

            } else {
                ClassUtils.extractGetter(accessor.getDeclaringClass(), accessor.getName(), accessor.getType())
                        .ifPresent(getter -> setupWithGetter(accessor, getter));
            }

            // コンポーネントタイプの設定
            if(Collection.class.isAssignableFrom(accessor.getType())) {
                ParameterizedType type = (ParameterizedType) method.getGenericParameterTypes()[0];
                accessor.componentType = Optional.of((Class<?>) type.getActualTypeArguments()[0]);

            } else if(accessor.getType().isArray()) {
                accessor.componentType = Optional.of(accessor.getType().getComponentType());

            } else if(Map.class.isAssignableFrom(accessor.getType())) {
                ParameterizedType type = (ParameterizedType) method.getGenericParameterTypes()[0];
                accessor.componentType = Optional.of((Class<?>) type.getActualTypeArguments()[1]);
            }

        } else {
            throw new IllegalArgumentException(MessageBuilder.create("method.noAccessor")
                    .varWithClass("className", method.getDeclaringClass())
                    .var("methodName", methodName)
                    .format());
        }

        // 位置・ラベル情報のアクセッサの設定
        if(accessor.hasAnnotation(XlsMapColumns.class)) {
            accessor.mapPositionSetter = mapPositionSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.mapLabelSetter = mapLabelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());

        } else if(accessor.hasAnnotation(XlsArrayColumns.class)
                || accessor.hasAnnotation(XlsArrayCells.class)
                || accessor.hasAnnotation(XlsLabelledArrayCells.class)
                || accessor.hasAnnotation(XlsIterateTables.class)){
            // リストや配列形式の場合
            accessor.arrayPositionSetter = arrayPositionSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.arrayLabelSetter = arrayLabelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());

            if(accessor.hasAnnotation(XlsArrayColumns.class)
                    || accessor.hasAnnotation(XlsLabelledArrayCells.class)) {
                // インデックスが付いていないラベルの設定
                accessor.labelSetter = labelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            }

        } else {
            // XlsMapColumnsを持たない通常のプロパティの場合
            accessor.positionSetter = positionSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.positionGetter = positionGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());

            accessor.labelSetter = labelSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.labelGetter = labelGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            
            accessor.commentSetter = commentSetterFactory.create(accessor.getDeclaringClass(), accessor.getName());
            accessor.commentGetter = commentGetterFactory.create(accessor.getDeclaringClass(), accessor.getName());

        }


        return accessor;
    }

    private void setupWithFiled(final FieldAccessor accessor, final Field field) {

        accessor.targetField = Optional.of(field);

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

            final Class<? extends Annotation> annoClass = anno.annotationType();

            if(accessor.annotationMap.containsKey(annoClass)) {
                final String message = MessageBuilder.create("anno.duplicated")
                        .varWithClass("classType", accessor.getDeclaringClass())
                        .var("property", field.getName())
                        .varWithAnno("anno", annoClass)
                        .format();
                log.warn(message);
            }

            accessor.annotationMap.put(annoClass, anno);
        }


    }

    private void setupWithGetter(final FieldAccessor accessor, final Method method) {

        accessor.targetGetter = Optional.of(method);

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

            final Class<? extends Annotation> annoClass = anno.annotationType();

            if(accessor.annotationMap.containsKey(annoClass)) {
                final String message = MessageBuilder.create("anno.duplicated")
                        .varWithClass("classType", accessor.getDeclaringClass())
                        .var("property", method.getName() + "()")
                        .varWithAnno("anno", annoClass)
                        .format();
                log.warn(message);
            }

            accessor.annotationMap.put(annoClass, anno);
        }
    }

    private void setupWithSetter(final FieldAccessor accessor, final Method method) {

        accessor.targetSetter = Optional.of(method);

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

            final Class<? extends Annotation> annoClass = anno.annotationType();

            if(accessor.annotationMap.containsKey(annoClass)) {
                final String message = MessageBuilder.create("anno.duplicated")
                        .varWithClass("classType", accessor.getDeclaringClass())
                        .var("property", method.getName() + "(...)")
                        .varWithAnno("anno", annoClass)
                        .format();
                log.warn(message);
            }

            accessor.annotationMap.put(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;
    }

}