package org.maachang.dao.dbms.ctbl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.maachang.conf.Config;
import org.maachang.conf.ReadIni;
import org.maachang.dao.dbms.DbUtil;
import org.maachang.dao.dbms.Record;
import org.maachang.dao.dbms.RecordFactory;
import org.maachang.dao.dbms.kind.SupportKind;
import org.maachang.util.Digest;
import org.maachang.util.FileUtil;

/**
 * テーブル生成オブジェクト.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangDao 1.00
 */
public class CreateTable {
    
    /**
     * LOG.
     */
    private static final Log LOG = LogFactory.getLog( CreateTable.class ) ;
    
    /**
     * バージョン管理ディレクトリ.
     */
    private static final String VERSION_DIR = "_version" ;
    
    /**
     * 結合元テーブルを示す条件.
     */
    private static final String SRC_JOIN = "src_join" ;
    
    /**
     * テーブル結合条件.
     */
    private static final String JOIN = ".join" ;
    
    /**
     * イメージ結合条件.
     */
    private static final String IMAGE_JOIN = "@image" ;
    
    /**
     * テーブル生成モード : ヘルプ.
     */
    private static final String PARAM_HELP = "?" ;
    
    /**
     * テーブル生成モード : 生成.
     */
    private static final String PARAM_CREATE = "create" ;
    
    /**
     * テーブル生成モード : テーブル削除.
     */
    private static final String PARAM_DROP = "drop" ;
    
    /**
     * テーブル生成モード : Model再生性.
     */
    private static final String PARAM_MODEL = "model" ;
    
    /**
     * テーブル生成モード : マスター情報セット.
     */
    private static final String PARAM_MASTER = "master" ;
    
    /**
     * テーブル生成モード : バージョン表示.
     */
    private static final String PARAM_VERSION = "version" ;
    
    /**
     * バージョンコンフィグ拡張子.
     */
    private static final String TABLE_CONF = ".conf" ;
    
    /**
     * マスターデータ拡張子.
     */
    private static final String MASTER_CSV = ".csv" ;
    
    /**
     * マスターデータキャラクタセット.
     */
    private static final String MASTER_CSV_CHARSET = "SHIFT_JIS" ;
    
    /**
     * トランザクションモード.
     * [true]の場合、autoCommit.
     */
    private static final boolean TRANSACTION_MODE = false ;
    
    /**
     * コンストラクタ.
     */
    private CreateTable() {
        
    }
    
