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