package org.maachang.util;

import java.util.Arrays;

/**
 * ObjectArrayオブジェクト. <BR>
 * <BR>
 * オブジェクト配列を管理するオブジェクトです.<BR>
 * また、このオブジェクトは同期されていません.
 * 
 * @version 2007/10/18
 * @author masahito suzuki
 * @since MaachangBase 1.00
 */
public class ObjectArray {
    /**
     * 配列縮小係数.
     */
    private static final double REDUCTION_ARRAY = 0.375;

    /**
     * 開始時配列管理数 : デフォルト.
     */
    private static final int DEFAULT_START_LENGTH = 8;

    /**
     * 開始時配列管理数 : 最大値.
     */
    private static final int MAX_START_LENGTH = Integer.MAX_VALUE;

    /**
     * 開始時配列管理数 : 最小値.
     */
    private static final int MIN_START_LENGTH = 2;

    /**
     * 配列管理.
     */
    private Object[] array = null;

    /**
     * 開始時管理配列数.
     */
    private int startLength = DEFAULT_START_LENGTH;

    /**
     * 配列管理数.
     */
    private int length = DEFAULT_START_LENGTH;

    /**
     * 現在格納配列数.
     */
    private int nowLength = 0;

    /**
     * コンストラクタ.
     */
    public ObjectArray() {
    }

    /**
     * コンストラクタ. <BR>
     * <BR>
     * 対象のバッファ係数を設定します. <BR>
     * 
     * @param length
     *            対象のバッファ係数を設定します.<BR>
     *            設定可能な最大値は[9999]です.<BR>
     *            設定可能な最小値は[2]です.<BR>
     *            また、これらの設定範囲外を設定した場合、 デフォルトの値[16]となります.
     */
    public ObjectArray(int length) {

        this.startLength = (length < MIN_START_LENGTH || length > MAX_START_LENGTH) ? DEFAULT_START_LENGTH
                : length;
        this.length = this.startLength;
    }

    /**
     * ファイナライズ処理定義. <BR>
     * <BR>
     * ファイナライズ処理定義.
     * 
     * @exception Exception
     *                例外処理が返されます.
     */
    protected final void finalize() throws Exception {

        try {
            this.clear();
        } catch (Exception t) {
        }

    }

    /**
     * 情報クリア. <BR>
     * <BR>
     * 対象の情報をクリアします.
     */
    public final void clear() {
        this.array = null;
        this.length = this.startLength;
        this.nowLength = 0;
    }

    /**
     * 情報追加. <BR>
     * <BR>
     * 対象の情報を追加します. <BR>
     * 
     * @param value
     *            設定対象のオブジェクト情報を追加します.
     */
    public final void add(Object value) {
        int length;
        int nowSize;

        Object[] info = null;
        Object[] tmp = null;

        info = this.array;
        length = this.length;
        nowSize = this.nowLength;

        if (info == null) {
            info = new Object[length];
            info[nowSize] = value;

            this.array = info;
        } else if (info.length <= nowSize) {

            int i;

            length *= 2;
            tmp = info;
            info = new Object[length];

            System.arraycopy(tmp, 0, info, 0, nowSize);

            info[nowSize] = value;

            for (i = 0; i < nowSize; i++) {
                tmp[i] = null;
            }

            this.length = length;
            this.array = info;

        } else {
            info[nowSize] = value;
        }

        this.nowLength++;
    }

    /**
     * 情報設定. <BR>
     * <BR>
     * 対象の位置に情報をセットします. <BR>
     * 
     * @param no
     *            設定対象項番を設定します.
     * @param value
     *            設定対象情報を設定します.
     */
    public final void set(int no, Object value) {
        int nowLen;

        nowLen = this.nowLength;

        if (no < 0 || no >= nowLen) {
            return;
        }

        this.array[no] = value;

    }

    /**
     * 情報削除. <BR>
     * <BR>
     * 対象の情報を削除します. <BR>
     * 
     * @param no
     *            削除対象の項番を設定します.
     * @return Object 削除されたオブジェクト情報が返されます.<BR>
     *         情報が存在しない場合[null]が返されます.
     */
    public final Object remove(int no) {
        int i;
        int nowSize;
        int length;
        int newLength;
        Object[] info = null;
        Object[] tmp = null;

        Object ret = null;

        nowSize = this.nowLength;
        length = this.length;

        if (no < 0 || no >= nowSize || nowSize == 0) {
            return null;
        }

        info = this.array;

        ret = info[no];
        info[no] = null;

        if (no == 0) {

            tmp = info;
            System.arraycopy(tmp, 1, info, 0, (nowSize - 1));

            info[nowSize - 1] = null;

        } else if ((nowSize - no) != 1) {

            tmp = info;
            System.arraycopy(tmp, 0, info, 0, no);
            System.arraycopy(tmp, no + 1, info, no, nowSize - (no + 1));

            info[nowSize - 1] = null;

        }

        nowSize--;

        if (nowSize != 0 && (length * REDUCTION_ARRAY) >= nowSize) {

            newLength = length / 2;
            tmp = new Object[newLength];
            System.arraycopy(info, 0, tmp, 0, newLength);

            for (i = 0; i < nowSize; i++) {
                info[i] = null;
            }

            info = null;
            info = tmp;
            this.length = newLength;

        } else if (nowSize == 0) {

            info = null;

        }

        this.array = info;
        this.nowLength = nowSize;

        info = null;
        tmp = null;

        return ret;
    }

