    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}