EntityMetamodelFactory.java

package com.github.mygreen.sqlmapper.apt;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;

import com.github.mygreen.sqlmapper.apt.model.AptType;
import com.github.mygreen.sqlmapper.apt.model.EntityMetamodel;
import com.github.mygreen.sqlmapper.apt.model.PropertyMetamodel;
import com.github.mygreen.sqlmapper.core.annotation.Embeddable;
import com.github.mygreen.sqlmapper.core.annotation.EmbeddedId;
import com.github.mygreen.sqlmapper.core.annotation.Entity;
import com.github.mygreen.sqlmapper.core.annotation.Lob;
import com.github.mygreen.sqlmapper.core.annotation.MappedSuperclass;
import com.github.mygreen.sqlmapper.core.annotation.Transient;
import com.github.mygreen.sqlmapper.core.meta.EntityMetaFactory;

import lombok.RequiredArgsConstructor;

/**
 * クラス情報からメタモデルの情報を作成します。
 * プロパティやクラスの仕様は、{@link EntityMetaFactory}に準拠します。
 *
 * @version 0.3
 * @author T.TSUCHIE
 *
 */
@RequiredArgsConstructor
public class EntityMetamodelFactory {

    private final Types typeUtils;

    /**
     * APTの処理対象のエンティティ情報からメタ情報を抽出する。
     *
     * @param entityElement アノテーション「{@link Entity}/{@link MappedSuperclass}/{@link Embeddable}」が付与されている要素。
     * @return エンティティのモデル情報。
     * @throws ClassNotFoundException エンティティで指定したクラスが存在しない場合
     */
    public EntityMetamodel create(final TypeElement entityElement) throws ClassNotFoundException {

        final EntityMetamodel entityModel = new EntityMetamodel();
        entityModel.setClassName(entityElement.getSimpleName().toString());

        // パッケージ情報の取得
        Element enclosing = entityElement.getEnclosingElement();
        if(enclosing != null) {
            entityModel.setPackageName(enclosing.toString());
        }
        entityModel.setType(createAptType(entityElement.asType()));

        // 自身のクラス情報の取得
        entityModel.setEntityAnno(entityElement.getAnnotation(Entity.class));
        entityModel.setMappedSuperclassAnno(entityElement.getAnnotation(MappedSuperclass.class));
        entityModel.setEmbeddableAnno(entityElement.getAnnotation(Embeddable.class));

        // 親クラスの取得
        doSuperclass(entityModel, entityElement);

        // プロパティ情報の取得
        doPropety(entityModel, entityElement);

        return entityModel;
    }

    /**
     * 親クラスがアノテーション {@link MappedSuperclass} が付与されている場合、情報を付与する。
     * @param entityModel エンティティのモデル情報。
     * @param entityElement 情報作成元のエンティティ情報。
     */
    private void doSuperclass(final EntityMetamodel entityModel, final TypeElement entityElement) {

        final TypeMirror superclassType = entityElement.getSuperclass();
        if(superclassType.toString().equals(Object.class.getCanonicalName())) {
            // 継承していない場合
            return;
        }

        Element superclassElement = typeUtils.asElement(superclassType);
        if(superclassElement == null || !(superclassElement instanceof TypeElement)) {
            // 親クラスがタイプ情報でない場合
            return;
        }

        TypeElement superTypeElement = (TypeElement) superclassElement;
        if(superTypeElement.getAnnotation(MappedSuperclass.class) != null) {
            entityModel.setSuperClassType(createAptType(superclassType));
        }

    }

    /**
     * エンティティタイプを元にエンティティのメタ情報を作成する。
     * @param entityModel エンティティのメタ情報
     * @param entityElement 情報作成元のエンティティ情報。
     */
    private void doPropety(final EntityMetamodel entityModel, final TypeElement entityElement) {

        for(Element enclosedElement : entityElement.getEnclosedElements()) {
            if(!AptUtils.isInstanceField(enclosedElement)) {
                continue;
            }

            if(enclosedElement.getAnnotation(Transient.class) != null) {
                // 永続化対象外は除外
                continue;
            }

            entityModel.add(createPropertyModel((VariableElement)enclosedElement));
        }

    }

    /**
     * フィールド情報を元にプロパティのメタ情報を作成する。
     * @param fieldElement フィールド
     * @return プロパティのメタ情報
     */
    private PropertyMetamodel createPropertyModel(final VariableElement fieldElement) {

        PropertyMetamodel propertyModel = new PropertyMetamodel();

        propertyModel.setPropertyName(fieldElement.getSimpleName().toString());

        TypeMirror type = fieldElement.asType();
        propertyModel.setPropertyType(createAptType(type));

        // 埋め込み用かどうか
        propertyModel.setEmbedded(fieldElement.getAnnotation(EmbeddedId.class) != null);

        // LOBかどうか
        propertyModel.setLob(fieldElement.getAnnotation(Lob.class) != null);

        return propertyModel;

    }

    /**
     * APT処理用のタイプ情報を作成する。
     * @param typeMirror タイプ情報
     * @return APT処理用のタイプ情報
     */
    private AptType createAptType(final TypeMirror typeMirror) {

        AptType aptType = new AptType(typeMirror, Optional.ofNullable(typeUtils.asElement(typeMirror)));

        // 継承クラスの抽出
        List<TypeMirror> superTypes = new ArrayList<TypeMirror>();
        AptUtils.extractSuperClassTypes(typeMirror, typeUtils, superTypes);
        aptType.setSuperTypes(superTypes);

        return aptType;
    }
}