package org.maachang.mimdb.core.impl;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.maachang.mimdb.MimdbException;
import org.maachang.mimdb.core.MimdbIndex;
import org.maachang.mimdb.core.MimdbMiddleSearch;
import org.maachang.mimdb.core.MimdbSearchElement;
import org.maachang.mimdb.core.MimdbSearchType;
import org.maachang.mimdb.core.util.NNObjectKeyValue;
import org.maachang.mimdb.core.util.NumberList;
import org.maachang.mimdb.core.util.ObjectKeySet;
import org.maachang.mimdb.core.util.ObjectList;

/**
 * 文字列インデックスカラム.
 * 
 * @version 2013/10/12
 * @author masahito suzuki
 * @since MasterInMemDB 1.00
 */
@SuppressWarnings("unchecked")
public class MStringIndex implements MimdbIndex {
    
    /** ワーク子要素. **/
    private static final class MStringColumnWorkChild {
        MStringIndexKey value ;
        int lineNo ;
        public MStringColumnWorkChild( MStringIndexKey v,int n ) {
            value = v ;
            lineNo = n ;
        }
    }
    
    /** インデックス子要素. **/
    private static final class MStringColumnIndexChild implements Comparable<MStringColumnIndexChild> {
        MStringIndexKey value ;
        NumberList lineNo = new NumberList() ;
        public MStringColumnIndexChild( MStringIndexKey v,int n ) {
            value = v ;
            lineNo.add( n ) ;
        }
        public int compareTo( MStringColumnIndexChild n ) {
            return value.compareTo( n.value ) ;
        }
        public int[] getLineNo() {
            int len = lineNo.size() ;
            int[] ret = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                ret[ i ] = lineNo.get( i ) ;
            }
            return ret ;
        }
    }
    
    /**
     * カラム名.
     */
    protected String name = null ;
    
    /**
     * DbId.
     */
    protected long dbId = -1L ;
    
    /**
     * ngramフラグ.
     */
    protected boolean ngramFlag = false ;
    
    /**
     * ワークデータ.
     */
    private ObjectList<MStringColumnWorkChild> work = null ;
    
    /**
     * ワーク初期サイズ.
     */
    private int workLength = -1 ;
    
    /**
     * インデックスデータ.
     */
    private MStringIndexKey[] indexList = null ;
    
    /**
     * 行番号.
     */
    protected int[][] lineList = null ;
    
    /**
     * Hashインデックス.
     */
    private HashIndex<MStringIndexKey> hashIndex = null ;
    
    /**
     * Hashインデックス利用フラグ.
     */
    private boolean useHashFlag = false ;
    
    /**
     * NULL行番号.
     */
    protected int[] nullLineList = null ;
    
    /**
     * ソート順リスト.
     */
    protected int[] sortNoList = null ;
    
    /**
     * 全データ長.
     */
    protected int allLength = -1 ;
    
    /**
     * NGram-Like検索対応.
     */
    protected Object likeNGram = null ;
    
    /**
     * インデックスフラグ.
     */
    protected boolean indexFlag = false ;
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MStringIndex( long id,String n,boolean h ) {
        this( id,n,0,false,h ) ;
    }
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param size 初期バッファサイズを設定.
     * @param n カラム名を設定します.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MStringIndex( long id,String n,int size,boolean h ) {
        this( id,n,size,false,h ) ;
    }
    
    /**
     * コンストラクタ.
     * @param id DbIdを設定します.
     * @param n カラム名を設定します.
     * @param size 初期バッファサイズを設定.
     * @param ngram Like検索をNGramで行う場合は[true]を設定します.
     * @param h ハッシュインデックスを利用する場合は[true].
     */
    public MStringIndex( long id,String n,int size,boolean ngram,boolean h ) {
        if( n == null || ( n = n.trim().toLowerCase() ).length() <= 0 ) {
            throw new MimdbException( "カラム名が設定されていません" ) ;
        }
        if( size <= 0 ) {
            size = AbstractNumberIndex.DEF_SIZE ;
        }
        dbId = id ;
        workLength = size ;
        name = n ;
        ngramFlag = ngram ;
        useHashFlag = h ;
    }
    
    /**
     * クリア.
     */
    public void clear() {
        work = null ;
        indexList = null ;
        lineList = null ;
        hashIndex = null ;
        nullLineList = null ;
        sortNoList = null ;
        allLength = -1 ;
        likeNGram = null ;
        indexFlag = false ;
    }
    
    /**
     * DB更新IDを取得.
     * この情報が、結果データと一致しない場合は、その結果データは古くなっています.
     * @return int DB更新IDが返却されます.
     */
    public long getDbId() {
        return dbId ;
    }
    
    /**
     * NGramでのLike検索に対応しているかチェック.
     * @return boolean [true]の場合、NGramでのLike検索が可能です.
     */
    public boolean isNGram() {
        return ngramFlag ;
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     */
    public void add( String value,int lineNo ) {
        if( work == null ) {
            work = new ObjectList<MStringColumnWorkChild>( workLength ) ;
        }
        if( value == null ) {
            work.add( new MStringColumnWorkChild( null,lineNo ) ) ;
        }
        else {
            work.add( new MStringColumnWorkChild( new MStringIndexKey( value ),lineNo ) ) ;
        }
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     */
    public void add( char[] value,int lineNo ) {
        if( work == null ) {
            work = new ObjectList<MStringColumnWorkChild>( workLength ) ;
        }
        if( value == null ) {
            work.add( new MStringColumnWorkChild( null,lineNo ) ) ;
        }
        else {
            work.add( new MStringColumnWorkChild( new MStringIndexKey( value ),lineNo ) ) ;
        }
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     */
    public void add( MStringIndexKey value,int lineNo ) {
        if( work == null ) {
            work = new ObjectList<MStringColumnWorkChild>( workLength ) ;
        }
        work.add( new MStringColumnWorkChild( null,lineNo ) ) ;
    }
    
    /**
     * 情報セット.
     * @param value 対象の情報を設定します.
     * @param lineNo 行番号を設定します.
     * @exception Exception 例外.
     */
    public void add( Object value,int lineNo ) throws Exception {
        add( MimdbUtils.convertString( value ),lineNo ) ;
    }
    
    /**
     * インデックスの生成.
     * @exception Exception 例外.
     */
    public void createIndex() throws Exception {
        if( work == null ) {
            clear() ;
            return ;
        }
        
        // 前のインデックスをクリア.
        indexList = null ;
        lineList = null ;
        nullLineList = null ;
        sortNoList = null ;
        allLength = -1 ;
        likeNGram = null ;
        indexFlag = false ;
        
        // インデックス条件の作成.
        NumberList nullList = new NumberList( 32 ) ;
        MStringColumnWorkChild k ;
        MStringColumnIndexChild n ;
        int len = work.size() ;
        int _allLength = len ;
        
        // 同一Valueの条件を集計.
        NNObjectKeyValue<MStringIndexKey,MStringColumnIndexChild> map =
            new NNObjectKeyValue<MStringIndexKey,MStringColumnIndexChild>( len << 1 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            k = work.get( i ) ;
            // NULLデータの場合.
            if( k.value.isNull() ) {
                nullList.add( k.lineNo ) ;
            }
            // 通常データの場合.
            else {
                if( ( n = map.get( k.value ) ) != null ) {
                    n.lineNo.add( k.lineNo ) ;
                }
                else {
                    map.put( k.value,new MStringColumnIndexChild( k.value,k.lineNo ) ) ;
                }
            }
        }
        k = null ;
        n = null ;
        
        // ワークデータの廃棄.
        work = null ;
        
        // 実データのインデックス条件が存在する場合.
        len = map.size() ;
        if( len > 0 ) {
            // 同一Value条件の設定.
            MStringColumnIndexChild[] lst = new MStringColumnIndexChild[ len ] ;
            NNObjectKeyValue<MStringIndexKey,MStringColumnIndexChild> it = map.reset() ;
            int cnt = 0 ;
            while( it.hasNext() ) {
                lst[ cnt ++ ] = map.get( it.next() ) ;
            }
            it = null ;
            map = null ;
            // ソート処理.
            Arrays.sort( lst ) ;
            
            // ソート結果の条件を設定.
            MStringIndexKey[] _indexList = new MStringIndexKey[ len ] ;
            int[][] _lineList = new int[ len ][] ;
            HashIndex<MStringIndexKey> _hashIndex = null ;
            
            // ソート項番.
            int[] _sortNoList = new int[ _allLength ] ;
            Arrays.fill( _sortNoList,-1 ) ;
            
            int j,lenJ ;
            int[] nlst ;
            
            // インデックス条件を抽出.
            for( int i = 0 ; i < len ; i ++ ) {
                n = lst[ i ] ;
                _indexList[ i ] = n.value ;
                nlst = n.getLineNo() ; ;
                _lineList[ i ] = nlst ;
                
                lenJ = nlst.length ;
                for( j = 0 ; j < lenJ ; j ++ ) {
                    _sortNoList[ nlst[ j ] ] = i ;
                }
            }
            lst = null ;
            
            // HashIndexを作成する場合.
            if( useHashFlag ) {
                _hashIndex = new HashIndex<MStringIndexKey>( _indexList ) ;
            }
            
            // インデックス条件のセット.
            indexList = _indexList ;
            lineList = _lineList ;
            hashIndex = _hashIndex ;
            sortNoList = _sortNoList ;
            
            // Ngram検索が指定されている場合.
            if( ngramFlag ) {
                likeNGram = NGram.createIndex( _indexList ) ;
            }
        }
        
        // NULLリストのインデックス条件が存在する場合.
        len = nullList.size() ;
        if( len > 0 ) {
            int[] _nullLineList = new int[ len ] ;
            for( int i = 0 ; i < len ; i ++ ) {
                _nullLineList[ i ] = nullList.get( i ) ;
            }
            nullList = null ;
            
            // インデックス条件のセット.
            nullLineList = _nullLineList ;
        }
        
        // インデックス情報をセット.
        allLength = _allLength ;
        indexFlag = true ;
        
    }
    
    /** 検索内容の整合性処理. **/
    protected MimdbSearchElement _checkSearchInfo( MimdbSearchElement info ) throws Exception {
        // IN,between検索の場合.
        if( info.getType() == MimdbSearchType.TYPE_IN ||
            info.getType() == MimdbSearchType.TYPE_BETWEEN ) {
            // リストで無い場合は、リストに変換する.
            Object o = info.getValue() ;
            if( !( o instanceof List ) ) {
                List<MStringIndexKey> lst = new ArrayList<MStringIndexKey>( 1 ) ;
                if( o == null ) {
                    lst.add( null ) ;
                }
                else {
                    lst.add( new MStringIndexKey( MimdbUtils.convertString( o ) ) ) ;
                }
                info.setValue( lst ) ;
            }
            else {
                List n = (List)o ;
                int len = n.size() ;
                List<MStringIndexKey> lst = new ArrayList<MStringIndexKey>( len ) ;
                for( int i = 0 ; i < len ; i ++ ) {
                    if( n.get( i ) == null ) {
                        lst.add( null ) ;
                    }
                    else {
                        lst.add( new MStringIndexKey( MimdbUtils.convertString( n.get( i ) ) ) ) ;
                    }
                }
                info.setValue( lst ) ;
            }
            return info ;
        }
        // in以外.
        if( info.getValue() == null ) {
            info.setValue( null ) ;
        }
        else {
            info.setValue( new MStringIndexKey( MimdbUtils.convertString( info.getValue() ) ) ) ;
        }
        return info ;
    }
    
    /** バイナリ検索(MStringIndexKey). **/
    protected final int _stringBinarySearchMString( MStringIndexKey target ) {
        if( indexList == null ) {
            return -1 ;
        }
        // Hashインデックスで検索.
        if( useHashFlag ) {
            return hashIndex.eq( target ) ;
        }
        return MStringIndexKey.searchMStringIndexKey( indexList,target ) ;
    }
    
    /** 大なり小なり検索処理. **/
    protected int _MStringBinarySearchBM( boolean big,boolean eq,MStringIndexKey target ) {
        if( indexList == null ) {
            return -1 ;
        }
        int p = MStringIndexKey.searchMStringIndexKeyBS( big,indexList,target ) ;
        // 同一条件が許可されている場合.
        if( eq ) {
            // 大なり.
            if( big ) {
                while( p < indexList.length ) {
                    if( indexList[ p ].toBig( target ) > 0 ) {
                        ++ p ;
                        continue ;
                    }
                    break ;
                }
            }
            // 小なり.
            else {
                while( p < indexList.length ) {
                    if( indexList[ p ].toBig( target ) <= 0 ) {
                        ++ p ;
                        continue ;
                    }
                    -- p ;
                    break ;
                }
            }
        }
        // 同一条件が許可されていない場合.
        else {
            // 大なり.
            if( big ) {
                while( p < indexList.length ) {
                    if( indexList[ p ].toBig( target ) >= 0 ) {
                        ++ p ;
                        continue ;
                    }
                    break ;
                }
            }
            // 小なり.
            else {
                while( p < indexList.length ) {
                    if( indexList[ p ].toBig( target ) < 0 ) {
                        ++ p ;
                        continue ;
                    }
                    -- p ;
                    break ;
                }
            }
        }
        return p ;
    }
    
    /** Likeノーマル. **/
    protected final int[] normalLike( Object[] ps,int allLen )
        throws Exception {
        int len = ps.length ;
        
        // 通常パース条件.
        int n = -99 ; // 定義不明.
        int rLen,j,p ;
        MStringIndexKey v,value ;
        MStringIndexKey[] indexs = indexList ;
        // 最後方チェック条件.
        boolean last = false ;
        
        // 各場所保存条件を設定.
        NGramResult list = null ;
        NGramResult newList = null ;
        
        // 前回のindexof検索文字.
        MStringIndexKey bw = null ;
        
        // 通常パース開始.
        for( int i = 0 ; i < len ; i ++ ) {
            // 条件指定.
            if( ps[ i ] instanceof Integer ) {
                // 最後方検索条件の場合.
                if( (Integer)ps[ i ] == LikeParser.POS_LAST ) {
                    last = true ;
                }
                // 通常条件 or 検索条件.
                else {
                    n = (Integer)ps[ i ] ;
                }
            }
            // 文字指定.
            else {
                v = (MStringIndexKey)ps[ i ] ;
                // N文字指定の場合.
                if( n > 0 ) {
                    if( list == null ) {
                        list = new NGramLineList( allLen ) ;
                    }
                    newList = list.newList() ;
                    rLen = list.length() ;
                    // 最後方一致で検索する場合.
                    if( last ) {
                        // 前回の条件で、indexof検索が行われていない場合.
                        if( bw == null ) {
                            for( j = 0 ; j < rLen ; j ++ ) {
                                value = indexs[ list.position( j ) ] ;
                                // 条件不一致.
                                if( value.length() == list.offset( j ) + n + v.length() &&
                                    value.equals( list.offset( j ) + n,v,0,v.length() ) ) {
                                    // 条件一致.
                                    newList.add( list.position( j ),list.offset( j ) + n + v.length() ) ;
                                }
                            }
                        }
                        // 前回の条件で、indexof検索が行われた場合.
                        else {
                            int bwLen = bw.length ;
                            for( j = 0 ; j < rLen ; j ++ ) {
                                value = indexs[ list.position( j ) ] ;
                                while( true ) {
                                    // 条件不一致.
                                    if( value.length() == list.offset( j ) + n + v.length() &&
                                        value.equals( list.offset( j ) + n,v,0,v.length() ) ) {
                                        // 条件一致.
                                        newList.add( list.position( j ),list.offset( j ) + n + v.length() ) ;
                                        break ;
                                    }
                                    // 前回のワードで再検索.
                                    if( ( p = value.indexOf( bw,list.offset( j ) - ( bwLen - 1 ) ) ) != -1 ) {
                                        list.offset( j,p+bwLen ) ;
                                    }
                                    else {
                                        // 検索対象は存在しない.
                                        break ;
                                    }
                                }
                            }
                        }
                        // 最終確認処理は行わない.
                        last = false ;
                    }
                    // 通常一致.
                    else {
                        // 前回の条件で、indexof検索が行われていない場合.
                        if( bw == null ) {
                            for( j = 0 ; j < rLen ; j ++ ) {
                                value = indexs[ list.position( j ) ] ;
                                // 条件不一致.
                                if( value.length() >= list.offset( j ) + n + v.length() &&
                                    value.equals( list.offset( j ) + n,v,0,v.length() ) ) {
                                    // 条件一致.
                                    newList.add( list.position( j ),list.offset( j ) + n + v.length() ) ;
                                }
                            }
                        }
                        // 前回の条件で、indexof検索が行われた場合.
                        else {
                            for( j = 0 ; j < rLen ; j ++ ) {
                                value = indexs[ list.position( j ) ] ;
                                while( true ) {
                                    // 条件不一致.
                                    if( value.length() >= list.offset( j ) + n + v.length() &&
                                        value.equals( list.offset( j ) + n,v,0,v.length() ) ) {
                                        // 条件一致.
                                        newList.add( list.position( j ),list.offset( j ) + n + v.length() ) ;
                                        break ;
                                    }
                                    // 前回のワードで再検索.
                                    if( ( p = value.indexOf( bw,list.offset( j ) ) ) != -1 ) {
                                        list.offset( j,p+bw.length ) ;
                                    }
                                    else {
                                        // 検索対象は存在しない.
                                        break ;
                                    }
                                }
                            }
                        }
                    }
                    // データ切り替え.
                    list = newList ;
                    newList = null ;
                    n = -99 ;// 定義不明.
                    // 前回のindexof検索条件をクリア.
                    bw = null ;
                }
                // 条件指定の場合.
                // ただし、指定より、最後方チェックの方が優先される.
                else if( !last ) {
                    switch( n ) {
                        case LikeParser.POS_FIRST : // 先頭一致.
                            newList = new NGramLineList( false,allLen ) ;
                            rLen = allLen ;
                            // 先頭検索のみ、インデックスの最適位置で処理できる.
                            j = MStringIndexKey.searchMStringIndexKeyBS( false,indexs,v ) ;
                            // 開始位置で一致しない場合は、次から検索対象とする.
                            if( !indexs[ j ].startsWith( v ) ) {
                                ++ j ;
                            }
                            for( ; j < rLen ; j ++ ) {
                                if( indexs[ j ].startsWith( v ) ) {
                                    newList.add( j,v.length() ) ;
                                }
                                else {
                                    // 以降は存在しないので、処理を抜ける.
                                    break ;
                                }
                            }
                            // データ切り替え.
                            list = newList ;
                            newList = null ;
                            break ;
                        case LikeParser.POS_BETWEEN : // 次の文字列が一致.
                            if( list != null ) {
                                newList = list.newList() ;
                                rLen = list.length() ;
                                for( j = 0 ; j < rLen ; j ++ ) {
                                    if( ( p = indexs[ list.position( j ) ].indexOf( v,list.offset( j ) ) ) != -1 ) {
                                        newList.add( list.position( j ),p + v.length() ) ;
                                    }
                                }
                            }
                            else {
                                newList = new NGramLineList( false,allLen ) ;
                                rLen = allLen ;
                                for( j = 0 ; j < rLen ; j ++ ) {
                                    if( ( p = indexs[ j ].indexOf( v,0 ) ) != -1 ) {
                                        newList.add( j,p + v.length() ) ;
                                    }
                                }
                            }
                            // 前回のindexof検索条件としてセット.
                            bw = v ;
                            // データ切り替え.
                            list = newList ;
                            newList = null ;
                            break ;
                        case -99 :
                            throw new MimdbException( "Like構文が不正です" ) ;
                    }
                }
                // 最後方チェック条件の場合.
                if( last ) {
                    if( list != null ) {
                        newList = list.newList() ;
                        rLen = list.length() ;
                        for( j = 0 ; j < rLen ; j ++ ) {
                            if( indexs[ list.position( j ) ].length == list.offset( j ) + 1 + v.length() &&
                                indexs[ list.position( j ) ].endsWith( v ) ) {
                                newList.add( list.position( j ),v.length() ) ;
                            }
                        }
                    }
                    // 前回の条件で、indexof検索が行われた場合.
                    else {
                        newList = new NGramLineList( false,allLen ) ;
                        rLen = allLen ;
                        for( j = 0 ; j < rLen ; j ++ ) {
                            if( indexs[ j ].endsWith( v ) ) {
                                newList.add( j,v.length() ) ;
                            }
                        }
                    }
                    // データ切り替え.
                    list = newList ;
                    newList = null ;
                    // 前回のindexof検索条件をクリア.
                    bw = null ;
                    break ;
                }
                // 検索条件がゼロ件の場合.
                if( list == null || list.length() <= 0 ) {
                    return AbstractNumberIndex.NOT ;
                }
            }
        }
        // 検索条件がゼロ件の場合.
        if( list == null || list.length() <= 0 ) {
            return AbstractNumberIndex.NOT ;
        }
        // N文字指定の場合.
        else if( n > 0 ) {
            newList = list.newList() ;
            rLen = list.length() ;
            // 前回の条件で、indexof検索が行われていない場合.
            if( bw == null ) {
                for( j = 0 ; j < rLen ; j ++ ) {
                    // 条件一致
                    if( indexs[ list.position( j ) ].length() == list.offset( j ) + n ) {
                        // 条件一致.
                        newList.add( list.position( j ),0 ) ;
                    }
                }
            }
            else {
                int bwLen = bw.length ;
                // 条件一致
                for( j = 0 ; j < rLen ; j ++ ) {
                    value = indexs[ list.position( j ) ] ;
                    while( true ) {
                        // 条件一致
                        if( value.length() == list.offset( j ) + n ) {
                            // 条件一致.
                            newList.add( list.position( j ),0 ) ;
                            break ;
                        }
                        // 前回のワードで再検索.
                        if( ( p = value.indexOf( bw,list.offset( j ) - ( bwLen - 1 ) ) ) != -1 ) {
                            list.offset( j,p+bwLen ) ;
                        }
                        else {
                            // 検索対象は存在しない.
                            break ;
                        }
                    }
                }
            }
            // データ切り替え.
            list = newList ;
            newList = null ;
            // 検索条件がゼロ件の場合.
            if( list.length() <= 0 ) {
                return AbstractNumberIndex.NOT ;
            }
        }
        return list.list() ;
    }
    
    /** Like-NGram. **/
    protected final int[] ngramLike( Object[] ps,int allLen )
        throws Exception {
        int len = ps.length ;
        
        // 通常パース条件.
        int n = -99 ; // 定義不明.
        MStringIndexKey v ;
        MStringIndexKey[] indexs = indexList ;
        
        // 各場所保存条件を設定.
        NGramResult list = null ;
        NGramResult newList = null ;
        // 最後方チェック条件.
        boolean last = false ;
        
        // 前回のindexof検索文字.
        MStringIndexKey bw = null ;
        
        // 通常パース開始.
        for( int i = 0 ; i < len ; i ++ ) {
            // 条件指定.
            if( ps[ i ] instanceof Integer ) {
                // 最後方検索条件の場合.
                if( (Integer)ps[ i ] == LikeParser.POS_LAST ) {
                    last = true ;
                }
                // 通常条件 or 検索条件.
                else {
                    n = (Integer)ps[ i ] ;
                }
            }
            // 文字指定.
            else {
                v = (MStringIndexKey)ps[ i ] ;
                // N文字指定の場合.
                if( n > 0 ) {
                    // リスト条件が設定されていない場合.
                    if( list == null ) {
                        list = new NGramLineList( allLen ) ;
                    }
                    // 最後方一致で検索する場合.
                    if( last ) {
                        // 現在ポジションから続く、指定文字の検索.
                        newList = NGram.eqaulsPosition( true,list,likeNGram,v,n,bw ) ;
                        // 最終確認処理は行わない.
                        last = false ;
                    }
                    // 通常一致検索.
                    else {
                        // 現在ポジションから続く、指定文字の検索.
                        newList = NGram.eqaulsPosition( false,list,likeNGram,v,n,bw ) ;
                    }
                    
                    // データ切り替え.
                    list = newList ;
                    newList = null ;
                    n = -99 ;// 定義不明.
                    bw = null ; // 前回のindexof条件をクリア.
                }
                // 条件指定の場合.
                // ただし、指定より、最後方チェックの方が優先される.
                else if( !last ) {
                    switch( n ) {
                        case LikeParser.POS_FIRST : // 先頭一致.
                            list = NGram.startsWith( likeNGram,v ) ;
                            break ;
                        case LikeParser.POS_BETWEEN : // 次の文字列が一致.
                            list = NGram.indexOf( list,likeNGram,v ) ;
                            bw = v ; // 前回のindexof条件をセット.
                            break ;
                        case -99 :
                            throw new MimdbException( "Like構文が不正です" ) ;
                    }
                }
                // 最後方チェック条件の場合.
                if( last ) {
                    list = NGram.endsWith( list,likeNGram,v ) ;
                    bw = null ; // 前回のindexof条件をクリア.
                    break ;
                }
                // 検索条件がゼロ件の場合.
                if( list == null || list.length() <= 0 ) {
                    return AbstractNumberIndex.NOT ;
                }
            }
        }
        // 検索条件がゼロ件の場合.
        if( list == null || list.length() <= 0 ) {
            return AbstractNumberIndex.NOT ;
        }
        // N文字指定の場合.
        else if( n > 0 ) {
            // 長さが一致する条件のみ処理.
            ((NGramLineList)list).endOffset( n,indexs,bw ) ;
            
            // 検索条件がゼロ件の場合.
            if( list.length() <= 0 ) {
                return AbstractNumberIndex.NOT ;
            }
        }
        return list.list() ;
    }
    
    /** [Like]Likeパーサーの生成. **/
    protected final Object[] _createLikeParser( String like ) throws Exception {
        LikeParser p = LikeAnalyzer.parser( like ) ;
        Object[] o = ( ( LikeParserImpl )p ).parser ;
        int len = o.length ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( o[ i ] instanceof String ) {
                o[ i ] = new MStringIndexKey( ( String )o[ i ] ) ;
            }
        }
        return o ;
    }
    
    /** 検索位置に対する有効行数の合計値を取得. **/
    protected final int _calcUseLineSize( int[] posList ) {
        if( posList == null ) {
            return 0 ;
        }
        int ret = 0 ;
        int len = posList.length ;
        int[] n ;
        for( int i = 0 ; i < len ; i ++ ) {
            // データが何らか存在する場合.
            if( posList[ i ] != -1 ) {
                // null.
                if( posList[ i ] == -9 ) {
                    n = nullLineList ;
                }
                // インデックスデータ.
                else {
                    n = lineList[ posList[ i ] ] ;
                }
                ret += n.length ;
            }
        }
        return ret ;
    }
    
    /** 新規検索データの作成. **/
    protected final MimdbMiddleSearch _createMiddleSearch( MimdbMiddleSearch and,MimdbMiddleSearch or ) {
        
        // AND指定の場合.
        if( and != null ) {
            
            // AND属性で生成(オブジェクトを使いまわす).
            FlagsMiddleSearch ret = ( FlagsMiddleSearch )and ;
            ret.create( ret.dbId,ret.noList ) ;
            
            // ORも同時に指定されている場合.
            if( or != null ) {
                ret.or( or ) ;
            }
            
            return ret ;
        }
        // OR指定されている場合.
        else if( or != null ) {
            // AND属性の場合.
            if( or.isAnd() ) {
                // OR属性に変更.
                or.or( or ) ;
            }
            return or ;
        }
        // 何も条件が指定されていない場合.
        else {
            // 通常条件で作成.
            return new FlagsMiddleSearch( dbId,allLength ) ;
        }
    }
    
    /** 検索に対する有効行番号群をセット. **/
    protected final void _addSearchLineNo( MimdbMiddleSearch ret,int pos ) {
        // データなし.
        if( pos == -1 ) {
            return ;
        }
        
        int[] n ;
        
        // null.
        if( pos == -9 ) {
            n = nullLineList ;
        }
        // 通常情報.
        else {
            n = lineList[ pos ] ;
        }
        
        // 行情報の追加.
        _addLineNo( ret,true,n ) ;
    }
    
    /** 検索に対する有効番号群をクリア. **/
    protected final void _addSearchLineOffNo( MimdbMiddleSearch ret,int pos ) {
        // データなし.
        if( pos == -1 ) {
            return ;
        }
        
        int[] n ;
        
        // null.
        if( pos == -9 ) {
            n = nullLineList ;
        }
        // 通常情報.
        else {
            n = lineList[ pos ] ;
        }
        
        // 行情報の追加.
        _addLineNo( ret,false,n ) ;
    }
    
    /** 行番号群をセット. **/
    protected final void _addLineNo( MimdbMiddleSearch ret,boolean onFlg,int[] n ) {
        
        // 指定番号をONに設定.
        if( onFlg ) {
            
            ret.addArray( n ) ;
        }
        // 指定番号をOFFに設定.
        else {
            
            ret.offArray( n ) ;
        }
    }
    
    /** 基本検索. **/
    /** 検索結果はインデックス位置が返却される **/
    protected final int _searchNormal( MimdbSearchElement target ) throws Exception {
        if( lineList == null ) {
            return -1 ;
        }
        // 検索条件に応じた内容を取得.
        switch( target.getType() ) {
            case MimdbSearchType.TYPE_EQ :
            case MimdbSearchType.TYPE_NEQ :
                // 完全一致.
                return _stringBinarySearchMString( ( MStringIndexKey )target.getValue() ) ;
            case MimdbSearchType.TYPE_LBIG :
                // 大小一致.
                return _MStringBinarySearchBM( true,false,( MStringIndexKey )target.getValue() ) ;
            case MimdbSearchType.TYPE_LEQBIG :
                // 大小一致.
                return _MStringBinarySearchBM( true,true,( MStringIndexKey )target.getValue() ) ;
            case MimdbSearchType.TYPE_RBIG :
                // 大小一致.
                return _MStringBinarySearchBM( false,false,( MStringIndexKey )target.getValue() ) ;
            case MimdbSearchType.TYPE_REQBIG :
                // 大小一致.
                return _MStringBinarySearchBM( false,true,( MStringIndexKey )target.getValue() ) ;
        }
        throw new MimdbException( "数字検索に対して、不当な検索条件[" + target.getType() + "]です" ) ;
    }
    
    /** In検索. **/
    /** 検索結果はインデックス位置のリストが返却される. **/
    protected final int[] _searchIn( MimdbSearchElement target ) throws Exception {
        Object o ;
        List searchInfo = (List)target.getValue() ;
        int len = searchInfo.size() ;
        if( len <= 0 ) {
            return AbstractNumberIndex.NOT ;
        }
        int[] ret = new int[ len ] ;
        ObjectKeySet<Object> dblChk = new ObjectKeySet<Object>( len << 1 ) ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 同一条件チェック.
            o = searchInfo.get( i ) ;
            if( dblChk.contains( o ) ) {
                ret[ i ] = -1 ;
                continue ;
            }
            dblChk.add( o ) ;
            if( o == null ) {
                ret[ i ] = -9 ; // null.
            }
            else {
                // 完全一致.
                ret[ i ] = _stringBinarySearchMString( ( MStringIndexKey )o ) ;
            }
        }
        return ret ;
    }
    
    /** BETWEEN検索. **/
    /** 検索結果は、指定条件の[開始位置、終了位置]の条件が返却される. **/
    protected final int[] _searchBitween( MimdbSearchElement target ) throws Exception {
        Object o ;
        final List searchInfo = (List)target.getValue() ;
        final int len = searchInfo.size() ;
        if( len <= 0 ) {
            return AbstractNumberIndex.NOT ;
        }
        final int[] ret = new int[ len ] ;
        for( int i = 0 ; i < len ; i ++ ) {
            // 条件指定なし.
            if( ( o = searchInfo.get( i ) ) == null ) {
                ret[ i ] = -1 ;
            }
            // 偶数番(指定値より大きい条件).
            else if( (i & 1) == 0 ) {
                if( i + 1 < len && searchInfo.get( i+1 ) != null ) {
                    
                    // 開始位置、終了位置の数値が逆転している場合.
                    if( ((Comparable)o).compareTo( searchInfo.get( i+1 ) ) > 0 ) {
                        
                        // 開始、終了位置の条件を逆転させる.
                        ret[ i ] = _MStringBinarySearchBM( true,true,( MStringIndexKey )searchInfo.get( i+1 ) ) ;
                        ret[ i+1 ] = _MStringBinarySearchBM( false,true,( MStringIndexKey )o ) ;
                        i ++ ;
                        
                        continue ;
                    }
                }
                
                // 開始位置を取得.
                ret[ i ] = _MStringBinarySearchBM( true,true,( MStringIndexKey )o ) ;
            }
            // 奇数番(指定値より小さい条件).
            else {
                
                // 終了位置を取得.
                ret[ i ] = _MStringBinarySearchBM( false,true,( MStringIndexKey )o ) ;
            }
        }
        return ret ;
    }
    
    /** LIKE検索. **/
    /** 検索結果はインデックス位置のリストが返却される. **/
    /** また、この情報は、インデックスが利かない. **/
    protected final int[] _searchLike( MimdbSearchElement target ) throws Exception {
        // Like検索は、[%,_]をサポート.
        // [%][*]は不明な複数文字が設定されている内容.
        // [_][?]は不明な１文字が設定されている内容.
        
        // Likeパーサ作成.
        Object[] ps = _createLikeParser( target.getValue().toString() ) ;
        
        int allLen = indexList.length ;
        int len = ps.length ;
        // [特殊条件]内容が１文字の場合は、％指定のみか、_文字数定義.
        if( len == 1 ) {
            int n = (Integer)ps[ 0 ] ;
            if( n == LikeParser.POS_BETWEEN ) {
                // 全データが対象.
                int[] ret = new int[ allLen ] ;
                for( int i = 0 ; i < allLen ; i ++ ) {
                    ret[ i ] = i ;
                }
                return ret ;
            }
            // インデックス情報が存在しない場合.
            if( indexList == null ) {
                return AbstractNumberIndex.NOT ;
            }
            // 長さ一致.
            MStringIndexKey[] indexs = indexList ;
            int[] lst = new int[ allLen ] ;
            int lstCnt = 0 ;
            for( int i = 0 ; i < allLen ; i ++ ) {
                if( indexs[ i ].length == n ) {
                    lst[ lstCnt ++ ] = i ;
                }
            }
            int[] ret = new int[ lstCnt ] ;
            System.arraycopy( lst,0,ret,0,lstCnt ) ;
            lst = null ;
            return ret ;
        }
        // インデックス情報が存在しない場合.
        if( indexList == null ) {
            return AbstractNumberIndex.NOT ;
        }
        // [特殊条件]パース条件が3文字の場合.
        if( len == 3 ) {
            // Like構文ではなく、完全一致条件の場合.
            if( ps[ 0 ] instanceof Integer &&
                ps[ 1 ] instanceof Integer &&
                ps[ 2 ] instanceof MStringIndexKey &&
                ( Integer )ps[ 0 ] == LikeParser.POS_FIRST &&
                ( Integer )ps[ 1 ] == LikeParser.POS_LAST ) {
                // 完全一致.
                return new int[]{ _stringBinarySearchMString( ( MStringIndexKey )ps[ 2 ] ) } ;
            }
        }
        
        // NGramLike検索.
        if( ngramFlag ) {
            return ngramLike( ps,allLen ) ;
        }
        // ノーマルLike検索.
        return normalLike( ps,allLen ) ;
    }
    
    /** NULL検索. **/
    /** 検索結果はNULLの行番号群が返却される. **/
    protected final int[] _searchNull( MimdbSearchElement target ) throws Exception {
        // 検索条件に応じた内容を取得.
        switch( target.getType() ) {
            case MimdbSearchType.TYPE_EQ :
            case MimdbSearchType.TYPE_NEQ :
            if( nullLineList == null ) {
                return null ;
            }
            return nullLineList ;
        }
        throw new MimdbException( "数字NULL検索に対して、不当な検索条件[" + target.getType() + "]です" ) ;
    }
    
    /** 検索. **/
    protected MimdbMiddleSearch _coreSearch( MimdbSearchElement info,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        if( info == null ) {
            throw new MimdbException( "検索条件が設定されていません" ) ;
        }
        if( !indexFlag ) {
            throw new MimdbException( "インデックス化されていません" ) ;
        }
        
        // 検索タイプ.
        int type = info.getType() ;
        
        // 検索条件がNULLの場合.
        if( info.getValue() == null ) {
            int[] nullPos = _searchNull( info ) ;
            // NULL一致.
            if( type == MimdbSearchType.TYPE_EQ ) {
                if( nullPos == null ) {
                    return null ;
                }
                // 検索条件の入れ物を作成.
                MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
                // 行情報の追加.
                _addLineNo( ret,true,nullPos ) ;
                return ret ;
            }
            // NULL不一致.
            else {
                return neqSearch( -1,and,or ) ;
            }
        }
        // 検索条件がInの場合.
        // 検索条件がLikeの場合.
        else if( type == MimdbSearchType.TYPE_IN ||
            type == MimdbSearchType.TYPE_LIKE ) {
            
            int[] inPos ;
            // 検索条件がInの場合.
            if( type == MimdbSearchType.TYPE_IN ) {
                inPos = _searchIn( info ) ;
            }
            // 検索条件がLikeの場合.
            else {
                inPos = _searchLike( info ) ;
            }
            if( _calcUseLineSize( inPos ) <= 0 ) {
                return null ;
            }
            // 検索条件の入れ物を作成.
            MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
            // 検索結果追加.
            int len = inPos.length ;
            for( int i = 0 ; i < len ; i ++ ) {
                _addSearchLineNo( ret,inPos[ i ] ) ;
            }
            return ret ;
        }
        // 検索条件がBitweenの場合.
        else if( type == MimdbSearchType.TYPE_BETWEEN ) {
            
            // 検索結果追加.
            return betweenSearch( _searchBitween( info ),and,or ) ;
        }
        // 通常検索の場合.
        int pos = _searchNormal( info ) ;
        // notEquals以外で検索結果がゼロ件の場合.
        if( type != MimdbSearchType.TYPE_NEQ && pos == -1 ) {
            return null ;
        }
        // 検索条件に応じた処理.
        switch( type ) {
            case MimdbSearchType.TYPE_EQ :
                return eqSearch( pos,and,or ) ;
            case MimdbSearchType.TYPE_NEQ :
                return neqSearch( pos,and,or ) ;
            case MimdbSearchType.TYPE_LBIG :
            case MimdbSearchType.TYPE_LEQBIG :
                return lBigSearch( pos,and,or ) ;
            case MimdbSearchType.TYPE_RBIG :
            case MimdbSearchType.TYPE_REQBIG :
                return rBigSearch( pos,and,or ) ;
        }
        
        return null ;
    }
    
    /** 完全一致検索. **/
    protected final MimdbMiddleSearch eqSearch( int pos,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        // 検索条件の入れ物を作成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // 一致条件をセット.
        _addSearchLineNo( ret,pos ) ;
        return ret ;
    }
    
    /** 完全不一致検索. **/
    protected final MimdbMiddleSearch neqSearch( int pos,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        
        // 検索条件の入れ物を作成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // posが-1の場合は全部を対象とする.
        if( pos == -1 ) {
            ret.all() ;
        }
        // 条件が一致する場合は、指定条件以外のものを全てONにセット.
        else {
            ret.all() ;
            // 指定のものをOFFセット.
            _addSearchLineOffNo( ret,pos ) ;
        }
        return ret ;
    }
    
    /** 指定データより大きい(以上)の検索 **/
    protected final MimdbMiddleSearch lBigSearch( int pos,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        // 検索条件の入れ物を作成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        int len = lineList.length ;
        for( int i = pos ; i < len ; i ++ ) {
            _addSearchLineNo( ret,i ) ;
        }
        return ret ;
    }
    
    /** 指定データより小さい(以下)の検索 **/
    protected final MimdbMiddleSearch rBigSearch( int pos,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        // 検索条件の入れ物を作成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        for( int i = pos ; i >= 0 ; i -- ) {
            _addSearchLineNo( ret,i ) ;
        }
        return ret ;
    }
    
    /** Bitweenの範囲を検索. **/
    protected final MimdbMiddleSearch betweenSearch( int[] list,MimdbMiddleSearch and,MimdbMiddleSearch or )
        throws Exception {
        int len ;
        
        // 検索条件の入れ物を作成.
        MimdbMiddleSearch ret = _createMiddleSearch( and,or ) ;
        
        // Bitween条件が全件設定の場合.
        if( ( len = list.length ) == 1 && list[ 0 ] == -1 ) {
            ret.all() ;
            return ret ;
        }
        
        // 指定範囲条件をセット.
        int lineLen = lineList.length ;
        int s,e,j ;
        s = -1 ;
        e = -1 ;
        
        for( int i = 0 ; i < len ; i ++ ) {
            // 開始位置.
            if( ( i & 1 ) == 0 ) {
                s = list[ i ] ;
                
                // 終了位置が定義されていない場合.
                if( len == i + 1 ) {
                    
                    // 開始位置が定義されていない場合は、開始位置は0.
                    if( s == -1 ) {
                        s = 0 ;
                    }
                    
                    // 終了位置が定義されていない場合は、終了位置は、インデックスの最後.
                    e = lineLen ;
                    
                    // 行条件を追加.
                    for( j = s ; j <= e ; j ++ ) {
                        _addSearchLineNo( ret,j ) ;
                    }
                    
                }
            }
            // 終了位置
            else {
                e = list[ i ] ;
                // 開始位置が定義されていない場合は、開始位置は0.
                if( s == -1 ) {
                    s = 0 ;
                }
                // 終了位置が定義されていない場合は、終了位置は、インデックスの最後.
                if( e == -1 ) {
                    e = lineLen ;
                }
                
                // 行条件を追加.
                for( j = s ; j <= e ; j ++ ) {
                    _addSearchLineNo( ret,j ) ;
                }
            }
        }
        
        return ret ;
    }
    
    /**
     * インデックス化されているかチェック.
     * @return boolean [true]の場合、インデックス化されています.
     */
    public boolean isIndex() {
        return indexFlag ;
    }
    
    /**
     * 検索処理.
     * @param info 検索条件を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch search( MimdbSearchElement info )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),null,null ) ;
    }
    
    /**
     * AND検索処理.
     * @param info 検索条件を設定します.
     * @param and アンド検索条件元を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch and( MimdbSearchElement info,MimdbMiddleSearch and )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),and,null ) ;
    }
    
    /**
     * OR検索処理.
     * @param info 検索条件を設定します.
     * @param or オア検索条件元を設定します.
     * @return MimdbMiddleSearch 検索結果が返却されます.
     * @exception Exception 例外.
     */
    public MimdbMiddleSearch or( MimdbSearchElement info,MimdbMiddleSearch or )
        throws Exception {
        return _coreSearch( _checkSearchInfo( info ),null,or ) ;
    }
    
    /**
     * インデックス数を取得.
     * @return int インデックス数が返却されます.
     */
    public int getIndexSize() {
        return indexList.length ;
    }
    
    /**
     * ソート順条件を取得.
     * @return int[] ソート順の条件が返却されます.
     */
    public int[] getSortNoList() {
        return sortNoList ;
    }
    
    /**
     * カラム名を取得.
     * @return String カラム名が返却されます.
     */
    public String getName() {
        return name ;
    }
    
    /**
     * カラムタイプを取得.
     * @return int カラムタイプが返却されます.
     */
    public int getType() {
        return COLUMN_STRING ;
    }
    
    /**
     * DBカラムタイプを取得.
     * @return int DBカラムタイプ(java.sql.Types)が返却されます.
     */
    public int getDBType() {
        return java.sql.Types.VARCHAR ;
    }
    
}
