    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.system;
    017
    018import java.util.List;                                                                                          // 6.9.2.1 (2018/03/12)
    019import java.util.ArrayList;                                                                                     // 6.9.2.1 (2018/03/12)
    020import java.util.Set;                                                                                           // 6.9.2.1 (2018/03/12)
    021import java.util.HashSet;                                                                                       // 6.9.2.1 (2018/03/12)
    022
    023import java.util.concurrent.ConcurrentMap;                                                      // 6.4.3.3 (2016/03/04)
    024import java.util.concurrent.ConcurrentHashMap;
    025
    026import static org.opengion.fukurou.system.HybsConst.CR;                         // 6.1.0.0 (2014/12/26) refactoring
    027import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;      // 6.4.2.0 (2016/01/29)
    028
    029/**
    030 * ThrowUtil.java は、共通的に使用される Throwable,Exception関連メソッドを集約した、クラスです。
    031 *
    032 * StringUtil にあったメソッドを、こちらに移動させました。
    033 *
    034 * @og.group ユーティリティ
    035 *
    036 * @og.rev 6.4.2.0 (2016/01/29) 新規作成
    037 * @og.rev 6.4.3.2 (2016/02/19) 全面書き換え
    038 *
    039 * @version  6.0
    040 * @author       Kazuhiko Hasegawa
    041 * @since    JDK8.0,
    042 */
    043public final class ThrowUtil {
    044        public static final int MIN_STACK_SIZE = 3;                                     // 先頭から、通常にスタックトレースする行数。
    045//      public static final int NOMAL_STACK_SIZE = 5;                           // 先頭から、通常にスタックトレースする行数。
    046//      public static final int UNLIMITED_SIZE = -1;                            // スタックトレースする行数。-1 は、制限なし
    047
    048        // 6.9.2.1 (2018/03/12) 複数個所で呼ばれた場合の処理が、うまく出来ないので、暫定対策。(同時起動で、混ざる)
    049        private static final CaseBuilder C_BUF = new CaseBuilder();
    050
    051        // 6.9.3.0 (2018/03/26) static で持ちます。
    052        private static final StackTraceElement NULL_STE = new StackTraceElement( "","","",-1 );
    053
    054        /**
    055         *      デフォルトコンストラクターをprivateにして、
    056         *      オブジェクトの生成をさせないようにする。
    057         *
    058         */
    059        private ThrowUtil() {}
    060
    061        /**
    062         * Throwable の printStackTrace() 結果の内、opengion に関する箇所だけを文字列に変換して返します。
    063         *
    064         * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動。
    065         * @og.rev 6.4.2.0 (2016/01/29) すべてのスタックトレースを行っていたが、絞り込みます。
    066         *
    067         * @param    th   printStackTraceすべき元のThrowableオブジェクト
    068         *
    069         * @return   Throwableの詳細メッセージ( th.printStackTrace() )
    070         */
    071        public static String ogStackTrace( final Throwable th ) {
    072                return ogStackTrace( null , th );
    073        }
    074
    075        /**
    076         * Throwable の printStackTrace() 結果の内、opengion に関する箇所だけを文字列に変換して返します。
    077         *
    078         * printStackTrace() すると、膨大なメッセージが表示されるため、その中の、"org.opengion" を
    079         * 含む箇所だけを、抜粋します。
    080         * また、同じ内容のエラーも除外します。
    081         * 先頭から、MIN_STACK_SIZE 行は、元のままのメッセージを出力します。
    082         * Throwable#getCause() で、再帰的に原因をさかのぼります。
    083         *
    084         * @og.rev 5.7.2.0 (2014/01/10) 新規作成
    085         * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動し、msg 引数を追加。
    086         * @og.rev 6.4.3.2 (2016/02/19) (通常try-with-resources文によって)抑制された例外も出力します。
    087         * @og.rev 6.5.0.1 (2016/10/21) メッセージに、BuildNumber.ENGINE_INFO を含める。
    088         * @og.rev 6.9.0.1 (2018/02/05) causeの対応が中途半端だったので、修正します。
    089         * @og.rev 6.9.2.1 (2018/03/12) 最小件数のbreak判定の方法を見直します。
    090         *
    091         * @param    msg 合成したいメッセージ
    092         * @param    th 元のThrowableオブジェクト
    093         *
    094         * @return   Throwableの詳細メッセージ( StackTraceElement の抜粋 )
    095         */
    096        public static String ogStackTrace( final String msg,final Throwable th ) {
    097//              final CaseBuilder buf = new CaseBuilder()
    098                C_BUF.init()
    099                        .append( "Version: " , BuildNumber.ENGINE_INFO )                                // 6.5.0.1 (2016/10/21) エンジンのバージョン
    100                        .append( "Message: " , msg );                                                                   // 追加メッセージ
    101
    102                if( th != null ) {
    103        //              final CaseBuilder buf = new CaseBuilder()
    104        //                      .append( "Version: " , BuildNumber.ENGINE_INFO )                                // 6.5.0.1 (2016/10/21) エンジンのバージョン
    105        //                      .append( "Message: " , msg )                                                                    // 追加メッセージ
    106                                // Throwable.toString() で、クラス名とメッセージを分けて、それぞれを重複チェックする。
    107                        C_BUF.append( "Error  : " , th.getClass().getCanonicalName() )          // クラス名
    108                           .append( "         " , th.getLocalizedMessage() )                            // メッセージ
    109//                         .addStackTrace( th , MIN_STACK_SIZE , UNLIMITED_SIZE );
    110                           .addStackTrace( th , MIN_STACK_SIZE );                                                       // 6.9.2.1 (2018/03/12)
    111
    112                        // 原因の Throwable
    113                        Throwable tmpTh = th.getCause();
    114                        while( tmpTh != null ) {
    115                                 C_BUF.append( "Cause  : " , tmpTh.getClass().getCanonicalName() )      // クラス名
    116                                        .append( "         " , tmpTh.getLocalizedMessage() )                    // メッセージ
    117//                                      .addStackTrace( tmpTh , 0 , UNLIMITED_SIZE );                                   // 最小行は指定しない。
    118                                        .addStackTrace( tmpTh , MIN_STACK_SIZE );                                               // 6.9.2.1 (2018/03/12)
    119
    120                                tmpTh = tmpTh.getCause();
    121                        }
    122
    123                        // 6.4.3.2 (2016/02/19) (通常try-with-resources文によって)抑制された例外も出力します。
    124                        // このThrowable は、原因の Throwable は、求めません。
    125                        for( final Throwable supTh : th.getSuppressed() ) {
    126                                C_BUF.append( "Suppressed : " , supTh.getClass().getCanonicalName() )           // クラス名
    127                                   .append( "             " , supTh.getLocalizedMessage() )                             // メッセージ
    128//                                 .addStackTrace( supTh , 0 , UNLIMITED_SIZE );
    129                                   .addStackTrace( supTh , MIN_STACK_SIZE );                                                    // 6.9.2.1 (2018/03/12)
    130                        }
    131                }
    132
    133//              return buf.toString();
    134                return C_BUF.toString().trim();
    135        }
    136
    137        /**
    138         * 発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列を返します。
    139         *
    140         * 通常、System.out.println() で済ましていたエラーメッセージに対して、
    141         * エラー発生場所を特定する為の情報を付与したメッセージを作成します。
    142         * 発生場所の行番号は、このメソッド（実施は、オーバーロード先）で new Throwable して、取得します。
    143         * 呼ぶ場所で、new Throwable() する代わりの簡易目祖度になります。
    144         *
    145         * @og.rev 6.4.2.0 (2016/01/29) 新規作成。
    146         *
    147         * @param    msg 合成したいメッセージ
    148         *
    149         * @return   発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列
    150         */
    151        public static String ogThrowMsg( final String msg ) {
    152                return ogThrowMsg( msg , null );
    153        }
    154
    155        /**
    156         * 発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列を返します。
    157         *
    158         * 通常、System.out.println() で済ましていたエラーメッセージに対して、
    159         * エラー発生場所を特定する為の情報を付与したメッセージを作成します。
    160         * 行番号は、引数のThrowableの最初の情報のみ使用する為、通常は、このメソッドを
    161         * 呼ぶ場所で、new Throwable() します。
    162         * 発生クラスは、org.opengion か、org.apache.jsp.jsp を含む３行のみの簡易メッセージとします。
    163         *
    164         * @og.rev 6.3.6.1 (2015/08/28) 新規作成
    165         * @og.rev 6.3.6.1 (2015/08/28) メッセージに、th.getLocalizedMessage() を含める。
    166         * @og.rev 6.3.9.0 (2015/11/06) thのnullチェックを先に行う。
    167         * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動するとともに、メソッド名を、ogErrMsg → ogThrowMsg に変更。
    168         * @og.rev 6.5.0.1 (2016/10/21) メッセージに、BuildNumber.ENGINE_INFO を含める。
    169         * @og.rev 6.9.0.1 (2018/02/05) causeの対応が中途半端だったので、修正します。
    170         * @og.rev 6.9.2.1 (2018/03/12) 最小件数のbreak判定の方法を見直します。
    171         *
    172         * @param    msg 合成したいメッセージ
    173         * @param    th  元のThrowableオブジェクト
    174         *
    175         * @return   発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列
    176         */
    177        public static String ogThrowMsg( final String msg,final Throwable th ) {
    178//              final CaseBuilder buf = new CaseBuilder()
    179                C_BUF.init()
    180                        .append( "Version: " , BuildNumber.ENGINE_INFO )                                        // 6.5.0.1 (2016/10/21) エンジンのバージョン
    181                        .append( "Message: " , msg );                                                                           // 追加メッセージ
    182
    183                if( th != null ) {
    184                        C_BUF.append( "Error  : " , th.getClass().getCanonicalName() )          // クラス名
    185                           .append( "         " , th.getLocalizedMessage() );                           // メッセージ
    186//                         .addStackTrace( th , MIN_STACK_SIZE , MIN_STACK_SIZE );
    187                //         .addStackTrace( th , MIN_STACK_SIZE );                                                       // 6.9.2.1 (2018/03/12)
    188
    189//                      final Throwable cause = th.getCause();                                                          // 原因の Throwable (１回だけ)
    190//                      if( cause != null ) {
    191//                              buf.append( "Cause  : " , cause.getClass().getCanonicalName() )         // クラス名
    192//                                 .append( "         " , cause.getLocalizedMessage() );                        // メッセージ
    193////                               .addStackTrace( cause , MIN_STACK_SIZE , MIN_STACK_SIZE );
    194//              //                 .addStackTrace( cause , MIN_STACK_SIZE );                                            // 6.9.2.1 (2018/03/12)
    195//                      }
    196
    197                        // 原因の Throwable
    198                        Throwable tmpTh = th.getCause();
    199                        while( tmpTh != null ) {
    200                                 C_BUF.append( "Cause  : " , tmpTh.getClass().getCanonicalName() )      // クラス名
    201                                        .append( "         " , tmpTh.getLocalizedMessage() );                   // メッセージ
    202
    203                                tmpTh = tmpTh.getCause();
    204                        }
    205
    206                        // 6.4.3.2 (2016/02/19) (通常try-with-resources文によって)抑制された例外も出力します。
    207                        // このThrowable は、原因の Throwable は、求めません。
    208                        for( final Throwable supTh : th.getSuppressed() ) {
    209                                C_BUF.append( "Suppressed : " , supTh.getClass().getCanonicalName() )           // クラス名
    210                                   .append( "             " , supTh.getLocalizedMessage() );                    // メッセージ
    211                        }
    212                }
    213
    214//              return buf.toString();
    215        //      return buf.toString().trim();
    216                return C_BUF.toThrowMsg().trim();
    217
    218//              final Throwable cause =    th == null ? new Throwable() : th.getCause();        // 原因の Throwable
    219//              final Throwable tmpTh = cause == null ? new Throwable() : cause;                        // 行番号取得のため、例外を発生させる。
    220//
    221//              final CaseBuilder buf = new CaseBuilder()
    222//                      .append( "Version: " , BuildNumber.ENGINE_INFO )                                        // 6.5.0.1 (2016/10/21) エンジンのバージョン
    223//                      // Throwable.toString() で、クラス名とメッセージを分けて、それぞれを重複チェックする。
    224//                      .append( "Error  : " , tmpTh.getClass().getCanonicalName() )            // クラス名
    225//                      .append( "         " , tmpTh.getLocalizedMessage() )                            // メッセージ
    226//                      .append( "Message: " , msg )                                                                            // 追加メッセージ
    227//                      .addStackTrace( tmpTh , MIN_STACK_SIZE , MIN_STACK_SIZE );
    228//
    229//              return buf.toString();
    230        }
    231
    232//      /**
    233//       * このクラスが連続で呼ばれた場合のメッセージを返します。
    234//       *
    235//       * この、staticクラスは、色々な箇所で呼ばれる可能性があり、しかも、
    236//       * 一連のエラーで、別々に呼ばれると、メッセージが分断されます。
    237//       *
    238//       * そこで、暫定的に、連続にメッセージのみ、内部変数に設定していきます。
    239//       * ただし、同時に異なるエラーで呼ばれた場合には、それらのメッセージが
    240//       * 混在する（まざる）ので、暫定的な処置です。
    241//       *
    242//       * この呼び出しで、内部変数をクリアします。
    243//       *
    244//       * @og.rev 6.9.2.1 (2018/03/12) 連続で呼ばれた場合のメッセージを返す。
    245//       *
    246//       * @return   連続で呼ばれた場合のメッセージ
    247//       */
    248//      public static String getLastMsg() {
    249//              final String rtn = buf.toString();
    250//              buf.clear();
    251//
    252//              return rtn;
    253//      }
    254
    255        /**
    256         * Throwable の getStackTrace() 結果の内、opengion に関する箇所だけのStackTraceElement配列を選別して返します。
    257         *
    258         * 通常、スタックトレース情報は、膨大なメッセージが含まれるため、その中の、org.opengion か、org.apache.jsp.jsp を
    259         * 含む箇所だけを、抜粋します。
    260         * ただし、ThrowUtilクラス(このクラス)内で、スタックトレースを new しているため、このクラスで発生した
    261         * スタックトレースは、含まないようにしています。このクラスからエラーが発生しないことを望みます。
    262         * 処理は、先頭から、minCnt件数までは、そのまま残し、それ以降は、関連するトレース情報のみ残します。
    263         *
    264         * @og.rev 6.9.2.1 (2018/03/12) 選別だけを別に用意します。
    265         *
    266         * @param    th         元のThrowableオブジェクト(!= null 保障済み)
    267         * @param    minCnt     StackTraceElementを登録する最小件数
    268         * @return   選別されたStackTraceElement配列
    269         * @see         java.lang.Throwable#getStackTrace()
    270         */
    271        public static StackTraceElement[] selectElement( final Throwable th , final int minCnt ) {
    272//              final StackTraceElement NULL_ELEMENT = new StackTraceElement( "","","",-1 );
    273                final Set<String> cacheSet = new HashSet<>();                   // 同一スタックの除外用
    274
    275                final List<StackTraceElement> list = new ArrayList<>();
    276
    277                int idx = 0;
    278                for( final StackTraceElement stEle : th.getStackTrace() ) {
    279                        final String stkMsg = stEle.toString();
    280                        if( cacheSet.contains( stkMsg ) ) { continue; }         // 同じメッセージを出さない対応
    281                        cacheSet.add( stkMsg );
    282
    283                        final String cls = stEle.getClassName();
    284
    285                        boolean flag = true;                            // 連続で、出力しない。
    286                        if( minCnt < 0 || idx < minCnt ||
    287        //                              !cls.contains( "ThrowUtil" ) &&                         // ここで、new Throwable しないので、判定不要。
    288                                                cls.contains( "org.opengion" ) || cls.contains( "org.apache.jsp.jsp" ) ) {
    289                                list.add( stEle );
    290                                flag = true;                                    // 省略解除
    291                        }
    292                        else {
    293                                if( flag ) {
    294//                                      list.add( NULL_ELEMENT );       // StackTraceElementの省略
    295                                        list.add( NULL_STE );           // StackTraceElementの省略
    296                                        flag = false;                           // 連続で、出力しない。
    297                                }
    298                        }
    299                        idx++ ;
    300                }
    301                return list.toArray( new StackTraceElement[list.size()] );
    302        }
    303
    304        /**
    305         * StringBuilder を、例外処理のスタックに特化した形で作り直した内部クラスです。
    306         *
    307         * printStackTrace() すると、膨大なメッセージが表示されるため、その中の、"org.opengion" と
    308         * "org.apache.jsp.jsp" を含む箇所だけを、抜粋します。
    309         * また、同じ内容のエラーも除外します。
    310         *
    311         * ※ 怪しい実装
    312         *    StackTrace は、内部的に、色々な箇所で呼ばれたり、同じ要因で、何度も再作成されたりします。
    313         *    この内部クラスも、ひとつの例外で、何度も作成されるため、重複チェックの方法を、時間制限で、
    314         *    同一メッセージの重複処理を行っています。
    315         *    よって、まったく異なる要因のエラーが同時に発生した場合、重複処理の判定で、除外される可能性が
    316         *    あります。（発生場所の行番号が異なれば、重複処理されても残るので、デバッグ可能です）
    317         *
    318         * @og.rev 6.4.3.2 (2016/02/19) 新規追加
    319         * @og.rev 6.9.2.1 (2018/03/12) 重複処理を、staticではなく、自身のオブジェクト内だけに変更します。
    320         * @og.rev 6.9.9.1 (2018/08/27) StringBuilder を、List に変更したが、empty時の処理漏れ対応。
    321         */
    322        private static final class CaseBuilder {
    323                private static final String USE_KEY    = "USE_KEY" ;
    324                private static final long   CACHE_TIME = 3000L;                 // Exceptionが発生した場合、連続してCallされるため、一まとめにする。
    325
    326//              private static final ConcurrentMap<String,String> MSG_MAP = new ConcurrentHashMap<>();  // 同期Setの代わり。重複の取り除き判定
    327                private final ConcurrentMap<String,String> MSG_MAP = new ConcurrentHashMap<>(); // 同期Setの代わり。重複の取り除き判定
    328//              private static volatile long lastCall = 0L;
    329                private volatile long lastCall ;                                                // 初期値 0L
    330
    331//              private final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    332                private final List<String> list = new ArrayList<>();
    333
    334                /**
    335                 * デフォルトコンストラクタ。
    336                 *
    337                 * 時間制限が過ぎれば、キャッシュをクリアします。
    338                 *
    339                 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
    340                 * @og.rev 6.4.3.3 (2016/03/04) synchronized を取り除きます。
    341                 * @og.rev 6.9.2.1 (2018/03/12) 重複処理を、staticではなく、自身のオブジェクト内だけに変更します。
    342                 * @og.rev 6.9.9.1 (2018/08/27) デフォルトコンストラクタに、super(); を呼び出すようにします。
    343                 */
    344                public CaseBuilder() { super(); }               // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    345//                      final long now = System.currentTimeMillis();
    346//                      if( now - lastCall > CACHE_TIME ) {                                     // 前回キャッシュ時からの経過時刻が、CACHE_TIME を上回っている場合。
    347//                              lastCall = now;
    348//                              MSG_MAP.clear();
    349//                      }
    350//              }
    351
    352                /**
    353                 * 初期化します。
    354                 *
    355                 * @og.rev 6.9.2.1 (2018/03/12) 新規追加
    356                 *
    357                 * @return   自分自身
    358                 */
    359                public CaseBuilder init() {
    360                        final long now = System.currentTimeMillis();
    361                        if( now - lastCall > CACHE_TIME ) {                                     // 前回キャッシュ時からの経過時刻が、CACHE_TIME を上回っている場合。
    362                                lastCall = now;
    363                                MSG_MAP.clear();
    364//                              buf.setLength( 0 );
    365                                list.clear();
    366                        }
    367                        return this;
    368                }
    369
    370                /**
    371                 * タイトルとメッセージを内部のStringBuilderに追記していきます。
    372                 *
    373                 * メッセージが、null や、空文字の場合は、何もしません。また、同一メッセージの追加は出来ません。
    374                 * 戻り値に、自分自身のオブジェクトを返すので、StringBuilder と同様に、接続できます。
    375                 *
    376                 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
    377                 * @og.rev 6.4.3.3 (2016/03/04) 同一メッセージの判定を、trim() したキーで行います。
    378                 * @og.rev 6.9.2.1 (2018/03/12) タイトルがnullや空文字の場合も、なにもしません。
    379                 *
    380                 * @param    title      タイトルに相当します。
    381                 * @param    msg        メッセージ
    382                 * @return   自分自身
    383                 */
    384                public CaseBuilder append( final String title , final String msg ) {
    385//                      if( msg != null && !msg.isEmpty() ) {
    386                        if( title != null && !title.isEmpty() && msg != null && !msg.isEmpty() ) {
    387                                // 超特殊処理1
    388                                // msg に改行ｺｰﾄﾞを含む場合、最初の改行で、前後に分けて、それぞれをキャッシュ判定します。
    389                                // SQL文など、行が多く出る場合に、何度も繰り返されるのを避けるためです。
    390                                // 通常、１行目は、Exception発生元のため、異なるケースがありますが、２行目以降は、
    391                                // 同じケースが多いための処置です。
    392                                final String msg0 = msg.trim();
    393
    394                                // Map#putIfAbsent ： 戻り値は、以前の値。追加有り、置換なし(先勝)、削除なし
    395                                // Map#put と何が違うかというと、置き換えが無い。速度は誤差範囲です。
    396                                if( MSG_MAP.putIfAbsent( msg0,USE_KEY ) == null ) {             // 同期Setの代わり。未登録時は、null が戻る。
    397                                        final int adrs = msg0.indexOf( '\n' );                          // よくない判定方法
    398                                        if( adrs < 0 ) {
    399//                                              buf.append( CR ).append( title ).append( msg0 );
    400                                                list.add( title + msg0 );
    401                                        }
    402                                        else {
    403                                                final String msg1 = msg0.substring( 0,adrs ).trim();            // 最初の改行で、前後に分割します。
    404                                                final String msg2 = msg0.substring( adrs+1 ).trim();
    405                                                if( MSG_MAP.putIfAbsent( msg1,USE_KEY ) == null ) {
    406//                                                      buf.append( CR ).append( title ).append( msg1 );
    407                                                        list.add( title + msg1 );
    408                                                }
    409                                                if( MSG_MAP.putIfAbsent( msg2,USE_KEY ) == null ) {
    410//                                                      buf.append( CR ).append( title ).append( msg2 );
    411                                                        list.add( title + msg2 );
    412                                                }
    413                                        }
    414                                }
    415                        }
    416                        return this;
    417                }
    418
    419                /**
    420                 * Throwable の getStackTrace() 結果の内、opengion に関する箇所だけのStackTraceElement配列をCaseBuilderオブジェクトに書き込みます。
    421                 *
    422                 * 通常、スタックトレース情報は、膨大なメッセージが含まれるため、その中の、org.opengion か、org.apache.jsp.jsp を
    423                 * 含む箇所だけを、抜粋します。
    424                 * ただし、ThrowUtilクラス(このクラス)内で、スタックトレースを new しているため、このクラスで発生した
    425                 * スタックトレースは、含まないようにしています。このクラスからエラーが発生しないことを望みます。
    426                 * 処理は、先頭から、MIN_STACK_SIZE 行は、元のままのメッセージを出力し、minCnt件数で、打ち切ります。
    427                 * minCnt が、0 か、マイナスの場合は、打ち切り制限なしです。( Integer.MAX_VALUE を指定させるのが嫌だっただけです。 )
    428                 *
    429                 * @og.rev 6.4.2.0 (2016/01/29) 新規作成
    430                 * @og.rev 6.9.2.1 (2018/03/12) 選別だけを別に用意します。
    431                 *
    432                 * @param    th         元のThrowableオブジェクト(!= null 保障済み)
    433                 * @param    minCnt     StackTraceElementを登録する最小件数
    434                 * @return   自分自身
    435                 * @see         java.lang.Throwable#getStackTrace()
    436                 */
    437//              public CaseBuilder addStackTrace( final Throwable th , final int minCnt , final int minCnt ) {
    438                public CaseBuilder addStackTrace( final Throwable th , final int minCnt ) {
    439                        for( final StackTraceElement stEle : selectElement( th,minCnt ) ) {
    440                                if( stEle == null || "".equals( stEle.getClassName() ) ) {
    441                                        append( "    at   " , "...." );                                 // StackTraceElement が省略されている。
    442                                }
    443                                else {
    444                                        append( "    at   " , stEle.toString() );
    445                                }
    446                        }
    447                        return this;
    448
    449//                      int idx = 0;
    450//                      for( final StackTraceElement stEle : th.getStackTrace() ) {
    451//                              final String cls = stEle.getClassName();
    452//
    453//                              boolean flag = true;            // 連続で、出力しない。
    454//                              if( minCnt < 0 || idx < minCnt ||
    455//                                              !cls.contains( "ThrowUtil" ) &&
    456//                                                      ( cls.contains( "org.opengion" ) || cls.contains( "org.apache.jsp.jsp" ) ) ) {
    457//                                      append( "    at   " , stEle.toString() );
    458//                                      flag = true;                    // 省略解除
    459//                              }
    460//                              else {
    461//                                      if( flag ) {
    462//                                              append( "    at   " , "...." );                                 // 連続で、出力しない。
    463//                                              flag = false;
    464//                                      }
    465//                              }
    466//                              idx++ ;
    467//
    468//      //                      // 6.9.2.1 (2018/03/12) 最小件数のbreak判定の方法を見直します。
    469//      //                      // ThrowUtil クラスは除外し、org.opengion か、org.apache.jsp.jsp のみ収集する。
    470//      //                      if( idx < minCnt ||
    471//      //                                      !cls.contains( "ThrowUtil" ) &&
    472//      //                                              ( cls.contains( "org.opengion" ) || cls.contains( "org.apache.jsp.jsp" ) ) ) {
    473//      //                              append( "    at   " , stEle.toString() );
    474//      //                      }
    475//      //                      else {
    476//      //                              append( "    at   " , "...." );                                 // 最初の１回しか、出力されません。
    477//      //                      }
    478//      //                      if( minCnt > 0 && idx >= minCnt ) { break; }            // minCnt 件で、処理を打ち切ります。
    479//      //                      idx++ ;
    480//                      }
    481//                      return this;
    482                }
    483
    484                /**
    485                 * 内部のStringBuilderを、文字列に変換して返します。
    486                 *
    487                 * 出力の直前に改行コードを出しています。
    488                 *
    489                 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
    490                 * @og.rev 6.9.9.1 (2018/08/27) StringBuilder を、List に変更したが、empty時の処理漏れ対応。
    491                 *
    492                 * @return   内部のStringBuilderの文字列化されたもの
    493                 */
    494                @Override
    495                public String toString() {
    496                        if( list.isEmpty() ) { return CR; }                     // 6.9.9.1 (2018/08/27)
    497
    498//                      return buf.append( CR ).toString();
    499//                      return buf.toString().trim();
    500                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    501                        for( final String msg : list ) {
    502                                buf.append( CR ).append( msg );
    503                        }
    504                        return buf.toString();
    505                }
    506
    507                /**
    508                 * 内部のStringBuilderを、文字列に変換して返します。
    509                 *
    510                 * 出力の直前に改行コードを出しています。
    511                 *
    512                 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
    513                 * @og.rev 6.9.9.1 (2018/08/27) StringBuilder を、List に変更したが、empty時の処理漏れ対応。
    514                 *
    515                 * @return   内部のStringBuilderの文字列化されたもの
    516                 */
    517                public String toThrowMsg() {
    518                        if( list.isEmpty() ) { return CR; }                     // 6.9.9.1 (2018/08/27)
    519
    520                        final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
    521                        for( final String msg : list ) {
    522                                if( ! msg.startsWith( "    at   " ) ) {
    523                                        buf.append( CR ).append( msg );
    524                                }
    525                        }
    526                        return buf.toString();
    527                }
    528        }
    529}