    /**
     * テーブル生成.
     * <BR><BR>
     * テーブル生成処理を実行します.
     * <BR>
     * @param mode [false]の場合、バッチ処理で、テーブル生成を行います.
     * @param basePath 対象のテーブルコンフィグディレクトリを設定します.
     * @param outDir モデルファイル出力先ディレクトリ名を設定します.
     * @param factory 対象のレコードファクトリを生成します.
     * @exception Exception 例外.
     */
    public static final void create( boolean mode,String basePath,String outDir,String param,RecordFactory factory )
        throws Exception {
        if( param == null || ( param = param.trim() ).length() <= 0 ) {
            param = PARAM_CREATE ;
        }
        else {
            param = param.toLowerCase() ;
        }
        // ヘルプ表示.
        if( PARAM_HELP.equals( param ) ) {
            viewHelp() ;
            return ;
        }
        if( basePath == null || ( basePath = basePath.trim() ).length() <= 0 ) {
            if( mode == false ) {
                System.out.println( "テーブルコンフィグディレクトリは存在しません" ) ;
                return ;
            }
            throw new IllegalArgumentException( "テーブルコンフィグディレクトリは存在しません" ) ;
        }
        if( factory == null ) {
            if( mode == false ) {
                System.out.println( "データベース定義は存在しません." ) ;
                return ;
            }
            throw new IllegalArgumentException( "データベース定義は存在しません." ) ;
        }
        SupportKind dbms = factory.getSupportKind() ;
        if( dbms == null ) {
            if( mode == false ) {
                System.out.println( "アダプタ名が非サポートのデータベースです." ) ;
                return ;
            }
            throw new IOException( "アダプタ名が非サポートのデータベースです." ) ;
        }
        if( mode == false ) {
            System.out.println( "## startup create table" ) ;
        }
        else {
            LOG.info( "## startup create table" ) ;
        }
        Record record = null ;
        
        try {
            
            Config tableConfig = null ;
            Config lastVersionConfig = null ;
            boolean execVersion = false ;
            
            // 現在の最大バージョンの内容を取得.
            int version = getVersion( basePath ) ;
            
            // バージョン表示.
            if( param.endsWith( PARAM_VERSION ) ) {
                System.out.println( "version " + version ) ;
                return ;
            }
            // マスターファイルをDBに書き込む.
            else if( param.endsWith( PARAM_MASTER ) ) {
                if( mode == false ) {
                    System.out.println( "masterファイルをDBに書き込みます" ) ;
                }
                else {
                    LOG.info( "masterファイルをDBに書き込みます" ) ;
                }
                record = factory.getRecord() ;
                record.getConnection().setAutoCommit( TRANSACTION_MODE ) ;
                insertMaster( mode,record,basePath ) ;
                return ;
            }
            // バージョン指定で実行する場合.
            else if( isNumber( param ) ) {
                tableConfig = getVersionConfig( basePath,param ) ;
                if( tableConfig == null || tableConfig.size() <= 0 ) {
                    if( mode == false ) {
                        System.out.println( "読み込み可能なコンフィグファイルは存在しません." ) ;
                    }
                    else {
                        LOG.info( "読み込み可能なコンフィグファイルは存在しません." ) ;
                    }
                    return ;
                }
                execVersion = true ;
            }
            // 通常実行.
            else {
                
                // コンフィグ内容を読み込む.
                tableConfig = readTableConfig( mode,basePath ) ;
                if( tableConfig == null || tableConfig.size() <= 0 ) {
                    if( mode == false ) {
                        System.out.println( "読み込み可能なコンフィグファイルは存在しません." ) ;
                    }
                    else {
                        LOG.info( "読み込み可能なコンフィグファイルは存在しません." ) ;
                    }
                    return ;
                }
                // テーブルを削除する場合.
                if( param.equals( PARAM_DROP ) ) {
                    if( mode == false ) {
                        System.out.println( "テーブルを削除." ) ;
                    }
                    else {
                        LOG.info( "テーブルを削除." ) ;
                    }
                    if( version > 0 ) {
                        tableConfig = getVersionConfig( basePath,String.valueOf( version ) ) ;
                    }
                    record = factory.getRecord() ;
                    record.getConnection().setAutoCommit( TRANSACTION_MODE ) ;
                    dropTable( mode,record,tableConfig ) ;
                    deleteVersionInfo( basePath ) ;
                    return ;
                }
                // テーブル名に合わせたModelファイルを出力.
                else if( param.equals( PARAM_MODEL ) ) {
                    if( mode == false ) {
                        System.out.println( "modelファイルのみを出力." ) ;
                    }
                    else {
                        LOG.info( "modelファイルのみを出力." ) ;
                    }
                    // Modelファイルを強制出力.
                    fileOutModel( true,outDir,tableConfig ) ;
                    return ;
                }
                
            }
            
            // 最終バージョンファイルを取得.
            if( version > 0 ) {
                lastVersionConfig = getVersionConfig( basePath,String.valueOf( version ) ) ;
            }
            
            // アダプタ名から、対象のテーブルオブジェクトを取得.
            Table table = TableMaster.get( dbms.getAdapter() ) ;
            if( table == null ) {
                if( mode == false ) {
                    System.out.println( "対象アダプタ名[" + dbms.getAdapter() + "]はサポートされていません" ) ;
                }
                else {
                    LOG.info( "対象アダプタ名[" + dbms.getAdapter() + "]はサポートされていません" ) ;
                }
                return ;
            }
            
            // Modelファイルを出力.
            fileOutModel( false,outDir,tableConfig ) ;
            
            String sql = null ;
            // バージョンファイルが存在しない場合は、全SQLを生成.
            if( lastVersionConfig == null ) {
                sql = table.createTable( tableConfig ) ;
            }
            // 最後のバージョン条件と比較して、同じ場合は処理しない.
            else if( equlasConfig( tableConfig,lastVersionConfig ) == true ) {
                if( mode == false ) {
                    System.out.println( "テーブル内容に変更点はありません" ) ;
                }
                else {
                    LOG.info( "テーブル内容に変更点はありません" ) ;
                }
                return ;
            }
            // 最後のバージョン条件と比較して、同じでない場合は、部分実行.
            else {
                sql = partSQL( table,tableConfig,lastVersionConfig ) ;
            }
            
            // 実行可能なSQL文が存在しない場合.
            if( sql == null || ( sql = sql.trim() ).length() <= 0 ) {
                if( mode == false ) {
                    System.out.println( "実行可能なSQL文は存在しません" ) ;
                }
                else {
                    LOG.info( "実行可能なSQL文は存在しません" ) ;
                }
                return ;
            }
            
            // テーブル生成.
            record = factory.getRecord() ;
            record.getConnection().setAutoCommit( TRANSACTION_MODE ) ;
            executionBySql( mode,record,sql ) ;
            
            // バージョンファイルを実行した場合.
            if( execVersion == true ) {
                if( mode == false ) {
                    System.out.println( "## version " + param + TABLE_CONF + " を実行." ) ;
                }
                else {
                    LOG.info( "## version " + param + TABLE_CONF + " を実行." ) ;
                }
            }
            
            // バージョンファイルを出力.
            outputVersionConfig( mode,basePath,version,tableConfig ) ;
            
        } catch( Exception e ) {
            if( record != null ) {
                try {
                    record.rollback() ;
                } catch( Exception ee ) {
                }
            }
            if( mode == false ) {
                System.out.println( "## error" ) ;
                e.printStackTrace() ;
            }
            else {
                LOG.info( "## error",e ) ;
            }
        } finally {
            if( record != null ) {
                try {
                    record.close() ;
                } catch( Exception ee ) {
                }
            }
        }
    }
    
