/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.extension.jdbc.gen.internal.meta;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import javax.persistence.Entity;

import org.seasar.extension.jdbc.EntityMeta;
import org.seasar.extension.jdbc.EntityMetaFactory;
import org.seasar.extension.jdbc.gen.internal.exception.DocletUnavailableRuntimeException;
import org.seasar.extension.jdbc.gen.internal.exception.EntityClassNotFoundRuntimeException;
import org.seasar.extension.jdbc.gen.internal.util.FileUtil;
import org.seasar.extension.jdbc.gen.meta.EntityMetaReader;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.ClassTraversal;
import org.seasar.framework.util.ClassUtil;
import org.seasar.framework.util.ClassTraversal.ClassHandler;

import com.sun.javadoc.Doclet;

/**
 * {@link EntityMetaReader}の実装クラスです。
 * 
 * @author taedium
 */
public class EntityMetaReaderImpl implements EntityMetaReader {

    /** ロガー */
    protected static Logger logger = Logger
            .getLogger(EntityMetaReaderImpl.class);

    /** {@link Doclet}が使用可能な場合{@code true} */
    protected static boolean docletAvailable;
    static {
        try {
            Class.forName("com.sun.javadoc.Doclet"); // tools.jar
            docletAvailable = true;
        } catch (final Throwable ignore) {
        }
    }

    /** ルートディレクトリ */
    protected File classpathDir;

    /** 読み取り対象とするパッケージ名 */
    protected String packageName;

    /** エンティティメタデータのファクトリ */
    protected EntityMetaFactory entityMetaFactory;

    /** 読み取り対象とするエンティティクラス名のパターン */
    protected Pattern shortClassNamePattern;

    /** 読み取り非対象とするエンティティクラス名のパターン */
    protected Pattern ignoreShortClassNamePattern;

    /** コメントを読む場合 {@code true} */
    protected boolean readComment;

    /**
     * javaファイルが存在するディレクトリのリスト、{@code useComment}が{@code true}の場合{@code null}
     * であってはならない
     */
    protected List<File> javaFileSrcDirList = new ArrayList<File>();

    /**
     * javaファイルのエンコーディング、{@code useComment}が{@code true}の場合{@code null}
     * であってはならない
     */
    protected String javaFileEncoding;

    /**
     * インタスタンスを構築します。
     * 
     * @param classpathDir
     *            ルートディレクトリ
     * @param packageName
     *            パッケージ名、パッケージ名を指定しない場合は{@code null}
     * @param entityMetaFactory
     *            エンティティメタデータのファクトリ
     * @param shortClassNamePattern
     *            対象とするエンティティクラス名の正規表現
     * @param ignoreShortClassNamePattern
     *            対象としないエンティティクラス名の正規表現
     * @param readComment
     *            エンティティのコメントを使用する場合 {@code true}
     * @param javaFileSrcDirList
     *            javaファイルが存在するディレクトリのリスト、{@code readComment}が{@code true}の場合
     *            {@code null}であってはならない
     * @param javaFileEncoding
     *            javaファイルのエンコーディング、{@code readComment}が{@code true}の場合{@code
     *            null}であってはならない
     */
    public EntityMetaReaderImpl(File classpathDir, String packageName,
            EntityMetaFactory entityMetaFactory, String shortClassNamePattern,
            String ignoreShortClassNamePattern, boolean readComment,
            List<File> javaFileSrcDirList, String javaFileEncoding) {
        if (classpathDir == null) {
            throw new NullPointerException("classpathDir");
        }
        if (entityMetaFactory == null) {
            throw new NullPointerException("entityMetaFactory");
        }
        if (shortClassNamePattern == null) {
            throw new NullPointerException("shortClassNamePattern");
        }
        if (ignoreShortClassNamePattern == null) {
            throw new NullPointerException("ignoreShortClassNamePattern");
        }
        if (readComment) {
            if (javaFileSrcDirList == null) {
                throw new NullPointerException("javaFileSrcDirList");
            }
            if (javaFileSrcDirList.isEmpty()) {
                throw new IllegalArgumentException("javaFileSrcDirList");
            }
            if (javaFileEncoding == null) {
                throw new NullPointerException("javaFileEncoding");
            }
        }
        this.classpathDir = classpathDir;
        this.packageName = packageName;
        this.entityMetaFactory = entityMetaFactory;
        this.shortClassNamePattern = Pattern.compile(shortClassNamePattern);
        this.ignoreShortClassNamePattern = Pattern
                .compile(ignoreShortClassNamePattern);
        this.readComment = readComment;
        if (javaFileSrcDirList != null) {
            this.javaFileSrcDirList.addAll(javaFileSrcDirList);
        }
        this.javaFileEncoding = javaFileEncoding;
    }

