001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.fukurou.util;
017
018import java.util.MissingResourceException;
019import java.util.Map;
020import java.util.HashMap;
021import java.util.List;
022import java.util.ArrayList;
023import java.util.Iterator;
024
025/**
026 * AbstractObjectPool は、生成された Object をプールするキャッシュクラスです。
027 * サブクラスで、各クラスごとにオブジェクトを生成/初期化/終了するように各メソッドを
028 * コーディングしなおしてください。
029 * サブクラスでは、Object createInstance() と、oid objectInitial( Object obj )、
030 * void objectFinal( Object obj )  を オーバーライドしてください。
031 *
032 * @version  4.0
033 * @author   Kazuhiko Hasegawa
034 * @since    JDK5.0,
035 */
036abstract public class AbstractObjectPool<E> {
037        /** 内部でオブジェクトをプールしている配列。                */
038        private List<E>                                                 pool = null;                    // プールしているオブジェクト
039        private Map<Integer,TimeStampObject>    poolBack = null;                // 作成したオブジェクトのタイムスタンプ管理
040
041        /** プール自体を拡張可能かどうかを決める変数。拡張制限(true)/無制限(false) */
042        private boolean limit    ;
043
044        /** 最大オブジェクト数 */
045        private int maxsize    ;
046
047        /** 生成したオブジェクトの寿命(秒)を指定します。 0 は、制限なしです。*/
048        private int     limitTime ;             // 3.5.4.3 (2004/01/05) キャッシュの寿命を指定します。
049
050        /** 制限なしの場合でも、実質この値以上のキャッシュは、許可しません。*/
051        private static final int MAX_LIMIT_COUNT = 1000 ;               // 3.6.0.8 (2004/11/19)
052
053        /**
054         * 初期化メソッド
055         *
056         * 初期オブジェクト数、最大オブジェクト数、拡張制限を指定します。
057         *
058         * 初期オブジェクト数は、プールを作成すると同時に確保するオブジェクトの個数です。
059         * オブジェクトの生成に時間がかかり、かつ、必ず複数使用するのであれば,
060         * 予め複数確保しておけば、パフォーマンスが向上します。
061         * 最大オブジェクト数は、拡張制限が、無制限(limit = false )の場合は、
062         * 無視されます。制限ありの場合は、この値を上限に、オブジェクトを増やします。
063         * 拡張制限は、生成するオブジェクト数に制限をかけるかどうかを指定します。
064         * 一般に、コネクション等のリソースを確保する場合は、拡張制限を加えて、
065         * 生成するオブジェクト数を制限します。
066         *
067         * @param   minsize 初期オブジェクト数
068         * @param   maxsize 最大オブジェクト数
069         * @param   limit   拡張制限(true)/無制限(false)
070         */
071        protected synchronized void init( final int minsize, final int maxsize, final boolean limit ) {
072                init( minsize, maxsize, limit,0 ) ;
073        }
074
075        /**
076         * 初期化メソッド
077         *
078         * 初期オブジェクト数、初期配列数、拡張制限、オブジェクトの寿命を指定します。
079         *
080         * 初期オブジェクト数、初期配列数、拡張制限、までは、{@link  #init( int , int , boolean ) init}
081         * を参照してください。
082         * オブジェクトの寿命は、生成された時間からの経過時間(秒)だけ、キャッシュしておく
083         * 場合に使用します。
084         * 例えば、コネクション等で、長期間のプーリングがリソースを圧迫する場合や、
085         * 接続側自身が、タイマーで切断する場合など、オブジェクトの生存期間を
086         * 指定して管理する必要があります。
087         *
088         * @param   minsize 初期オブジェクト数
089         * @param   maxsize 初期配列数
090         * @param   limit   拡張制限(true)/無制限(false)
091         * @param   limitTime オブジェクトの寿命の時間制限値(秒)
092         * @see     #init( int , int , boolean )
093         */
094        protected synchronized void init( final int minsize, final int maxsize,
095                                                                        final boolean limit,final int limitTime ) {
096                pool = new ArrayList<E>( maxsize );
097                poolBack = new HashMap<Integer,TimeStampObject>();
098                this.maxsize = maxsize;
099                this.limit = limit;
100                this.limitTime = limitTime;
101                for( int i=0; i<minsize; i++ ) {
102                        E obj = createInstance();
103                        pool.add( obj );
104
105                        Integer key = Integer.valueOf( obj.hashCode() );
106                        poolBack.put( key,new TimeStampObject( obj,limitTime ) );
107                }
108        }
109
110        /**
111         * キャッシュのインスタンスを返します。
112         *
113         * なお、拡張制限をしている場合に、最初に確保した数以上のオブジェクト生成の
114         * 要求があった場合は、 MissingResourceException が throw されます。
115         * また,オブジェクトが寿命を超えている場合は、削除した後、新たに次の
116         * オブジェクトの生成を行います。
117         *
118         * @og.rev 4.0.0.1 (2007/12/03) 生成リミットチェックを厳密に行う。
119         * @og.rev 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。
120         *
121         * @return   キャッシュのインスタンス
122         * @throws MissingResourceException 拡張制限により、新しいインスタンスを生成できない場合
123         */
124        public synchronized E newInstance() throws MissingResourceException {
125                final E rtnobj ;
126                if( pool.isEmpty() ) {
127                        if( limit && poolBack.size() >= maxsize ) {
128                                String errMsg = "生成リミットいっぱいで新たに生成できません。["
129                                                        + poolBack.size() + "]";
130
131                                // 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。
132                                Iterator<TimeStampObject> itr = poolBack.values().iterator();
133                                while( itr.hasNext() ) {
134                                        TimeStampObject tso = itr.next();
135                                        if( tso == null || tso.isTimeOver() ) {
136                                                itr.remove();
137                                        }
138                                }
139
140                                throw new MissingResourceException( errMsg,getClass().getName(),"limit" );
141                        }
142                        else if( poolBack.size() > MAX_LIMIT_COUNT ) {
143                                clear();                // 全件キャッシュを破棄します。
144                                String errMsg = "ObjectPool で、メモリリークの可能性があります。["
145                                                        + poolBack.size() + "]";
146                                throw new RuntimeException( errMsg );
147                        }
148                        // 新規作成
149                        rtnobj = createInstance();
150                        Integer key = Integer.valueOf( rtnobj.hashCode() );
151                        poolBack.put( key,new TimeStampObject( rtnobj,limitTime ) );
152                }
153                else {
154                        // 既存取り出し
155                        rtnobj = pool.remove(0);
156                        if( rtnobj != null ) {
157                                Integer key = Integer.valueOf( rtnobj.hashCode() );
158                                TimeStampObject tso = poolBack.get( key );
159                                if( tso == null || tso.isTimeOver() ) {
160                                        remove( rtnobj );
161                                        return newInstance();
162                                }
163                        }
164                        else {
165                                // 通常ありえない。
166                                String errMsg = "オブジェクトの取得に失敗しました。" ;
167                                throw new MissingResourceException( errMsg,getClass().getName(),"pool" );
168                        }
169                }
170
171                return rtnobj;
172        }
173
174        /**
175         * 具体的に新しいインスタンスを生成するメソッド。
176         *
177         * サブクラスで具体的に記述する必要があります。
178         *
179         * @return   新しいインスタンス
180         */
181        abstract protected E createInstance();
182
183        /**
184         * オブジェクトを、オブジェクトプールに戻します。
185         * 戻すべきオブジェクトが null の場合は,削除されたと判断します。
186         *
187         * @param   obj オブジェクトプールに戻すオブジェクト
188         */
189        public synchronized void release( final E obj ) {
190                E obj2 = objectInitial( obj );
191                if( obj2 != null ) {
192                        Integer key = Integer.valueOf( obj2.hashCode() );
193                        TimeStampObject tso = poolBack.get( key );
194                        if( tso != null ) {
195                                pool.add( obj2 );
196                        }
197                        else {                                  // 3.5.6.2 (2004/07/05) 追加
198                                LogWriter.log( "ObjectPool で、メモリリークの可能性がある。[" + obj2 + "]" );
199                                remove( obj2 );
200                        }
201                }
202        }
203
204        /**
205         * オブジェクトを、オブジェクトプールから削除します。
206         * remove されるオブジェクトは、すでにキャッシュから取り出された後なので、
207         * そのまま、何もしなければ自然消滅(GC)されます。
208         * 自然消滅する前に、objectFinal( Object ) が呼ばれます。
209         * 生成されたオブジェクトの総数も、ひとつ減らします。
210         *
211         * @param   obj 削除するオブジェクト
212         */
213        public synchronized void remove( final E obj ) {
214                if( obj != null ) {
215                        Integer key = Integer.valueOf( obj.hashCode() );
216                        poolBack.remove( key );
217                }
218
219                objectFinal( obj );
220        }
221
222        /**
223         * オブジェクトプールの要素数を返します。
224         *
225         * @return   プールの要素数
226         */
227        public synchronized int size() {
228                return poolBack.size();
229        }
230
231        /**
232         * オブジェクトプールが要素を持たないかどうかを判定します。
233         *
234         * @return   オブジェクトプールが要素を持っていない、つまりそのサイズが 0 の場合にだけ true、そうでない場合は false
235         */
236        public synchronized boolean isEmpty() {
237                return poolBack.isEmpty() ;
238        }
239
240        /**
241         * すべての要素を オブジェクトプールから削除します。
242         * 貸し出し中のオブジェクトは、クリアしません。よって、返り値は、
243         * すべてのオブジェクトをクリアできた場合は、true 、貸し出し中の
244         * オブジェクトが存在した場合(クリアできなかった場合)は、false です。
245         *
246         * @return すべてクリア(true)/貸し出し中のオブジェクトが残っている(false)
247         */
248        public synchronized boolean clear() {
249                Iterator<E> itr = pool.iterator();
250                while( itr.hasNext() ) {
251                        remove( itr.next() );
252                }
253                pool.clear();
254
255                // 貸し出し中の場合は、remove 出来ない為、poolBack に残っている。
256                // それでも、poolBack をクリアすることで、release 返却時にも、
257                // remove されるようになります。
258                // ただし、作成オブジェクト数が、一旦 0 にリセットされる為、
259                // 最大貸し出し可能数が、一時的に増えてしまいます。
260                boolean flag = poolBack.isEmpty();
261                poolBack.clear();
262
263                return flag;
264        }
265
266        /**
267         * オブジェクトプールから削除するときに呼ばれます。
268         * このメソッドで各オブジェクトごとの終了処理を行います。
269         * 例えば、データベースコネクションであれば、close() 処理などです。
270         *
271         * デフォルトでは、なにも行いません。
272         *
273         * @param  obj 終了処理を行うオブジェクト
274         */
275        protected synchronized void objectFinal( final E obj ) {
276                // ここでは処理を行いません。
277        }
278
279        /**
280         * オブジェクトプールに戻すとき(release するとき)に呼ばれます。
281         * このメソッドで各オブジェクトごとの初期処理を行います。
282         * オブジェクトプールに戻すときには、初期化して、次の貸し出しに
283         * 対応できるように、初期処理しておく必要があります。
284         *
285         * デフォルトでは、引数のオブジェクトをそのまま返します。
286         *
287         * @param  obj 初期処理を行うオブジェクト
288         *
289         * @return 初期処理を行ったオブジェクト
290         */
291        protected synchronized E objectInitial( final E obj ) {
292                return obj;
293        }
294
295        /**
296         * 内部状況を簡易的に表現した文字列を返します。
297         *
298         * @return   このオブジェクトプールの文字列表現
299         */
300        @Override
301        public synchronized String toString() {
302                StringBuilder buf = new StringBuilder();
303                buf.append( "  freeCount    = [" ).append( pool.size()          ).append( "]\n" );
304                buf.append( "  createCount  = [" ).append( poolBack.size()      ).append( "]" );
305                buf.append( " ( max=[" ).append( maxsize ).append( "] )\n"      );
306                buf.append( "  limiter      = [" ).append( limit                        ).append( "]\n" );
307                buf.append( "  limitTime    = [" ).append( limitTime            ).append( "](s)\n" );
308
309                Iterator<E> itr = pool.iterator();
310                buf.append( "Free Objects \n" );
311                while( itr.hasNext() ) {
312                        E obj = itr.next();
313                        if( obj != null ) {
314                                Integer key = Integer.valueOf( obj.hashCode() );
315                                buf.append( "  " );
316                                buf.append( poolBack.get( key ) );
317                                buf.append( "  " ).append( obj );
318                                buf.append( "\n" );
319                        }
320                }
321                return buf.toString();
322        }
323}
324
325/**
326 * TimeStampObject は、生成された Object を、生成時刻とともに管理するクラスです。
327 * 内部のハッシュキーは、登録するオブジェクトと同一で、管理できるのは、異なるオブジェクト
328 * のみです。
329 *
330 * @version  4.0
331 * @author   Kazuhiko Hasegawa
332 * @since    JDK5.0,
333 */
334class TimeStampObject implements Comparable<TimeStampObject> {  // 4.3.3.6 (2008/11/15) Generics警告対応
335        private final long              timeStamp ;
336        private final long              limitTime ;
337        private final int               hcode ;
338
339        /**
340         * コンストラクター。
341         *
342         * @param  obj 管理するオブジェクト
343         * @param  limit オブジェクトの寿命(秒)
344         * @throws IllegalArgumentException TimeStampObject のインスタンスに、NULL はセットできません。
345         */
346        public TimeStampObject( final Object obj,final int limit ) {
347                if( obj == null ) {
348                        String errMsg = "TimeStampObject のインスタンスに、NULL はセットできません。" ;
349                        throw new IllegalArgumentException( errMsg );
350                }
351
352                timeStamp = System.currentTimeMillis();
353                if( limit > 0 ) {
354                        limitTime = timeStamp + limit * 1000L ;
355                }
356                else {
357                        limitTime = Long.MAX_VALUE ;
358                }
359
360                hcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) ;
361        }
362
363        /**
364         * 内部管理しているオブジェクトの生成時刻を返します。
365         *
366         * @return   生成時刻(ms)
367         */
368        public long getTimeStamp() {
369                return timeStamp;
370        }
371
372        /**
373         * オブジェクトの寿命がきたかどうかを返します。
374         *
375         * @return   寿命判定(true:寿命/false:まだ使える)
376         */
377        public boolean isTimeOver() {
378                return System.currentTimeMillis() > limitTime ;
379        }
380
381        /**
382         * オブジェクトが同じかどうかを判定します。
383         *
384         * 内部オブジェクトの equals() メソッドと、作成時刻の両方を判断します。
385         * 内部オブジェクトの equals() が同じでも、作成時刻が異なると、
386         * false を返します。これは、全く同一オブジェクトを管理する場合でも、
387         * タイムスタンプを差し替える事で、異なるオブジェクトとして
388         * 認識させるということです。
389         *
390         * @param    obj Object
391         *
392         * @return   true:同じ/false:異なる。
393         */
394        @Override
395        public boolean equals( final Object obj ) {
396                if( obj instanceof TimeStampObject ) {
397                        TimeStampObject other = (TimeStampObject)obj ;
398                        return hcode == other.hcode && timeStamp == other.timeStamp ;
399                }
400                return false ;
401        }
402
403        /**
404         * ハッシュコードを返します。
405         *
406         * ここで返すのは、自分自身のハッシュコードではなく、
407         * 内部管理のオブジェクトのハッシュコードです。
408         *
409         * hashcode = (int)((timeStamp)&amp;(Integer.MAX_VALUE))^(obj.hashCode())
410         *
411         * この計算式は、変更される可能性があります。
412         *
413         * @return  内部管理のオブジェクトのハッシュコード
414         */
415        @Override
416        public int hashCode() { return hcode; }
417
418        /**
419         * このオブジェクトと指定されたオブジェクトの順序を比較します。
420         *
421         * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、
422         * 等しい場合はゼロ、大きい場合は正の整数を返します。
423         *
424         * @param  other TimeStampObject オブジェクト
425         *
426         * @return  順序比較の値
427         * @throws ClassCastException 指定されたオブジェクトがキャストできない場合。
428         * @see Comparable#compareTo(Object)
429         */
430        @Override
431        public int compareTo( final TimeStampObject other ) {   // 4.3.3.6 (2008/11/15) Generics警告対応
432                long diff = timeStamp - other.timeStamp;
433
434                if( diff > 0 ) { return 1; }
435                else if( diff < 0 ) { return -1; }
436                else {
437                        if( equals( other ) ) { return 0; }
438                        else { return hcode - other.hcode; }
439                }
440        }
441
442        /**
443         * このオブジェクトの内部表現を返します。
444         *
445         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
446         *
447         * @return  オブジェクトの内部表現文字列
448         */
449        @Override
450        public String toString() {
451                // Create Timeは、一度求めれば変わらないので、キャッシュしても良い。
452                return "[Create Time = " + HybsDateUtil.getDate( timeStamp,"yyyy/MM/dd HH:mm:ss" )
453                         + " , Time Over = " + (int)((limitTime - System.currentTimeMillis())/1000.0) + "(S)]" ;
454        }
455}