    /**
     * SQL文を実行.
     */
    private static final void executionBySql( boolean mode,Record record,String sql )
        throws Exception {
        BufferedReader buf = null ;
        try {
            long tm = System.currentTimeMillis() ;
            buf = new BufferedReader( new StringReader( sql ) ) ;
            for( ;; ) {
                String one = buf.readLine() ;
                if( one == null ) {
                    break ;
                }
                if( mode == false ) {
                    System.out.println( ">"+one ) ;
                }
                else {
                    LOG.info( ">"+one ) ;
                }
                try {
                    record.executeUpdate( one ) ;
                    try {
                        record.commit() ;
                    } catch( Exception ee ) {
                        try {
                            record.rollback() ;
                        } catch( Exception eee ) {
                        }
                    }
                    if( mode == false ) {
                        System.out.println( ".....OK." ) ;
                    }
                    else {
                        LOG.info( ".....OK." ) ;
                    }
                } catch( Exception e ) {
                    try {
                        record.rollback() ;
                    } catch( Exception ee ) {
                    }
                    String x = one.trim().toLowerCase() ;
                    if( x.startsWith( "create " ) ||
                        ( x.startsWith( "alter " ) && x.indexOf( " add " ) != -1 ) ) {
                        throw e ;
                    }
                    if( mode == false ) {
                        System.out.println( ".....>>"+e ) ;
                    }
                    else {
                        LOG.info( ".....>>"+e ) ;
                    }
                }
                if( mode == false ) {
                    System.out.println( "" ) ;
                }
            }
            try {
                record.commit() ;
            } catch( Exception ee ) {
                try {
                    record.rollback() ;
                } catch( Exception eee ) {
                }
            }
            if( mode == false ) {
                System.out.println( "## success:" + (System.currentTimeMillis()-tm)+"ms" ) ;
            }
            else {
                LOG.info( "## success:" + (System.currentTimeMillis()-tm)+"ms" ) ;
            }
        } finally {
            if( buf != null ) {
                try {
                    buf.close() ;
                } catch( Exception e ) {
                }
            }
        }
    }
    