    public List<EntityMeta> read() {
        final List<EntityMeta> entityMetaList = new ArrayList<EntityMeta>();

        ClassTraversal.forEach(classpathDir, new ClassHandler() {

            public void processClass(String packageName, String shortClassName) {
                if (isTargetPackage(packageName)
                        && isTargetClass(shortClassName)) {
                    String className = ClassUtil.concatName(packageName,
                            shortClassName);
                    Class<?> clazz = ClassUtil.forName(className);
                    if (clazz.isAnnotationPresent(Entity.class)) {
                        EntityMeta entityMeta = entityMetaFactory
                                .getEntityMeta(clazz);
                        entityMetaList.add(entityMeta);
                    }
                }
            }
        });

        if (entityMetaList.isEmpty()) {
            throw new EntityClassNotFoundRuntimeException(classpathDir,
                    packageName, shortClassNamePattern.pattern(),
                    ignoreShortClassNamePattern.pattern());
        }

        if (readComment) {
            readComment(entityMetaList);
        }

        return entityMetaList;
    }

    /**
     * 読み取りの対象パッケージの場合{@code true}を返します。
     * 
     * @param packageName
     *            パッケージ名
     * @return 読み取りの対象パッケージの場合{@code true}
     */
    protected boolean isTargetPackage(String packageName) {
        if (packageName == null) {
            return true;
        }
        if (packageName.equals(this.packageName)) {
            return true;
        }
        if (packageName.startsWith(this.packageName + ".")) {
            return true;
        }
        return false;
    }

    /**
     * 読み取りの対象クラスの場合{@code true}を返します。
     * 
     * @param shortClassName
     *            クラスの単純名
     * @return 読み取りの対象クラスの場合{@code true}
     */
    protected boolean isTargetClass(String shortClassName) {
        if (!shortClassNamePattern.matcher(shortClassName).matches()) {
            return false;
        }
        if (ignoreShortClassNamePattern.matcher(shortClassName).matches()) {
            return false;
        }
        return true;
    }

    /**
     * コメントを読みコメントをメタデータに設定します。
     * 
     * @param entityMetaList
     *            エンティティメタデータのリスト
     */
    protected void readComment(List<EntityMeta> entityMetaList) {
        if (!docletAvailable) {
            throw new DocletUnavailableRuntimeException();
        }
        String[] args = createDocletArgs();
        StringBuilder buf = new StringBuilder();
        for (String arg : args) {
            buf.append(arg).append(" ");
        }
        logger.log("DS2JDBCGen0019", new Object[] { buf.toString() });

        CommentDocletContext.setEntityMetaList(entityMetaList);
        try {
            com.sun.tools.javadoc.Main.execute(args);
        } finally {
            CommentDocletContext.setEntityMetaList(null);
        }
    }

    /**
     * {@link Doclet}の引数の配列を作成します。
     * 
     * @return {@link Doclet}の引数の配列
     */
    protected String[] createDocletArgs() {
        StringBuilder srcDirListBuf = new StringBuilder();
        for (File dir : javaFileSrcDirList) {
            srcDirListBuf.append(FileUtil.getCanonicalPath(dir));
            srcDirListBuf.append(File.pathSeparator);
        }
        srcDirListBuf.setLength(srcDirListBuf.length()
                - File.pathSeparator.length());

        List<String> args = new ArrayList<String>();
        args.add("-doclet");
        args.add(CommentDoclet.class.getName());
        args.add("-sourcepath");
        args.add(srcDirListBuf.toString());
        args.add("-encoding");
        args.add(javaFileEncoding);
        args.add("-subpackages");
        args.add(packageName);
        if (logger.isDebugEnabled()) {
            args.add("-verbose");
        }
        return args.toArray(new String[args.size()]);
    }

    public boolean isFiltered() {
        return !shortClassNamePattern.pattern().equals(".*")
                || !ignoreShortClassNamePattern.pattern().equals("");
    }

}
