package org.maachang.comet.httpd.engine.script.dao;

import java.util.ArrayList;

import org.maachang.dao.dbms.DbUtil;

/**
 * SQL-Parser.
 * 
 * @version 2007/08/19
 * @author masahito suzuki
 * @since MaachangComet 1.00
 */
public class ParseSql {
    
    private ParseSql() {}
    
    /**
     * 変数ヘッダ.
     */
    private static final char[] VALUE_HEAD = { ':','$' } ;
    
    /**
     * コードタイプ : 無効.
     */
    private static final int USE_NON = -1 ;
    
    /**
     * コードタイプ : a-zA-Z0-9文字.
     */
    private static final int USE_STRING = 1 ;
    
    /**
     * コードタイプ : a-zA-Z0-9以外のascii.
     */
    private static final int USE_OTHER = 2 ;
    
    /**
     * SQLをパース.
     * <BR><BR>
     * SQLをパースします.
     * <BR>
     * @param sql 対象のSQLを設定します.
     * @return ArrayList<String> パースされたSQLが返されます.
     * @exception Exception 例外.
     */
    public static final ArrayList<String> parseSQL( String sql ) throws Exception {
        if( sql == null || ( sql = sql.trim() ).length() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int st ;
        int cote ;
        int before ;
        int len = sql.length() ;
        st = cote = -1 ;
        before = USE_NON ;
        ArrayList<String> ret = new ArrayList<String>() ;
        for( int i = 0 ; i < len ; i ++ ) {
            char c = sql.charAt( i ) ;
            if( cote != -1 ) {
                if( cote == c ) {
                    cote = -1 ;
                    if( st != -1 ) {
                        ret.add( sql.substring( st,i+1 ) ) ;
                        st = -1 ;
                    }
                    before = USE_NON ;
                    continue ;
                }
            }
            else{
                if( c == '\'' || c == '\"' ) {
                    if( st != -1 ) {
                        ret.add( sql.substring( st,i ) ) ;
                        st = -1 ;
                    }
                    cote = c ;
                    st = i ;
                    before = USE_NON ;
                    continue ;
                }
                if( c == '\r' || c == '\n' || c == ' ' || c == '\t' || c == '　' ) {
                    if( st != -1 ) {
                        ret.add( sql.substring( st,i ) ) ;
                        st = -1 ;
                    }
                    before = USE_NON ;
                    continue ;
                }
                if( ( c >= 'a' && c <= 'z' ) ||
                    ( c >= 'A' && c <= 'Z' ) ||
                    ( c >= '0' && c <= '9' ) ||
                    c == '_' ) {
                    if( before == USE_NON ) {
                        st = i ;
                        before = USE_STRING ;
                    }
                    else if( before != USE_STRING ) {
                        if( st != -1 ) {
                            ret.add( sql.substring( st,i ) ) ;
                        }
                        st = i ;
                        before = USE_STRING ;
                    }
                    continue ;
                }
                if( isValue( c ) == true ) {
                    if( before == USE_STRING ) {
                        if( st != -1 ) {
                            ret.add( sql.substring( st,i ) ) ;
                        }
                        st = i ;
                        before = USE_OTHER ;
                    }
                    else if( before == USE_NON ) {
                        if( st != -1 ) {
                            ret.add( sql.substring( st,i ) ) ;
                        }
                        st = i ;
                        before = USE_STRING ;
                    }
                    else if( before != USE_STRING ) {
                        if( st != -1 ) {
                            ret.add( sql.substring( st,i ) ) ;
                        }
                        st = i ;
                        before = USE_STRING ;
                    }
                    continue ;
                }
                
                if( before == USE_STRING ) {
                    if( st != -1 ) {
                        ret.add( sql.substring( st,i ) ) ;
                    }
                    st = i ;
                    before = USE_OTHER ;
                }
                else if( before == USE_NON ) {
                    if( st != -1 ) {
                        ret.add( sql.substring( st,i ) ) ;
                    }
                    st = i ;
                    before = USE_OTHER ;
                }
            }
        }
        if( st != -1 ) {
            ret.add( sql.substring( st,sql.length() ) ) ;
        }
        return ret ;
    }
    
    /**
     * パースしたSQLを戻す.
     * <BR><BR>
     * パースしたSQLを戻します.
     * <BR>
     * @param parse パースされたSQLを設定します.
     * @return String 結合されたSQL文が返されます.
     * @exception Exception 例外.
     */
    public static final String srcSQL( ArrayList<String> parse ) throws Exception {
        if( parse == null || parse.size() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int len = parse.size() ;
        StringBuilder buf = new StringBuilder() ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                buf.append( " " ) ;
            }
            String code = parse.get( i ) ;
            if( isValue( code ) == true ) {
                buf.append( DbUtil.convertJavaNameByDBName( code.substring( 1 ) ) ) ;
            }
            else if( "&&".equals( code ) == true ) {
                buf.append( "and" ) ;
            }
            else if( "||".equals( code ) == true ) {
                buf.append( "or" ) ;
            }
            else {
                buf.append( code ) ;
            }
        }
        String ret = buf.toString() ;
        buf = null ;
        if( ret.endsWith( ";" ) == true ) {
            ret.substring( 0,ret.length()-1 ) ;
        }
        return ret ;
    }
    
    /**
     * 変数文字列一覧を取得.
     * <BR><BR>
     * 変数文字列一覧を取得します.
     * <BR>
     * @param parse パースされたSQLを設定します.
     * @return ArrayList<String> 変数文字列が返されます.
     * @exception Exception 例外.
     */
    public static final ArrayList<String> getValues( ArrayList<String> parse )
        throws Exception {
        if( parse == null || parse.size() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        ArrayList<String> ret = new ArrayList<String>() ;
        int len = parse.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            String code = parse.get( i ) ;
            if( isValue( code ) == true ) {
                ret.add( code.substring( 1,code.length() ) ) ;
            }
        }
        if( ret.size() > 0 ) {
            return ret ;
        }
        return null ;
    }
    
    /**
     * 変数文字列が存在するかチェック.
     * <BR><BR>
     * 変数文字列が存在するかチェックします.
     * <BR>
     * @param parse パースされたSQLを設定します.
     * @return boolean [true]の場合は存在する.
     * @exception Exception 例外.
     */
    public static final boolean isValues( ArrayList<String> parse )
        throws Exception {
        if( parse == null || parse.size() <= 0 ) {
            throw new IllegalArgumentException( "引数は不正です" ) ;
        }
        int len = parse.size() ;
        for( int i = 0 ; i < len ; i ++ ) {
            String code = parse.get( i ) ;
            if( isValue( code ) == true ) {
                return true ;
            }
        }
        return false ;
    }
    
    /**
     * 指定文字コードが変数文字の場合.
     */
    private static final boolean isValue( char c ) {
        int len = VALUE_HEAD.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( c == VALUE_HEAD[ i ] ) {
                return true ;
            }
        }
        return false ;
    }
    
    /**
     * 指定文字列が変数であるかチェック.
     */
    private static final boolean isValue( String code ) {
        if( isValue( code.charAt( 0 ) ) == true ) {
            if( code.length() > 1 ) {
                char c = code.charAt( 1 ) ;
                if( ( c >= 'a' && c <= 'z' ) ||
                    ( c >= 'A' && c <= 'Z' ) ||
                    ( c >= '0' && c <= '9' ) ||
                    c == '_' ) {
                    return true ;
                }
            }
        }
        return false ;
    }
    
}
