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)
030import java.util.Objects;                                                                                       // 8.4.2.2 (2023/03/17) ハッシュコード求め
031
032/**
033 * AbstractObjectPool は、生成された Object をプールするキャッシュクラスです。
034 * サブクラスで、各クラスごとにオブジェクトを生成/初期化/終了するように各メソッドを
035 * コーディングしなおしてください。
036 * サブクラスでは、Object createInstance() と、oid objectInitial( Object obj )、
037 * void objectFinal( Object obj )  を オーバーライドしてください。
038 *
039 * @version  4.0
040 * @author   Kazuhiko Hasegawa
041 * @since    JDK5.0,
042 */
043public abstract class AbstractObjectPool<E> {
044
045        /** 内部でオブジェクトをプールしている配列。 */
046        private List<E>                                                 pool    ;               // プールしているオブジェクト
047        /** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
048        private final ConcurrentMap<Integer,TimeStampObject>    poolBkMap = new ConcurrentHashMap<>();          // 作成したオブジェクトのタイムスタンプ管理
049        private final Object lock = new Object();                               // 6.3.9.0 (2015/11/06) ロック用のオブジェクト。poolとpoolBkMapを同時にロックする。
050
051        /** プール自体を拡張可能かどうかを決める変数。拡張制限(true)/無制限(false) */
052        private boolean limit    ;
053
054        /** 最大オブジェクト数 */
055        private int maxsize    ;
056
057        /** 生成したオブジェクトの寿命(秒)を指定します。 0 は、制限なしです。*/
058        private int     limitTime ;             // 3.5.4.3 (2004/01/05) キャッシュの寿命を指定します。
059
060        /** 制限なしの場合でも、実質この値以上のキャッシュは、許可しません。*/
061        private static final int MAX_LIMIT_COUNT = 1000 ;               // 3.6.0.8 (2004/11/19)
062
063        /**
064         * デフォルトコンストラクター
065         *
066         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
067         */
068        protected AbstractObjectPool() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
069
070        /**
071         * 初期化メソッド
072         *
073         * 初期オブジェクト数、最大オブジェクト数、拡張制限を指定します。
074         *
075         * 初期オブジェクト数は、プールを作成すると同時に確保するオブジェクトの個数です。
076         * オブジェクトの生成に時間がかかり、かつ、必ず複数使用するのであれば,
077         * 予め複数確保しておけば、パフォーマンスが向上します。
078         * 最大オブジェクト数は、拡張制限が、無制限(limit = false )の場合は、
079         * 無視されます。制限ありの場合は、この値を上限に、オブジェクトを増やします。
080         * 拡張制限は、生成するオブジェクト数に制限をかけるかどうかを指定します。
081         * 一般に、コネクション等のリソースを確保する場合は、拡張制限を加えて、
082         * 生成するオブジェクト数を制限します。
083         *
084         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
085         *
086         * @param   minsize 初期オブジェクト数
087         * @param   maxsize 最大オブジェクト数
088         * @param   limit   拡張制限(true)/無制限(false)
089         */
090        protected void init( final int minsize, final int maxsize, final boolean limit ) {
091                init( minsize, maxsize, limit,0 ) ;
092        }
093
094        /**
095         * 初期化メソッド
096         *
097         * 初期オブジェクト数、初期配列数、拡張制限、オブジェクトの寿命を指定します。
098         *
099         * 初期オブジェクト数、初期配列数、拡張制限、までは、{@link  #init( int , int , boolean ) init}
100         * を参照してください。
101         * オブジェクトの寿命は、生成された時間からの経過時間(秒)だけ、キャッシュしておく
102         * 場合に使用します。
103         * 例えば、コネクション等で、長期間のプーリングがリソースを圧迫する場合や、
104         * 接続側自身が、タイマーで切断する場合など、オブジェクトの生存期間を
105         * 指定して管理する必要があります。
106         *
107         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
108         * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
109         *
110         * @param   minsize 初期オブジェクト数
111         * @param   maxsize 初期配列数
112         * @param   limit   拡張制限(true)/無制限(false)
113         * @param   limitTime オブジェクトの寿命の時間制限値(秒)
114         * @see     #init( int , int , boolean )
115         */
116        protected void init( final int minsize, final int maxsize,final boolean limit,final int limitTime ) {
117                this.maxsize = maxsize;
118                this.limit     = limit;
119                this.limitTime = limitTime;
120                synchronized( lock ) {
121                        pool     = Collections.synchronizedList( new ArrayList<>( maxsize ) );          // 6.3.9.0 (2015/11/06)
122                        poolBkMap.clear();                                                                                                                      // 6.4.3.1 (2016/02/12)
123                        for( int i=0; i<minsize; i++ ) {
124                                final E obj = createInstance();
125                                pool.add( obj );
126
127                                final Integer key = Integer.valueOf( obj.hashCode() );
128                                poolBkMap.put( key,new TimeStampObject( obj,limitTime ) );
129                        }
130                }
131        }
132
133        /**
134         * キャッシュのインスタンスを返します。
135         *
136         * なお、拡張制限をしている場合に、最初に確保した数以上のオブジェクト生成の
137         * 要求があった場合は、 MissingResourceException が throw されます。
138         * また,オブジェクトが寿命を超えている場合は、削除した後、新たに次の
139         * オブジェクトの生成を行います。
140         *
141         * @og.rev 4.0.0.1 (2007/12/03) 生成リミットチェックを厳密に行う。
142         * @og.rev 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。
143         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
144         *
145         * @return   キャッシュのインスタンス
146         * @throws MissingResourceException 拡張制限により、新しいインスタンスを生成できない場合
147         */
148        public E newInstance() throws MissingResourceException {
149                final E rtnobj ;
150                synchronized( lock ) {
151                        if( pool.isEmpty() ) {
152                                if( limit && poolBkMap.size() >= maxsize ) {
153                                        final String errMsg = "生成リミットいっぱいで新たに生成できません。["
154                                                                + poolBkMap.size() + "]";
155
156                                        // 4.0.0.1 (2007/12/03) 生成リミットエラー時に、タイムアウトをチェックする。
157                                        final Iterator<TimeStampObject> itr = poolBkMap.values().iterator();
158                                        while( itr.hasNext() ) {
159                                                final TimeStampObject tso = itr.next();
160                                                if( tso == null || tso.isTimeOver() ) {
161                                                        itr.remove();
162                                                }
163                                        }
164
165                                        throw new MissingResourceException( errMsg,getClass().getName(),"limit" );
166                                }
167                                else if( poolBkMap.size() > MAX_LIMIT_COUNT ) {
168                                        clear();                // 全件キャッシュを破棄します。
169                                        final String errMsg = "ObjectPool で、メモリリークの可能性があります。size=["
170                                                                + poolBkMap.size() + "]";
171                                        throw new OgRuntimeException( errMsg );
172                                }
173                                // 新規作成
174                                rtnobj = createInstance();
175                                final Integer key = Integer.valueOf( rtnobj.hashCode() );
176                                poolBkMap.put( key,new TimeStampObject( rtnobj,limitTime ) );
177                        }
178                        else {
179                                // 既存取り出し
180                                rtnobj = pool.remove(0);
181                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
182                                if( rtnobj == null ) {
183                                        // 通常ありえない。
184                                        final String errMsg = "オブジェクトの取得に失敗しました。" ;
185                                        throw new MissingResourceException( errMsg,getClass().getName(),"pool" );
186                                }
187
188                                        final Integer key = Integer.valueOf( rtnobj.hashCode() );
189                                        final TimeStampObject tso = poolBkMap.get( key );
190                                        if( tso == null || tso.isTimeOver() ) {
191                                                remove( rtnobj );
192                                                return newInstance();
193                                        }
194                        }
195                }
196                return rtnobj;
197        }
198
199        /**
200         * 具体的に新しいインスタンスを生成するメソッド。
201         *
202         * サブクラスで具体的に記述する必要があります。
203         *
204         * @return   新しいインスタンス
205         */
206        protected abstract E createInstance();
207
208        /**
209         * オブジェクトを、オブジェクトプールに戻します。
210         * 戻すべきオブジェクトが null の場合は,削除されたと判断します。
211         *
212         * @og.rev 6.3.9.0 (2015/11/06) Use block level rather than method level synchronization.(PMD)
213         *
214         * @param   obj オブジェクトプールに戻すオブジェクト
215         */
216        public void release( final E obj ) {
217                final E obj2 = objectInitial( obj );
218                if( obj2 != null ) {
219                        final Integer key = Integer.valueOf( obj2.hashCode() );
220                        synchronized( lock ) {
221                                final TimeStampObject tso = poolBkMap.get( key );
222                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
223                                if( tso == null ) {
224                                        // 6.0.2.5 (2014/10/31) Ctrl-C で終了させると、なぜか poolBkMap から、オブジェクトが消える。原因不明????
225                                        //                      LogWriter.log( "ObjectPool で、メモリリークの可能性がある。obj=[" + obj + "]" );
226                                        remove( obj2 );
227                                }
228                                else {
229                                        pool.add( obj2 );
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         * @og.rev 8.4.2.2 (2023/03/17) ハッシュコード求めに、java.util.Objects#hash を使用します。
399         *
400         * @param  obj 管理するオブジェクト
401         * @param  limit オブジェクトの寿命(秒)
402         * @throws IllegalArgumentException TimeStampObject のインスタンスに、NULL はセットできません。
403         */
404        public TimeStampObject( final Object obj,final int limit ) {
405                if( obj == null ) {
406                        final String errMsg = "TimeStampObject のインスタンスに、NULL はセットできません。" ;
407                        throw new IllegalArgumentException( errMsg );
408                }
409                objStr = String.valueOf( obj );         // 6.0.2.5 (2014/10/31) 表示用
410
411                timeStamp = System.currentTimeMillis();
412                if( limit > 0 ) {
413                        limitTime = timeStamp + limit * 1000L ;
414                }
415                else {
416                        limitTime = Long.MAX_VALUE ;
417                }
418
419                // 8.4.2.2 (2023/03/17) ハッシュコード求めに、java.util.Objects#hash を使用します。
420//              hcode = (int)((timeStamp)&(Integer.MAX_VALUE))^(obj.hashCode()) ;
421                hcode = Objects.hash( objStr, Long.valueOf( timeStamp ) );
422        }
423
424        /**
425         * 内部管理しているオブジェクトの生成時刻を返します。
426         *
427         * @return   生成時刻(ms)
428         */
429        public long getTimeStamp() {
430                return timeStamp;
431        }
432
433        /**
434         * オブジェクトの寿命がきたかどうかを返します。
435         *
436         * @return   寿命判定(true:寿命/false:まだ使える)
437         */
438        public boolean isTimeOver() {
439                return System.currentTimeMillis() > limitTime ;
440        }
441
442        /**
443         * オブジェクトが同じかどうかを判定します。
444         *
445         * 内部オブジェクトの equals() メソッドと、作成時刻の両方を判断します。
446         * 内部オブジェクトの equals() が同じでも、作成時刻が異なると、
447         * false を返します。これは、全く同一オブジェクトを管理する場合でも、
448         * タイムスタンプを差し替える事で、異なるオブジェクトとして
449         * 認識させるということです。
450         *
451         * @og.rev 8.4.2.2 (2023/03/17) ハッシュコード求めに対応した、equals に変更します。
452         *
453         * @param    obj オブジェクト
454         *
455         * @return   true:同じ/false:異なる。
456         */
457        @Override
458        public boolean equals( final Object obj ) {
459                if( obj instanceof TimeStampObject ) {
460                        final TimeStampObject other = (TimeStampObject)obj ;
461//                      return hcode == other.hcode && timeStamp == other.timeStamp ;
462                        return objStr.equals( other.objStr ) && timeStamp == other.timeStamp ;
463                }
464                return false ;
465        }
466
467        /**
468         * ハッシュコードを返します。
469         *
470         * ここで返すのは、自分自身のハッシュコードではなく、
471         * 内部管理のオブジェクトのハッシュコードです。
472         *
473         * <del> hashcode = (int)((timeStamp)&amp;(Integer.MAX_VALUE))^(obj.hashCode()) </del>
474         * hcode = Objects.hash( objStr, Long.valueOf( timeStamp ) );
475         *
476         * この計算式は、変更される可能性があります。
477         *
478         * @return  内部管理のオブジェクトのハッシュコード
479         */
480        @Override
481        public int hashCode() { return hcode; }
482
483        /**
484         * このオブジェクトと指定されたオブジェクトの順序を比較します。
485         *
486         * このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、
487         * 等しい場合はゼロ、大きい場合は正の整数を返します。
488         *
489         * @param  other TimeStampObject オブジェクト
490         *
491         * @return  順序比較の値
492         * @throws ClassCastException 指定されたオブジェクトがキャストできない場合。
493         * @see Comparable#compareTo(Object)
494         */
495        @Override       // Comparable
496        public int compareTo( final TimeStampObject other ) {   // 4.3.3.6 (2008/11/15) Generics警告対応
497                final long diff = timeStamp - other.timeStamp;
498
499                if( diff > 0 ) { return 1; }
500                else if( diff < 0 ) { return -1; }
501                else {
502                        if( equals( other ) ) { return 0; }
503                        else { return hcode - other.hcode; }
504                }
505        }
506
507        /**
508         * このオブジェクトの内部表現を返します。
509         *
510         * @og.rev 5.5.7.2 (2012/10/09) HybsDateUtil を利用するように修正します。
511         *
512         * @return  オブジェクトの内部表現文字列
513         * @og.rtnNotNull
514         */
515        @Override
516        public String toString() {
517                // Create Timeは、一度求めれば変わらないので、キャッシュしても良い。
518                return "[CreateTime=" + DateSet.getDate( timeStamp,"yyyy/MM/dd HH:mm:ss" )
519                         + " , TimeOver=" + (int)((limitTime - System.currentTimeMillis())/1000.0) + "(s)"
520                         + " , object=" + objStr + "]" ;                // 6.0.2.5 (2014/10/31) 表示用
521        }
522}