    /**
     * テーブルを削除.
     */
    private static final void dropTable( boolean mode,Record record,Config config )
        throws Exception {
        Object[] sections = config.getSections() ;
        if( sections != null && sections.length > 0 ) {
            int len = sections.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( sections[ i ] != null &&
                    ( ( String )( sections[ i ] = ( ( String )sections[ i ] ).trim() ) ).length() > 0 ) {
                    for( int j = 0 ; j < 3 ; j ++ ) {
                        try {
                            switch( j ) {
                                case 0 :
                                record.executeUpdate( "drop table " + sections[ i ] + ";\n" ) ;
                                break ;
                                case 1 :
                                record.executeUpdate( "drop sequence " + sections[ i ] + SupportKind.SEQ_NAME + ";\n" ) ;
                                break ;
                                case 2 :
                                record.executeUpdate( "alter table " + sections[ i ] + " drop constraint " + sections[ i ] + "_pk ;\n" ) ;
                                break ;
                            }
                            try {
                                record.commit() ;
                            } catch( Exception ee ) {
                                try {
                                    record.rollback() ;
                                } catch( Exception eee ) {
                                }
                            }
                        } catch( Exception e ) {
                            try {
                                record.rollback() ;
                            } catch( Exception ee ) {
                            }
                        }
                    }
                    if( mode == false ) {
                        System.out.println( "drop table " + sections[ i ] ) ;
                    }
                    else {
                        LOG.info( "drop table " + sections[ i ] ) ;
                    }
                }
            }
        }
    }
    
    /**
     * tableディレクトリ以下の定義ファイルを読み込む.
     */
    private static final Config readTableConfig( boolean mode,String path )
        throws Exception {
        String readPath = FileUtil.getFullPath( path )+FileUtil.FILE_SPACE ;
        Config conf = new Config() ;
        int cnt = readByDir( mode,conf,0,readPath ) ;
        if( mode == false ) {
            System.out.println( "@@@@ read config [END:" + cnt + "]" ) ;
        }
        else {
            LOG.info( "@@@@ read config [END:" + cnt + "]" ) ;
        }
        return conf ;
    }
    
    /**
     * 指定ディレクトリ以下のコンフィグファイルを読み込む.
     */
    private static final int readByDir( boolean mode,Config conf,int cnt,String base )
        throws Exception {
        String dir = base ;
        if( dir.endsWith( "/" ) == false && dir.endsWith( "\\" ) == false ) {
            dir += FileUtil.FILE_SPACE ;
        }
        String[] list = FileUtil.getFileDirList( dir ) ;
        if( list != null && list.length > 0 ) {
            int len = list.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                String c = dir + list[ i ] ;
                if( FileUtil.isDirExists( c ) == true ) {
                    cnt = readByDir( mode,conf,cnt,c ) ;
                }
                else if( FileUtil.isRead( c ) && c.endsWith( TABLE_CONF ) ) {
                    if( mode == false ) {
                        System.out.println( "-- readFile:[" + list[ i ] + "]" ) ;
                    }
                    else {
                        LOG.info( "-- readFile:[" + list[ i ] + "]" ) ;
                    }
                    getIniParams( conf,c ) ;
                    cnt ++ ;
                }
                list[ i ] = null ;
            }
        }
        return cnt ;
    }
    
    /**
     * Configファイルを取得.
     */
    private static final void getIniParams( Config out,String name )
        throws Exception {
        BufferedReader buf = null ;
        try {
            buf = new BufferedReader( new InputStreamReader( new FileInputStream( name ),"UTF8" ) ) ;
            ReadIni.analisys( out,buf ) ;
            buf.close() ;
            buf = null ;
        } finally {
            if( buf != null ) {
                try {
                    buf.close() ;
                } catch( Exception e ) {
                }
            }
            buf = null ;
        }
    }
    
    /**
     * テーブル一覧に対するモデルファイルを生成.
     */
    private static final void fileOutModel( boolean mode,String outDir,Config config )
        throws Exception {
        Object[] o = config.getSections() ;
        if( o == null && o.length <= 0 ) {
            return ;
        }
        if( outDir.endsWith( "\\" ) || outDir.endsWith( "/" ) ) {
            outDir = outDir.substring( 0,outDir.length() - 1 ) ;
        }
        int len = o.length ;
        String date = new SimpleDateFormat( "yyyy/MM/dd HH:mm:ss" ).format( new Date() ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            String s = ( String )o[ i ] ;
            if( s == null || ( s = s.trim() ).length() <= 0 ) {
                continue ;
            }
            String section = s ;
            String javaName = DbUtil.convertDBNameByJavaName( true,s ) ;
            s = outDir + "/" + javaName + ".ms" ;
            if( mode == true || FileUtil.isFileExists( s ) == false ) {
                StringBuilder buf = new StringBuilder() ;
                buf.append( "// " ).append( javaName ).append( " モデル." ).append( "\n" ) ;
                buf.append( "// " ).append( date ).append( "\n" ) ;
                buf.append( "// " ).append( "\n" ) ;
                
                // src_join条件を出力.
                String srcJoin = srcJoinInfo( config,section ) ;
                if( srcJoin != null ) {
                    buf.append( "\n" ) ;
                    buf.append( srcJoin ) ;
                    buf.append( "\n" ) ;
                }
                
                // join条件を出力.
                String join = joinInfo( config,section ) ;
                if( join != null ) {
                    buf.append( "\n" ) ;
                    buf.append( join ) ;
                    buf.append( "\n" ) ;
                }
                
                FileUtil.setFileByString( s,buf.toString() ) ;
                buf = null ;
            }
        }
    }
    
    /**
     * src_join条件を取得.
     */
    private static final String srcJoinInfo( Config config,String section ) {
        String[] srcJoin = config.getAll( section,SRC_JOIN ) ;
        if( srcJoin != null && srcJoin.length > 0 ) {
            StringBuilder buf = new StringBuilder() ;
            int len = srcJoin.length ;
            buf.append( "//** 結合元カラム. **\n" ) ;
            buf.append( DbUtil.convertDBNameByJavaName( true,section ) ).
                append( "." ).append( "srcJoin = [" ) ;
            for( int i = 0 ; i < len ; i ++ ) {
                if( i != 0 ) {
                    buf.append( "," ) ;
                }
                buf.append( "\"" ).
                    append( DbUtil.convertDBNameByJavaName( true,srcJoin[i] ) ).
                    append( "\"" ) ;
            }
            config.removeKey( section,SRC_JOIN ) ;
            buf.append( "] ;" ) ;
            return buf.toString() ;
        }
        return null ;
    }
    
    /**
     * Join条件を取得.
     */
    private static final String joinInfo( Config config,String section ) {
        String[] keys = config.getKeys( section ) ;
        int cnt = 0 ;
        if( keys != null && keys.length > 0 ) {
            int len = keys.length ;
            HashSet<String> imageJoin = new HashSet<String>() ;
            String javaName = DbUtil.convertDBNameByJavaName( true,section ) ;
            StringBuilder buf = new StringBuilder() ;
            buf.append( "//** 結合カラム. **\n" ) ;
            buf.append( javaName ).append( JOIN ).append( "= {" ) ;
            for( int i = 0 ; i < len ; i ++ ) {
                String key = keys[ i ].toLowerCase() ;
                if( key.endsWith( JOIN ) ) {
                    String value = config.get( section,keys[ i ],0 ) ;
                    if( value != null && ( value = value.trim() ).length() > 0 ) {
                        // imageJoinの場合.
                        if( value.equals( IMAGE_JOIN ) ) {
                            imageJoin.add( key.substring( 0,key.length() - JOIN.length() ) ) ;
                        }
                        else {
                            // テーブル結合の場合.
                            if( cnt != 0 ) {
                                buf.append( "," ) ;
                            }
                            String joinKey = key.substring( 0,key.length() - JOIN.length() ) ;
                            joinKey = DbUtil.convertDBNameByJavaName( false,joinKey ) ;
                            buf.append( "\"" ).append( joinKey ).append( "\":" ).
                                append( "\"" ).append( DbUtil.convertDBNameByJavaName( true,value ) ).append( "\"" ) ;
                            cnt ++ ;
                        }
                    }
                    config.removeKey( section,keys[ i ] ) ;
                }
            }
            keys = null ;
            boolean useFlag = false ;
            if( cnt != 0 ) {
                buf.append( "} ;" ) ;
                useFlag = true ;
            }
            // imageJoinが存在する場合.
            if( imageJoin.size() > 0 ) {
                if( useFlag == true ) {
                    buf.append( "\n\n" ) ;
                }
                else {
                    buf = new StringBuilder() ;
                }
                useFlag = true ;
                buf.append( "//** image結合カラム. **\n" ) ;
                buf.append( javaName ).append( ".imageJoin" ).append( "= [" ) ;
                Object[] o = imageJoin.toArray() ;
                len = imageJoin.size() ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( i != 0 ) {
                        buf.append( "," ) ;
                    }
                    buf.append( "\"" ).append( DbUtil.convertDBNameByJavaName( false,( String )o[ i ] ) ).append( "\"" ) ;
                }
                buf.append( "] ;" ) ;
            }
            if( useFlag == true ) {
                return buf.toString() ;
            }
        }
        return null ;
    }
    
    /**
     * バージョンファイル一覧を削除.
     */
    private static final void deleteVersionInfo( String baseDir )
        throws Exception {
        if( baseDir.endsWith( "\\" ) || baseDir.endsWith( "/" ) ) {
            baseDir = baseDir.substring( 0,baseDir.length()-1 ) ;
        }
        String dir = new StringBuilder().
            append( baseDir ).append( FileUtil.FILE_SPACE ).
            append( VERSION_DIR ).append( FileUtil.FILE_SPACE ).toString() ;
        if( FileUtil.isDirExists( dir ) == false ) {
            return ;
        }
        String[] tmp = FileUtil.getFileDirList( dir ) ;
        if( tmp == null || tmp.length <= 0 ) {
            return ;
        }
        int len = tmp.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( tmp[ i ] == null || tmp[ i ].length() <= 0 || tmp[ i ].endsWith( TABLE_CONF ) == false ) {
                continue ;
            }
            FileUtil.removeFile( dir + tmp[ i ] ) ;
        }
    }
    
    /**
     * 現在のバージョン値を取得.
     */
    private static final int getVersion( String baseDir )
        throws Exception {
        if( baseDir.endsWith( "\\" ) || baseDir.endsWith( "/" ) ) {
            baseDir = baseDir.substring( 0,baseDir.length()-1 ) ;
        }
        String dir = new StringBuilder().
            append( baseDir ).append( FileUtil.FILE_SPACE ).
            append( VERSION_DIR ).append( FileUtil.FILE_SPACE ).toString() ;
        if( FileUtil.isDirExists( dir ) == false ) {
            return -1 ;
        }
        String[] tmp = FileUtil.getFileDirList( dir ) ;
        if( tmp == null || tmp.length <= 0 ) {
            return -1 ;
        }
        ArrayList<Integer> lst = new ArrayList<Integer>() ;
        int len = tmp.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( tmp[ i ] == null || tmp[ i ].length() <= 0 || tmp[ i ].endsWith( TABLE_CONF ) == false ) {
                continue ;
            }
            String s = tmp[ i ].substring( 0,tmp[ i ].length() - TABLE_CONF.length() ) ;
            if( isNumber( s ) == false ) {
                continue ;
            }
            lst.add( new Integer( s ) ) ;
        }
        if( lst.size() <= 0 ) {
            return -1 ;
        }
        tmp = null ;
        len = lst.size() ;
        int ret = 0 ;
        for( int i = 0 ; i < len ; i ++ ) {
            Integer t = lst.get( i ) ;
            if( ret < t.intValue() ) {
                ret = t.intValue() ;
            }
        }
        return ret ;
    }
    
    /**
     * 指定バージョンのコンフィグファイルを取得.
     */
    private static final Config getVersionConfig( String baseDir,String version )
        throws Exception {
        if( baseDir.endsWith( "\\" ) || baseDir.endsWith( "/" ) ) {
            baseDir = baseDir.substring( 0,baseDir.length()-1 ) ;
        }
        String file = new StringBuilder().
            append( baseDir ).append( FileUtil.FILE_SPACE ).
            append( VERSION_DIR ).append( FileUtil.FILE_SPACE ).
            append( version ).append( TABLE_CONF ).toString() ;
        if( FileUtil.isFileExists( file ) == false ) {
            return null ;
        }
        Config conf = new Config() ;
        getIniParams( conf,file ) ;
        return conf ;
    }
    
    /**
     * 指定バージョン+1でコンフィグファイルを出力.
     */
    private static final void outputVersionConfig( boolean mode,String baseDir,int version,Config config )
        throws Exception {
        if( baseDir.endsWith( "\\" ) || baseDir.endsWith( "/" ) ) {
            baseDir = baseDir.substring( 0,baseDir.length()-1 ) ;
        }
        String dir = new StringBuilder().
            append( baseDir ).append( FileUtil.FILE_SPACE ).
            append( VERSION_DIR ).append( FileUtil.FILE_SPACE ).toString() ;
        if( FileUtil.isDirExists( dir ) == false ) {
            version = -1 ;
            FileUtil.mkdirs( dir ) ;
        }
        String versionName = null ;
        if( version <= -1 ) {
            versionName = "1" + TABLE_CONF ;
        }
        else {
            versionName = new StringBuilder().append( version+1 ).append( TABLE_CONF ).toString() ;
        }
        String data = allConfig( false,config ) ;
        if( data == null || data.length() <= 0 ) {
            if( mode == false ) {
                System.out.println( "## version " + versionName + " を出力に失敗." ) ;
            }
            else {
                LOG.error( "## version " + versionName + " を出力に失敗." ) ;
            }
            return ;
        }
        FileUtil.setFileByString( dir + versionName,data ) ;
        if( mode == false ) {
            System.out.println( "## version " + versionName + " を出力." ) ;
        }
        else {
            LOG.info( "## version " + versionName + " を出力." ) ;
        }
    }
    
    /**
     * 現在のコンフィグファイル内容を１コンフィグファイル形式にまとめる.
     */
    private static final String allConfig( boolean createMode,Config config )
        throws Exception {
        Object[] o = config.getSections() ;
        if( o == null || o.length <= 0 ) {
            return null ;
        }
        Arrays.sort( o ) ;
        int len = o.length ;
        StringBuilder buf = new StringBuilder() ;
        int cnt = 0 ;
        for( int i = 0 ; i < len ; i ++ ) {
            String section = ( String )o[ i ] ;
            if( section == null || ( section = section.trim() ).length() <= 0 ) {
                continue ;
            }
            section = section.toLowerCase() ;
            String[] keys = config.getKeys( section ) ;
            if( keys == null || keys.length <= 0 ) {
                continue ;
            }
            Arrays.sort( keys ) ;
            int lenJ = keys.length ;
            if( cnt != 0 ) {
                buf.append( "\n" ) ;
            }
            buf.append( "[" ).append( section ).append( "]\n" ) ;
            for( int j = 0 ; j < lenJ ; j ++ ) {
                if( keys[ j ] == null || ( keys[ j ] = keys[ j ].trim() ).length() <= 0 ) {
                    continue ;
                }
                String key = keys[ j ] ;
                if( createMode == true && ( key.toLowerCase().equals( SRC_JOIN ) ||
                    key.toLowerCase().endsWith( JOIN ) ) ) {
                    continue ;
                }
                String[] all = config.getAll( section,key ) ;
                if( all == null || all.length <= 0 ) {
                    continue ;
                }
                trimArray( all ) ;
                Arrays.sort( all ) ;
                int lenK = all.length ;
                for( int k = 0 ; k < lenK ; k ++ ) {
                    if( all[ k ] == null ) {
                        continue ;
                    }
                    buf.append( key ).append( "=" ).append( "\"" ).append( all[ k ] ).append( "\"\n" ) ;
                }
            }
            cnt ++ ;
        }
        return buf.toString() ;
    }
    
    /**
     * 2つのコンフィグ内容を比較.
     */
    private static final boolean equlasConfig( Config src,Config dest )
        throws Exception {
        String srcStr = Digest.convert( "SHA1",allConfig( true,src ).getBytes( "UTF8" ) ) ;
        String destStr = Digest.convert( "SHA1",allConfig( true,dest ).getBytes( "UTF8" ) ) ;
        return srcStr.equals( destStr ) ;
    }
    
    /**
     * 差分情報を取得.
     */
    private static final String partSQL( Table table,Config src,Config dest )
        throws Exception {
        if( src.size() <= 0 || dest.size() <= 0 ) {
            return null ;
        }
        StringBuilder buf = new StringBuilder() ;
        HashSet<String> createKeyTable = new HashSet<String>() ;
        // 追加条件を取得.
        Object[] srcTable = src.getSections() ;
        int len = srcTable.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            String tableName = ( String )srcTable[ i ] ;
            if( table == null || ( tableName = tableName.trim() ).length() <= 0 ||
                src.size( tableName ) <= 0 ) {
                continue ;
            }
            // 追加対象テーブルが存在する場合.
            if( dest.isSection( tableName ) == false ) {
                buf.append( table.createTable( tableName,src ) ) ;
                continue ;
            }
            // 追加対象要素が存在するかチェック.
            String[] keys = src.getKeys( tableName ) ;
            if( keys == null || keys.length <= 0 ) {
                continue ;
            }
            int lenJ = keys.length ;
            for( int j = 0 ; j < lenJ ; j ++ ) {
                String key = keys[ j ] ;
                if( key == null || ( key = key.trim() ).length() <= 0 ||
                    "id".equals( key.toLowerCase() ) || TableUtil.isType( key ) == true ) {
                    continue ;
                }
                // キー追加を検知した場合.
                if( dest.isKeys( tableName,key ) == false ) {
                    buf.append( table.addColumn( tableName,key,src ) ) ;
                    if( isPrimaryKey( src,tableName,key ) == true ) {
                        createKeyTable.add( tableName ) ;
                    }
                }
            }
        }
        srcTable = null ;
        
        // 削除条件を取得.
        Object[] destTable = dest.getSections() ;
        len = destTable.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            String tableName = ( String )destTable[ i ] ;
            if( table == null || ( tableName = tableName.trim() ).length() <= 0 ||
                dest.size( tableName ) <= 0 ) {
                continue ;
            }
            // 削除対象テーブルが存在する場合.
            if( src.isSection( tableName ) == false ) {
                buf.append( table.dropTable( tableName ) ) ;
                continue ;
            }
            // 削除対象要素が存在するかチェック.
            String[] keys = dest.getKeys( tableName ) ;
            if( keys == null || keys.length <= 0 ) {
                continue ;
            }
            int lenJ = keys.length ;
            for( int j = 0 ; j < lenJ ; j ++ ) {
                String key = keys[ j ] ;
                if( key == null || ( key = key.trim() ).length() <= 0 ||
                    "id".equals( key.toLowerCase() ) || TableUtil.isType( key ) == true ) {
                    continue ;
                }
                // キー削除を検知した場合.
                if( src.isKeys( tableName,key ) == false ) {
                    buf.append( table.dropColumn( tableName,key ) ) ;
                    if( isPrimaryKey( dest,tableName,key ) == true ) {
                        createKeyTable.add( tableName ) ;
                    }
                }
            }
        }
        destTable = null ;
        
        // 生成/削除を検出したプライマリーキーを処理.
        if( createKeyTable.size() > 0 ) {
            String addSql = buf.toString() ;
            buf = new StringBuilder() ;
            Object[] pks = createKeyTable.toArray() ;
            len = pks.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                String pk = ( String )pks[ i ] ;
                if( pk == null || ( pk = pk.trim() ).length() <= 0 ) {
                    continue ;
                }
                buf.append( table.dropPrimaryKey( pk ) ) ;
                if( src.isSection( pk ) == true ) {
                    addSql += table.addPrimaryKey( pk,src ) ;
                }
            }
            buf.append( addSql ) ;
        }
        return buf.toString() ;
    }
    
    /**
     * 対象テーブルにキー条件が存在するかチェック.
     */
    private static final boolean isPrimaryKey( Config config,String table,String key ) {
        String[] one = config.getAll( table,key ) ;
        int len = one.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( TableUtil.isIndex( one[ i ] ) == true || TableUtil.isKey( one[ i ] ) == true ) {
                return true ;
            }
        }
        return false ;
    }
    
    /**
     * 指定条件が数値であるかチェック.
     */
    private static final boolean isNumber( String param ) {
        try {
            Integer.parseInt( param ) ;
            return true ;
        } catch( Exception e ) {
        }
        return false ;
    }
    
    /**
     * 指定文字列配列にTrim.
     */
    private static final void trimArray( String[] all ) {
        int len = all.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( all[ i ] == null || ( all[ i ] = all[ i ].trim() ).length() <= 0 ) {
                all[ i ] = null ;
            }
        }
    }
    
    /**
     * ヘルプ内容を表示.
     */
    private static final void viewHelp() {
        System.out.println( "maachangDao テーブル生成ヘルプ." ) ;
        System.out.println() ;
        System.out.println( "create     : テーブル生成処理を行います." ) ;
        System.out.println( "             また、引数なしでも同様の条件を実行します." ) ;
        System.out.println( "drop       : テーブル破棄およびバージョン破棄します." ) ;
        System.out.println( "model      : モデルファイルを再生性します." ) ;
        System.out.println( "master     : マスターファイル(csv)をマスターテーブルに格納します" ) ;
        System.out.println( "version    : 現在のモデルバージョンを表示します." ) ;
    }
    
    /**
     * マスター追加.
     */
    private static final void insertMaster( boolean mode,Record record,String base )
        throws Exception {
        String dir = base ;
        if( dir.endsWith( "/" ) == false && dir.endsWith( "\\" ) == false ) {
            dir += FileUtil.FILE_SPACE ;
        }
        String[] list = FileUtil.getFileDirList( dir ) ;
        if( list != null && list.length > 0 ) {
            int len = list.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                String c = dir + list[ i ] ;
                if( FileUtil.isDirExists( c ) == true ) {
                    insertMaster( mode,record,c ) ;
                }
                else if( FileUtil.isRead( c ) && c.endsWith( MASTER_CSV ) ) {
                    if( mode == false ) {
                        System.out.println( "-- readMasterFile(start):[" + list[ i ] + "]" ) ;
                    }
                    else {
                        LOG.info( "-- readMastarFile(start):[" + list[ i ] + "]" ) ;
                    }
                    // CSVファイルを読み込んで、Insert処理.
                    int cnt = insertMasterCsv( mode,record,c ) ;
                    if( mode == false ) {
                        System.out.println( "-- readMasterFile(end):[" + list[ i ] + "] success:" + cnt ) ;
                    }
                    else {
                        LOG.info( "-- readMastarFile(end):[" + list[ i ] + "] success:" + cnt ) ;
                    }
                }
                list[ i ] = null ;
            }
        }
    }
    
    /**
     * １つのマスターファイルに対してDBにInsertする.
     */
    private static final int insertMasterCsv( boolean mode,Record record,String name )
        throws Exception {
        String file = FileUtil.getFileName( name ) ;
        String table = file.substring( 0,file.length() - MASTER_CSV.length() ) ;
        table = DbUtil.convertJavaNameByDBName( table ) ;
        file = null ;
        BufferedReader br = null ;
        int columnLength = 0 ;
        try {
            br = new BufferedReader( new InputStreamReader( new FileInputStream( name ),MASTER_CSV_CHARSET ) ) ;
            StringBuilder buf = null ;
            String columns = null ;
            boolean idFlag = false ;
            boolean supportBoolean = record.getSupportKind().isBoolean() ;
            int useLength = 0 ;
            for( int cnt = 0 ;; cnt ++ ) {
                String line = br.readLine() ;
                if( line == null ) {
                    break ;
                }
                if( cnt == 0 ) {
                    ArrayList<String> lst = DbUtil.cutCsv( false,line,"," ) ;
                    if( lst == null || lst.size() <= 0 ) {
                        return 0 ;
                    }
                    int len = lst.size() ;
                    columnLength = 0 ;
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( "id".equals( lst.get( i ).toLowerCase() ) ) {
                            idFlag = true ;
                        }
                        columnLength ++ ;
                    }
                    buf = new StringBuilder() ;
                    buf.append( "insert into " ).append( table ).append( " (" ) ;
                    if( idFlag == false ) {
                        buf.append( "id," ) ;
                    }
                    for( int i = 0 ; i < len ; i ++ ) {
                        if( i != 0 ) {
                            buf.append( "," ) ;
                        }
                        buf.append( DbUtil.convertJavaNameByDBName( lst.get( i ).trim() ) ) ;
                    }
                    lst.clear() ;
                    lst = null ;
                    buf.append( ") values(" ) ;
                    columns = buf.toString() ;
                    buf = null ;
                    try {
                        record.executeUpdate( new StringBuilder().append( "delete from ").append( table ).toString() ) ;
                        record.commit() ;
                    } catch( Exception ee ) {
                    }
                    continue ;
                }
                ArrayList<String> lst = DbUtil.cutCsv( false,line,"," ) ;
                if( lst == null || lst.size() <= 0 ) {
                    continue ;
                }
                buf = new StringBuilder() ;
                buf.append( columns ) ;
                int len = lst.size() ;
                // ヘッダ上のデータ件数と一致しない条件の場合は、処理しない.
                if( columnLength != len ) {
                    continue ;
                }
                if( idFlag == false ) {
                    buf.append( cnt ).append( "," ) ;
                }
                for( int i = 0 ; i < len ; i ++ ) {
                    if( i != 0 ) {
                        buf.append( "," ) ;
                    }
                    String value = lst.get( i ).trim() ;
                    if( value == null || value.length() <= 0 ) {
                        value = "null" ;
                        //value = "" ;
                    }
                    else if( supportBoolean == false ) {
                        String v = value.toLowerCase() ;
                        if( "false".equals( v ) ) {
                            value = "\'0\'" ;
                        }
                        else if( "true".equals( v ) ) {
                            value = "\'1\'" ;
                        }
                    }
                    buf.append( value ) ;
                }
                buf.append( ");" ) ;
                lst.clear() ;
                lst = null ;
                String sql = buf.toString() ;
                buf = null ;
                if( mode == false ) {
                    System.out.println( ">sql:" + sql ) ;
                }
                else {
                    LOG.info( ">sql:" + sql ) ;
                }
                try {
                    record.executeUpdate( sql ) ;
                    record.commit() ;
                    useLength ++ ;
                } catch( Exception ee ) {
                    try {
                        record.rollback() ;
                    } catch( Exception eee ) {
                    }
                }
            }
            br.close() ;
            br = null ;
            return useLength ;
        } finally {
            if( br != null ) {
                try {
                    br.close() ;
                } catch( Exception e ) {
                }
            }
            br = null ;
        }
    }
}
