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

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import static org.opengion.fukurou.system.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring
import org.opengion.fukurou.system.DateSet;							// 6.4.2.0 (2016/01/29)

import java.util.MissingResourceException;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collections;										// 6.3.9.0 (2015/11/06)

/**
 * AbstractObjectPool は、生成された Object をプールするキャッシュクラスです。
 * サブクラスで、各クラスごとにオブジェクトを生成／初期化／終了するように各メソッドを
 * コーディングしなおしてください。
 * サブクラスでは、Object createInstance() と、oid objectInitial( Object obj )、
 * void objectFinal( Object obj )  を オーバーライドしてください。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public abstract class AbstractObjectPool<E> {

	/** 内部でオブジェクトをプールしている配列。 */
	private List<E>							pool	;		// プールしているオブジェクト
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	private final ConcurrentMap<Integer,TimeStampObject>	poolBkMap = new ConcurrentHashMap<>();		// 作成したオブジェクトのタイムスタンプ管理
	private final Object lock = new Object();				// 6.3.9.0 (2015/11/06) ロック用のオブジェクト。poolとpoolBkMapを同時にロックする。

	/** プール自体を拡張可能かどうかを決める変数。拡張制限(true)／無制限(false) */
	private boolean limit    ;

	/** 最大オブジェクト数 */
	private int maxsize    ;

	/** 生成したオブジェクトの寿命(秒)を指定します。 0 は、制限なしです。*/
	private int     limitTime ;		// 3.5.4.3 (2004/01/05) キャッシュの寿命を指定します。

	/** 制限なしの場合でも、実質この値以上のキャッシュは、許可しません。*/
	private static final int MAX_LIMIT_COUNT = 1000 ;		// 3.6.0.8 (2004/11/19)

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	protected AbstractObjectPool() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * 初期化メソッド
	 *
	 * 初期オブジェクト数、最大オブジェクト数、拡張制限を指定します。
	 *
	 * 初期オブジェクト数は、プールを作成すると同時に確保するオブジェクトの個数です。
	 * オブジェクトの生成に時間がかかり、かつ、必ず複数使用するのであれば,
	 * 予め複数確保しておけば、パフォーマンスが向上します。
	 * 最大オブジェクト数は、拡張制限が、無制限(limit = false )の場合は、
	 * 無視されます。制限ありの場合は、この値を上限に、オブジェクトを増やします。
	 * 拡張制限は、生成するオブジェクト数に制限をかけるかどうかを指定します。
	 * 一般に、コネクション等のリソースを確保する場合は、拡張制限を加えて、
	 * 生成するオブジェクト数を制限します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @param   minsize 初期オブジェクト数
	 * @param   maxsize 最大オブジェクト数
	 * @param   limit   拡張制限(true)／無制限(false)
	 */
	protected void init( final int minsize, final int maxsize, final boolean limit ) {
		init( minsize, maxsize, limit,0 ) ;
	}

	/**
	 * 初期化メソッド
	 *
	 * 初期オブジェクト数、初期配列数、拡張制限、オブジェクトの寿命を指定します。
	 *
	 * 初期オブジェクト数、初期配列数、拡張制限、までは、{@link  #init( int , int , boolean ) init}
	 * を参照してください。
	 * オブジェクトの寿命は、生成された時間からの経過時間(秒)だけ、キャッシュしておく
	 * 場合に使用します。
	 * 例えば、コネクション等で、長期間のプーリングがリソースを圧迫する場合や、
	 * 接続側自身が、タイマーで切断する場合など、オブジェクトの生存期間を
	 * 指定して管理する必要があります。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @param   minsize 初期オブジェクト数
	 * @param   maxsize 初期配列数
	 * @param   limit   拡張制限(true)／無制限(false)
	 * @param   limitTime オブジェクトの寿命の時間制限値(秒)
	 * @see     #init( int , int , boolean )
	 */
	protected void init( final int minsize, final int maxsize,final boolean limit,final int limitTime ) {
		this.maxsize = maxsize;
		this.limit     = limit;
		this.limitTime = limitTime;
		synchronized( lock ) {
			pool	 = Collections.synchronizedList( new ArrayList<>( maxsize ) );		// 6.3.9.0 (2015/11/06)
			poolBkMap.clear();															// 6.4.3.1 (2016/02/12)
			for( int i=0; i<minsize; i++ ) {
				final E obj = createInstance();
				pool.add( obj );

				final Integer key = Integer.valueOf( obj.hashCode() );
				poolBkMap.put( key,new TimeStampObject( obj,limitTime ) );
			}
		}
	}

	/**
	 * キャッシュのインスタンスを返します。
	 *
	 * なお、拡張制限をしている場合に、最初に確保した数以上のオブジェクト生成の
	 * 要求があった場合は､ MissingResourceException が throw されます。
	 * また,オブジェクトが寿命を超えている場合は、削除した後、新たに次の
	 * オブジェクトの生成を行います。
	 *
	 * @og.rev 4.0.0.1 (2007/12/03) 生成リミットチェックを厳密に行う。
	 * @og.rev 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @return   キャッシュのインスタンス
	 * @throws MissingResourceException 拡張制限により、新しいインスタンスを生成できない場合
	 */
	public E newInstance() throws MissingResourceException {
		final E rtnobj ;
		synchronized( lock ) {
			if( pool.isEmpty() ) {
				if( limit && poolBkMap.size() >= maxsize ) {
					final String errMsg = "生成リミットいっぱいで新たに生成できません。["
								+ poolBkMap.size() + "]";

					// 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。
					final Iterator<TimeStampObject> itr = poolBkMap.values().iterator();
					while( itr.hasNext() ) {
						final TimeStampObject tso = itr.next();
						if( tso == null || tso.isTimeOver() ) {
							itr.remove();
						}
					}

					throw new MissingResourceException( errMsg,getClass().getName(),"limit" );
				}
				else if( poolBkMap.size() > MAX_LIMIT_COUNT ) {
					clear();		// 全件キャッシュを破棄します。
					final String errMsg = "ObjectPool で、メモリリークの可能性があります。size=["
								+ poolBkMap.size() + "]";
					throw new OgRuntimeException( errMsg );
				}
				// 新規作成
				rtnobj = createInstance();
				final Integer key = Integer.valueOf( rtnobj.hashCode() );
				poolBkMap.put( key,new TimeStampObject( rtnobj,limitTime ) );
			}
			else {
				// 既存取り出し
				rtnobj = pool.remove(0);
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
				if( rtnobj == null ) {
					// 通常ありえない。
					final String errMsg = "オブジェクトの取得に失敗しました。" ;
					throw new MissingResourceException( errMsg,getClass().getName(),"pool" );
				}

					final Integer key = Integer.valueOf( rtnobj.hashCode() );
					final TimeStampObject tso = poolBkMap.get( key );
					if( tso == null || tso.isTimeOver() ) {
						remove( rtnobj );
						return newInstance();
					}
			}
		}
		return rtnobj;
	}

	/**
	 * 具体的に新しいインスタンスを生成するメソッド。
	 *
	 * サブクラスで具体的に記述する必要があります。
	 *
	 * @return   新しいインスタンス
	 */
	protected abstract E createInstance();

	/**
	 * オブジェクトを、オブジェクトプールに戻します。
	 * 戻すべきオブジェクトが null の場合は,削除されたと判断します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @param   obj オブジェクトプールに戻すオブジェクト
	 */
	public void release( final E obj ) {
		final E obj2 = objectInitial( obj );
		if( obj2 != null ) {
			final Integer key = Integer.valueOf( obj2.hashCode() );
			synchronized( lock ) {
				final TimeStampObject tso = poolBkMap.get( key );
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
				if( tso == null ) {
					// 6.0.2.5 (2014/10/31) Ctrl-C で終了させると、なぜか poolBkMap から、オブジェクトが消える。原因不明????
					//			LogWriter.log( "ObjectPool で、メモリリークの可能性がある。obj=[" + obj + "]" );
					remove( obj2 );
				}
				else {
					pool.add( obj2 );
				}
			}
		}
	}

	/**
	 * オブジェクトを、オブジェクトプールから削除します。
	 * remove されるオブジェクトは、すでにキャッシュから取り出された後なので、
	 * そのまま、何もしなければ自然消滅(GC)されます。
	 * 自然消滅する前に、objectFinal( Object ) が呼ばれます。
	 * 生成されたオブジェクトの総数も、ひとつ減らします。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @param   obj 削除するオブジェクト
	 */
	public void remove( final E obj ) {
		if( obj != null ) {
			final Integer key = Integer.valueOf( obj.hashCode() );
			synchronized( lock ) {
				poolBkMap.remove( key );
			}
		}

		objectFinal( obj );
	}

	/**
	 * オブジェクトプールの要素数を返します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @return   プールの要素数
	 */
	public int size() {
		synchronized( lock ) {
			return poolBkMap.size();
		}
	}

	/**
	 * オブジェクトプールが要素を持たないかどうかを判定します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @return   オブジェクトプールが要素を持っていない、つまりそのサイズが 0 の場合にだけ true、そうでない場合は false
	 */
	public boolean isEmpty() {
		synchronized( lock ) {
			return poolBkMap.isEmpty() ;
		}
	}

	/**
	 * すべての要素を オブジェクトプールから削除します。
	 * 貸し出し中のオブジェクトは、クリアしません。よって、返り値は、
	 * すべてのオブジェクトをクリアできた場合は、true 、貸し出し中の
	 * オブジェクトが存在した場合(クリアできなかった場合)は、false です。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @return すべてクリア(true)/貸し出し中のオブジェクトが残っている(false)
	 */
	public boolean clear() {
		synchronized( lock ) {
			final Iterator<E> itr = pool.iterator();
			while( itr.hasNext() ) {
				remove( itr.next() );
			}
			pool.clear();

			// 貸し出し中の場合は、remove 出来ない為、poolBkMap に残っている。
			// それでも、poolBkMap をクリアすることで、release 返却時にも、
			// remove されるようになります。
			// ただし、作成オブジェクト数が、一旦 0 にリセットされる為、
			// 最大貸し出し可能数が、一時的に増えてしまいます。
			final boolean flag = poolBkMap.isEmpty();
			poolBkMap.clear();

			return flag;
		}
	}

	/**
	 * オブジェクトプールから削除するときに呼ばれます。
	 * このメソッドで各オブジェクトごとの終了処理を行います。
	 * 例えば､データベースコネクションであれば､close() 処理などです。
	 *
	 * デフォルトでは、なにも行いません。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @param  obj 終了処理を行うオブジェクト
	 */
	protected void objectFinal( final E obj ) {
		// ここでは処理を行いません。
	}

	/**
	 * オブジェクトプールに戻すとき(release するとき)に呼ばれます。
	 * このメソッドで各オブジェクトごとの初期処理を行います。
	 * オブジェクトプールに戻すときには､初期化して､次の貸し出しに
	 * 対応できるように、初期処理しておく必要があります。
	 *
	 * デフォルトでは、引数のオブジェクトをそのまま返します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @param  obj 初期処理を行うオブジェクト
	 *
	 * @return 初期処理を行ったオブジェクト
	 */
	protected E objectInitial( final E obj ) {
		return obj;
	}

	/**
	 * 内部状況を簡易的に表現した文字列を返します。
	 *
	 * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
	 *
	 * @return   このオブジェクトプールの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		synchronized( lock ) {
			// 6.0.2.5 (2014/10/31) char を append する。
			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
				.append( "  freeCount   = [" ).append( pool.size()		).append( ']'	).append( CR )
				.append( "  createCount = [" ).append( poolBkMap.size()	).append( ']'	)
				.append( " ( max=["			 ).append( maxsize			).append( "] )"	).append( CR )
				.append( "  limiter     = [" ).append( limit			).append( ']'	).append( CR )
				.append( "  limitTime   = [" ).append( limitTime		).append( "](s)" ).append( CR );

			final Iterator<E> itr = pool.iterator();
			buf.append( "Free Objects " ).append( CR );
			while( itr.hasNext() ) {
				final E obj = itr.next();
				if( obj != null ) {
					final Integer key = Integer.valueOf( obj.hashCode() );
					buf.append( ' ' ).append( poolBkMap.get( key ) )
						.append( ' ' ).append( obj ).append( CR );
				}
			}
			return buf.toString();
		}
	}
}

/**
 * TimeStampObject は、生成された Object を、生成時刻とともに管理するクラスです。
 * 内部のハッシュキーは、登録するオブジェクトと同一で、管理できるのは、異なるオブジェクト
 * のみです。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
class TimeStampObject implements Comparable<TimeStampObject> {	// 4.3.3.6 (2008/11/15) Generics警告対応
	private final long		timeStamp ;
	private final long		limitTime ;
	private final String	objStr ;			// 6.0.2.5 (2014/10/31) 表示用
	private final int		hcode ;

	/**
	 * コンストラクター。
	 *
	 * @param  obj 管理するオブジェクト
	 * @param  limit オブジェクトの寿命(秒)
	 * @throws IllegalArgumentException TimeStampObject のインスタンスに、NULL はセットできません。
	 */
	public TimeStampObject( final Object obj,final int limit ) {
		if( obj == null ) {
			final String errMsg = "TimeStampObject のインスタンスに、NULL はセットできません。" ;
			throw new IllegalArgumentException( errMsg );
		}

		timeStamp = System.currentTimeMillis();
		if( limit > 0 ) {
			limitTime = timeStamp + limit * 1000L ;
		}
		else {
			limitTime = Long.MAX_VALUE ;
		}

		hcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) ;

		objStr = String.valueOf( obj );		// 6.0.2.5 (2014/10/31) 表示用
	}

	/**
	 * 内部管理しているオブジェクトの生成時刻を返します。
	 *
	 * @return   生成時刻(ms)
	 */
	public long getTimeStamp() {
		return timeStamp;
	}

	/**
	 * オブジェクトの寿命がきたかどうかを返します。
	 *
	 * @return   寿命判定(true:寿命/false:まだ使える)
	 */
	public boolean isTimeOver() {
		return System.currentTimeMillis() > limitTime ;
	}

	/**
	 * オブジェクトが同じかどうかを判定します。
	 *
	 * 内部オブジェクトの equals() メソッドと、作成時刻の両方を判断します。
	 * 内部オブジェクトの equals() が同じでも、作成時刻が異なると、
	 * false を返します。これは、全く同一オブジェクトを管理する場合でも、
	 * タイムスタンプを差し替える事で、異なるオブジェクトとして
	 * 認識させるということです。
	 *
	 * @param    obj オブジェクト
	 *
	 * @return   true:同じ/false:異なる。
	 */
	@Override
	public boolean equals( final Object obj ) {
		if( obj instanceof TimeStampObject ) {
			final TimeStampObject other = (TimeStampObject)obj ;
			return hcode == other.hcode && timeStamp == other.timeStamp ;
		}
		return false ;
	}

	/**
	 * ハッシュコードを返します。
	 *
	 * ここで返すのは、自分自身のハッシュコードではなく、
	 * 内部管理のオブジェクトのハッシュコードです。
	 *
	 * hashcode = (int)((timeStamp)&amp;(Integer.MAX_VALUE))^(obj.hashCode())
	 *
	 * この計算式は、変更される可能性があります。
	 *
	 * @return  内部管理のオブジェクトのハッシュコード
	 */
	@Override
	public int hashCode() { return hcode; }

	/**
	 * このオブジェクトと指定されたオブジェクトの順序を比較します。
	 *
	 * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、
	 * 等しい場合はゼロ、大きい場合は正の整数を返します。
	 *
	 * @param  other TimeStampObject オブジェクト
	 *
	 * @return  順序比較の値
	 * @throws ClassCastException 指定されたオブジェクトがキャストできない場合。
	 * @see Comparable#compareTo(Object)
	 */
	@Override
	public int compareTo( final TimeStampObject other ) {	// 4.3.3.6 (2008/11/15) Generics警告対応
		final long diff = timeStamp - other.timeStamp;

		if( diff > 0 ) { return 1; }
		else if( diff < 0 ) { return -1; }
		else {
			if( equals( other ) ) { return 0; }
			else { return hcode - other.hcode; }
		}
	}

	/**
	 * このオブジェクトの内部表現を返します。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
	 *
	 * @return  オブジェクトの内部表現文字列
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		// Create Timeは、一度求めれば変わらないので、キャッシュしても良い。
		return "[CreateTime=" + DateSet.getDate( timeStamp,"yyyy/MM/dd HH:mm:ss" )
			 + " , TimeOver=" + (int)((limitTime - System.currentTimeMillis())/1000.0) + "(s)"
			 + " , object=" + objStr + "]" ;		// 6.0.2.5 (2014/10/31) 表示用
	}
}
