    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         * オブジェクトを、オブジェクトプールから削除します。
    237         * remove されるオブジェクトは、すでにキャッシュから取り出された後なので、
    238         * そのまま、何もしなければ自然消滅(GC)されます。
    239         * 自然消滅する前に、objectFinal( Object ) が呼ばれます。
    240         * 生成されたオブジェクトの総数も、ひとつ減らします。
    241         *
    242         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    243         *
    244         * @param   obj 削除するオブジェクト
    245         */
    246        public void remove( final E obj ) {
    247                if( obj != null ) {
    248                        final Integer key = Integer.valueOf( obj.hashCode() );
    249                        synchronized( lock ) {
    250                                poolBkMap.remove( key );
    251                        }
    252                }
    253
    254                objectFinal( obj );
    255        }
    256
    257        /**
    258         * オブジェクトプールの要素数を返します。
    259         *
    260         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    261         *
    262         * @return   プールの要素数
    263         */
    264        public int size() {
    265                synchronized( lock ) {
    266                        return poolBkMap.size();
    267                }
    268        }
    269
    270        /**
    271         * オブジェクトプールが要素を持たないかどうかを判定します。
    272         *
    273         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    274         *
    275         * @return   オブジェクトプールが要素を持っていない、つまりそのサイズが 0 の場合にだけ true、そうでない場合は false
    276         */
    277        public boolean isEmpty() {
    278                synchronized( lock ) {
    279                        return poolBkMap.isEmpty() ;
    280                }
    281        }
    282
    283        /**
    284         * すべての要素を オブジェクトプールから削除します。
    285         * 貸し出し中のオブジェクトは、クリアしません。よって、返り値は、
    286         * すべてのオブジェクトをクリアできた場合は、true 、貸し出し中の
    287         * オブジェクトが存在した場合(クリアできなかった場合)は、false です。
    288         *
    289         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    290         *
    291         * @return すべてクリア(true)/貸し出し中のオブジェクトが残っている(false)
    292         */
    293        public boolean clear() {
    294                synchronized( lock ) {
    295                        final Iterator<E> itr = pool.iterator();
    296                        while( itr.hasNext() ) {
    297                                remove( itr.next() );
    298                        }
    299                        pool.clear();
    300
    301                        // 貸し出し中の場合は、remove 出来ない為、poolBkMap に残っている。
    302                        // それでも、poolBkMap をクリアすることで、release 返却時にも、
    303                        // remove されるようになります。
    304                        // ただし、作成オブジェクト数が、一旦 0 にリセットされる為、
    305                        // 最大貸し出し可能数が、一時的に増えてしまいます。
    306                        final boolean flag = poolBkMap.isEmpty();
    307                        poolBkMap.clear();
    308
    309                        return flag;
    310                }
    311        }
    312
    313        /**
    314         * オブジェクトプールから削除するときに呼ばれます。
    315         * このメソッドで各オブジェクトごとの終了処理を行います。
    316         * 例えば､データベースコネクションであれば､close() 処理などです。
    317         *
    318         * デフォルトでは、なにも行いません。
    319         *
    320         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    321         *
    322         * @param  obj 終了処理を行うオブジェクト
    323         */
    324        protected void objectFinal( final E obj ) {
    325                // ここでは処理を行いません。
    326        }
    327
    328        /**
    329         * オブジェクトプールに戻すとき(release するとき)に呼ばれます。
    330         * このメソッドで各オブジェクトごとの初期処理を行います。
    331         * オブジェクトプールに戻すときには､初期化して､次の貸し出しに
    332         * 対応できるように、初期処理しておく必要があります。
    333         *
    334         * デフォルトでは、引数のオブジェクトをそのまま返します。
    335         *
    336         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    337         *
    338         * @param  obj 初期処理を行うオブジェクト
    339         *
    340         * @return 初期処理を行ったオブジェクト
    341         */
    342        protected E objectInitial( final E obj ) {
    343                return obj;
    344        }
    345
    346        /**
    347         * 内部状況を簡易的に表現した文字列を返します。
    348         *
    349         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
    350         *
    351         * @return   このオブジェクトプールの文字列表現
    352         * @og.rtnNotNull
    353         */
    354        @Override
    355        public String toString() {
    356                synchronized( lock ) {
    357                        // 6.0.2.5 (2014/10/31) char を append する。
    358                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
    359                                .append( "  freeCount   = [" ).append( pool.size()              ).append( ']'   ).append( CR )
    360                                .append( "  createCount = [" ).append( poolBkMap.size() ).append( ']'   )
    361                                .append( " ( max=["                      ).append( maxsize                      ).append( "] )" ).append( CR )
    362                                .append( "  limiter     = [" ).append( limit                    ).append( ']'   ).append( CR )
    363                                .append( "  limitTime   = [" ).append( limitTime                ).append( "](s)" ).append( CR );
    364
    365                        final Iterator<E> itr = pool.iterator();
    366                        buf.append( "Free Objects " ).append( CR );
    367                        while( itr.hasNext() ) {
    368                                final E obj = itr.next();
    369                                if( obj != null ) {
    370                                        final Integer key = Integer.valueOf( obj.hashCode() );
    371                                        buf.append( ' ' ).append( poolBkMap.get( key ) )
    372                                                .append( ' ' ).append( obj ).append( CR );
    373                                }
    374                        }
    375                        return buf.toString();
    376                }
    377        }
    378}
    379
    380/**
    381 * TimeStampObject は、生成された Object を、生成時刻とともに管理するクラスです。
    382 * 内部のハッシュキーは、登録するオブジェクトと同一で、管理できるのは、異なるオブジェクト
    383 * のみです。
    384 *
    385 * @version  4.0
    386 * @author   Kazuhiko Hasegawa
    387 * @since    JDK5.0,
    388 */
    389class TimeStampObject implements Comparable<TimeStampObject> {  // 4.3.3.6 (2008/11/15) Generics警告対応
    390        private final long              timeStamp ;
    391        private final long              limitTime ;
    392        private final String    objStr ;                        // 6.0.2.5 (2014/10/31) 表示用
    393        private final int               hcode ;
    394
    395        /**
    396         * コンストラクター。
    397         *
    398         * @param  obj 管理するオブジェクト
    399         * @param  limit オブジェクトの寿命(秒)
    400         * @throws IllegalArgumentException TimeStampObject のインスタンスに、NULL はセットできません。
    401         */
    402        public TimeStampObject( final Object obj,final int limit ) {
    403                if( obj == null ) {
    404                        final String errMsg = "TimeStampObject のインスタンスに、NULL はセットできません。" ;
    405                        throw new IllegalArgumentException( errMsg );
    406                }
    407
    408                timeStamp = System.currentTimeMillis();
    409                if( limit > 0 ) {
    410                        limitTime = timeStamp + limit * 1000L ;
    411                }
    412                else {
    413                        limitTime = Long.MAX_VALUE ;
    414                }
    415
    416                hcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) ;
    417
    418                objStr = String.valueOf( obj );         // 6.0.2.5 (2014/10/31) 表示用
    419        }
    420
    421        /**
    422         * 内部管理しているオブジェクトの生成時刻を返します。
    423         *
    424         * @return   生成時刻(ms)
    425         */
    426        public long getTimeStamp() {
    427                return timeStamp;
    428        }
    429
    430        /**
    431         * オブジェクトの寿命がきたかどうかを返します。
    432         *
    433         * @return   寿命判定(true:寿命/false:まだ使える)
    434         */
    435        public boolean isTimeOver() {
    436                return System.currentTimeMillis() > limitTime ;
    437        }
    438
    439        /**
    440         * オブジェクトが同じかどうかを判定します。
    441         *
    442         * 内部オブジェクトの equals() メソッドと、作成時刻の両方を判断します。
    443         * 内部オブジェクトの equals() が同じでも、作成時刻が異なると、
    444         * false を返します。これは、全く同一オブジェクトを管理する場合でも、
    445         * タイムスタンプを差し替える事で、異なるオブジェクトとして
    446         * 認識させるということです。
    447         *
    448         * @param    obj オブジェクト
    449         *
    450         * @return   true:同じ/false:異なる。
    451         */
    452        @Override
    453        public boolean equals( final Object obj ) {
    454                if( obj instanceof TimeStampObject ) {
    455                        final TimeStampObject other = (TimeStampObject)obj ;
    456                        return hcode == other.hcode && timeStamp == other.timeStamp ;
    457                }
    458                return false ;
    459        }
    460
    461        /**
    462         * ハッシュコードを返します。
    463         *
    464         * ここで返すのは、自分自身のハッシュコードではなく、
    465         * 内部管理のオブジェクトのハッシュコードです。
    466         *
    467         * hashcode = (int)((timeStamp)&amp;(Integer.MAX_VALUE))^(obj.hashCode())
    468         *
    469         * この計算式は、変更される可能性があります。
    470         *
    471         * @return  内部管理のオブジェクトのハッシュコード
    472         */
    473        @Override
    474        public int hashCode() { return hcode; }
    475
    476        /**
    477         * このオブジェクトと指定されたオブジェクトの順序を比較します。
    478         *
    479         * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、
    480         * 等しい場合はゼロ、大きい場合は正の整数を返します。
    481         *
    482         * @param  other TimeStampObject オブジェクト
    483         *
    484         * @return  順序比較の値
    485         * @throws ClassCastException 指定されたオブジェクトがキャストできない場合。
    486         * @see Comparable#compareTo(Object)
    487         */
    488        @Override
    489        public int compareTo( final TimeStampObject other ) {   // 4.3.3.6 (2008/11/15) Generics警告対応
    490                final long diff = timeStamp - other.timeStamp;
    491
    492                if( diff > 0 ) { return 1; }
    493                else if( diff < 0 ) { return -1; }
    494                else {
    495                        if( equals( other ) ) { return 0; }
    496                        else { return hcode - other.hcode; }
    497                }
    498        }
    499
    500        /**
    501         * このオブジェクトの内部表現を返します。
    502         *
    503         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
    504         *
    505         * @return  オブジェクトの内部表現文字列
    506         * @og.rtnNotNull
    507         */
    508        @Override
    509        public String toString() {
    510                // Create Timeは、一度求めれば変わらないので、キャッシュしても良い。
    511                return "[CreateTime=" + DateSet.getDate( timeStamp,"yyyy/MM/dd HH:mm:ss" )
    512                         + " , TimeOver=" + (int)((limitTime - System.currentTimeMillis())/1000.0) + "(s)"
    513                         + " , object=" + objStr + "]" ;                // 6.0.2.5 (2014/10/31) 表示用
    514        }
    515}