    /**
     * 情報取得. <BR>
     * <BR>
     * 対象の情報を取得します. <BR>
     * 
     * @param no
     *            取得対象の項番を設定します.
     * @return Object 取得された情報が返されます.<BR>
     *         情報が存在しない場合[null]が返されます.
     */
    public final Object get(int no) {

        if (no < 0 || no >= this.nowLength) {
            return null;
        }

        return this.array[no];
    }

    /**
     * データを昇順でソート. <BR>
     * <BR>
     * データを昇順でソートします.
     */
    public final void sort() {
        if (this.nowLength != 0) {
            Arrays.sort(this.array, 0, this.nowLength);
        }
    }

    /**
     * 対象の条件が一致する先頭の値を取得. <BR>
     * <BR>
     * 対象の条件と一致する先頭の値を取得します. また、この処理の実行前に１度ソートする必要があります. <BR>
     * 
     * @param key
     *            対象の条件を設定します.
     * @return int 結果情報が返されます.<BR>
     *         [-1]が返された場合、条件の内容は存在しません.
     */
    public final int indexOf(Object key) {
        return this.indexOf(key, 0);
    }

    /**
     * 対象の条件が一致する先頭の値を取得. <BR>
     * <BR>
     * 対象の条件と一致する先頭の値を取得します. また、この処理の実行前に１度ソートする必要があります. <BR>
     * 
     * @param key
     *            対象の条件を設定します.
     * @return int 結果情報が返されます.<BR>
     *         [-1]が返された場合、条件の内容は存在しません.
     */
    public final int indexOf(Object key, int index) {
        int len;
        int ret;
        Object[] tmp = null;

        if (this.nowLength != 0) {
            this.sort();
            len = this.nowLength - index;
            tmp = new Object[len];
            System.arraycopy(this.array, index, tmp, 0, len);
            ret = Arrays.binarySearch(tmp, key);
            tmp = null;
            ret = (ret < 0 || ret >= this.nowLength) ? -1 : ret;
        } else {
            ret = -1;
        }

        return ret;
    }

    /**
     * 対象のオブジェクト配列を取得. <BR>
     * <BR>
     * 対象のオブジェクト配列を取得します.<BR>
     * また、このオブジェクト配列は配列の再構成を行った場合、 情報の保証は行われません.<BR>
     * また、このオブジェクト群は基本的の読み込み専用で利用する ことを進めます. <BR>
     * 
     * @return Object[] 対象のオブジェクト配列が返されます.
     */
    public final Object[] getObjects() {
        return this.array;
    }

    /**
     * 対象のオブジェクト配列を取得. <BR>
     * <BR>
     * 対象のオブジェクト配列を取得します.<BR>
     * また、このオブジェクト配列は配列の再構成を行った場合、 情報の保証は行われません.<BR>
     * また、このオブジェクト群は基本的の読み込み専用で利用する ことを進めます. <BR>
     * 
     * @return Object[] 対象のオブジェクト配列が返されます.
     */
    public final Object[] getObjectArray() {
        return getObjectArray( this.nowLength ) ;
    }

    /**
     * 対象のオブジェクト配列を取得. <BR>
     * <BR>
     * 対象のオブジェクト配列を取得します.<BR>
     * また、このオブジェクト配列は配列の再構成を行った場合、 情報の保証は行われません.<BR>
     * また、このオブジェクト群は基本的の読み込み専用で利用する ことを進めます. <BR>
     * 
     * @param size 取得配列数を設定します.
     * @return Object[] 対象のオブジェクト配列が返されます.
     */
    public final Object[] getObjectArray( int size ) {
        if( size <= 0 ) {
            throw new IllegalArgumentException( "指定サイズは不正です" ) ;
        }
        size = ( size <= this.nowLength ) ? this.nowLength : size ;
        Object[] ret = new Object[ size ] ;
        System.arraycopy( this.array,0,ret,0,size ) ;
        return ret ;
    }

    /**
     * 格納情報数の取得. <BR>
     * <BR>
     * 格納されている情報数を取得します. <BR>
     * 
     * @return int 格納されている情報数が返されます.
     */
    public final int size() {
        return this.nowLength;
    }
    
    /**
     * 文字列変換.
     * @return String 文字列変換された内容が返されます.
     */
    public final String toString() {
        StringBuilder buf = new StringBuilder() ;
        buf.append( "[ " ) ;
        int len = this.nowLength ;
        for( int i = 0 ; i < len ; i ++ ) {
            if( i != 0 ) {
                buf.append( ", " ) ;
            }
            Object o = this.array[ i ] ;
            if( o == null ) {
                buf.append( "null@null" ) ;
            }
            else {
                buf.append( o ).append( "@" ).append( o.getClass().getName() ) ;
            }
        }
        return buf.append( " ]" ).toString() ;
    }

}
