EntityMetamodelProcessor.java

package com.github.mygreen.sqlmapper.apt;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;

import com.github.mygreen.sqlmapper.apt.model.EntityMetamodel;
import com.github.mygreen.sqlmapper.core.annotation.Embeddable;
import com.github.mygreen.sqlmapper.core.annotation.Entity;
import com.github.mygreen.sqlmapper.core.annotation.MappedSuperclass;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;

import lombok.extern.slf4j.Slf4j;

/**
 * エンティティのメタモデルクラスを生成するアノテーションプロセッサ。
 *
 *
 * @author T.TSUCHIE
 *
 */
@Slf4j
@SupportedAnnotationTypes({"com.github.mygreen.sqlmapper.core.annotation.*"})
@SupportedSourceVersion(SourceVersion.RELEASE_11)
public class EntityMetamodelProcessor extends AbstractProcessor {

    /**
     * APT用のファイラー - 文字コード設定などはAPTの定義を採用
     */
    private Filer filer;

    /**
     * APT用のメッセージ出力
     */
    private Messager messager;

    /**
     * APTのオプション設定を扱うクラス。
     */
    private MetamodelConfig metamodelConfig;

    /**
     * エンティティのメタモデル情報の作成クラス
     */
    private EntityMetamodelFactory entityMetamodelFactory;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        this.filer = processingEnv.getFiler();
        this.messager = processingEnv.getMessager();
        this.metamodelConfig = new MetamodelConfig(processingEnv.getOptions());
        this.entityMetamodelFactory = new EntityMetamodelFactory(processingEnv.getTypeUtils());
    }

    @Override
    public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {

        messager.printMessage(Kind.NOTE, "Running ");
        log.info("Running {}", getClass().getSimpleName());

        if(roundEnv.processingOver() || annotations.isEmpty()) {
            return false;
        }

        if(roundEnv.getRootElements() == null || roundEnv.getRootElements().isEmpty()) {
            log.info("No sources to process.");
            return false;
        }

        // アノテーションからエンティティ情報を組み立てます
        List<EntityMetamodel> entityModeles = new ArrayList<>();
        processEntityAnno(roundEnv, entityModeles);

        // ソースの生成
        EntitySpecFactory specFactory = new EntitySpecFactory(messager, metamodelConfig);
        for(EntityMetamodel entityModel : entityModeles) {
            TypeSpec typeSpec = specFactory.create(entityModel);
            generateEntityMetaModel(typeSpec, entityModel);
        }

        return false;
    }

    /**
     * エンティティクラスとして処理する。
     * @param roundEnv 環境情報
     * @param entityModeles 今まで作成してきたエンティティのメタモデル情報
     */
    private void processEntityAnno(final RoundEnvironment roundEnv, final List<EntityMetamodel> entityModeles) {

        // @Entityが付与されたクラスの処理
        for(Element element : roundEnv.getElementsAnnotatedWith(Entity.class)) {
            try {
                entityModeles.add(entityMetamodelFactory.create((TypeElement)element));
            } catch(ClassNotFoundException e) {
                messager.printMessage(Kind.ERROR, e.getMessage(), element);
                log.error("fail entity meta model for @Entity.", e);
            }
        }

        // @MappedSuperclassが付与されたクラスの処理
        for(Element element : roundEnv.getElementsAnnotatedWith(MappedSuperclass.class)) {
            try {
                entityModeles.add(entityMetamodelFactory.create((TypeElement)element));
            } catch(ClassNotFoundException e) {
                messager.printMessage(Kind.ERROR, e.getMessage(), element);
                log.error("fail entity meta model for @MappedSuperclass.", e);
            }
        }

        // @Embeddableが付与されたクラスの処理
        for(Element element : roundEnv.getElementsAnnotatedWith(Embeddable.class)) {
            try {
                entityModeles.add(entityMetamodelFactory.create((TypeElement)element));
            } catch(ClassNotFoundException e) {
                messager.printMessage(Kind.ERROR, e.getMessage(), element);
                log.error("fail entity meta model for @Embeddable.", e);
            }
        }

        // 内部クラスのとき、親クラスに付け替える。
        List<EntityMetamodel> innerEntityModeles = new ArrayList<>();
        for(Iterator<EntityMetamodel> itr = entityModeles.iterator(); itr.hasNext(); ) {
            EntityMetamodel entityModel = itr.next();

            // static 内部クラスのモデル情報を抽出する。
            if(entityModel.getType().isStaticInnerClass()) {
                innerEntityModeles.add(entityModel);
                itr.remove();
            }
        }

        for(EntityMetamodel innerEntityModel : innerEntityModeles) {
            // 親のエンティティモデルに関連付けする。
            for(EntityMetamodel parentEntityModel : entityModeles) {
                if(innerEntityModel.getPackageName().equals(parentEntityModel.getFullName())) {
                    // パッケージ名が親のFQNと一致する場合
                    parentEntityModel.add(innerEntityModel);
                    break;
                }
            }
        }

    }

    /**
     * エンティティのメタモデルクラスのソースを生成します。
     * @param typeSpec 生成するメタモデルのクラス情報
     * @param entityModel 生成するメタモデル情報
     */
    private void generateEntityMetaModel(final TypeSpec typeSpec, final EntityMetamodel entityModel) {
        JavaFile javaFile = JavaFile.builder(entityModel.getPackageName(), typeSpec)
                .indent(metamodelConfig.getIndent())
                .build();

        try {
            javaFile.writeTo(filer);
            log.info("generate Metamodel class - {}", entityModel.getFullName());

        } catch (IOException e) {
            messager.printMessage(Kind.ERROR, e.toString());
            log.error("fail write Metamodel class.", e);
        }
    }

}