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