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