/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.system;

import java.util.List;												// 6.9.2.1 (2018/03/12)
import java.util.ArrayList;											// 6.9.2.1 (2018/03/12)
import java.util.Set;												// 6.9.2.1 (2018/03/12)
import java.util.HashSet;											// 6.9.2.1 (2018/03/12)

import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;

import static org.opengion.fukurou.system.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.4.2.0 (2016/01/29)

/**
 * ThrowUtil.java は、共通的に使用される Throwable,Exception関連メソッドを集約した、クラスです。
 *
 * StringUtil にあったメソッドを、こちらに移動させました。
 *
 * @og.group ユーティリティ
 *
 * @og.rev 6.4.2.0 (2016/01/29) 新規作成
 * @og.rev 6.4.3.2 (2016/02/19) 全面書き換え
 *
 * @version  6.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public final class ThrowUtil {
	public static final int MIN_STACK_SIZE = 3;					// 先頭から、通常にスタックトレースする行数。
//	public static final int NOMAL_STACK_SIZE = 5;				// 先頭から、通常にスタックトレースする行数。
//	public static final int UNLIMITED_SIZE = -1;				// スタックトレースする行数。-1 は、制限なし

	// 6.9.2.1 (2018/03/12) 複数個所で呼ばれた場合の処理が、うまく出来ないので、暫定対策。(同時起動で、混ざる)
	private static final CaseBuilder C_BUF = new CaseBuilder();

	// 6.9.3.0 (2018/03/26) static で持ちます。
	private static final StackTraceElement NULL_STE = new StackTraceElement( "","","",-1 );

	/**
	 *	デフォルトコンストラクターをprivateにして、
	 *	オブジェクトの生成をさせないようにする。
	 *
	 */
	private ThrowUtil() {}

	/**
	 * Throwable の printStackTrace() 結果の内、opengion に関する箇所だけを文字列に変換して返します。
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動。
	 * @og.rev 6.4.2.0 (2016/01/29) すべてのスタックトレースを行っていたが、絞り込みます。
	 *
	 * @param    th   printStackTraceすべき元のThrowableオブジェクト
	 *
	 * @return   Throwableの詳細メッセージ( th.printStackTrace() )
	 */
	public static String ogStackTrace( final Throwable th ) {
		return ogStackTrace( null , th );
	}

	/**
	 * Throwable の printStackTrace() 結果の内、opengion に関する箇所だけを文字列に変換して返します。
	 *
	 * printStackTrace() すると、膨大なメッセージが表示されるため、その中の、"org.opengion" を
	 * 含む箇所だけを、抜粋します。
	 * また、同じ内容のエラーも除外します。
	 * 先頭から、MIN_STACK_SIZE 行は、元のままのメッセージを出力します。
	 * Throwable#getCause() で、再帰的に原因をさかのぼります。
	 *
	 * @og.rev 5.7.2.0 (2014/01/10) 新規作成
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動し、msg 引数を追加。
	 * @og.rev 6.4.3.2 (2016/02/19) (通常try-with-resources文によって)抑制された例外も出力します。
	 * @og.rev 6.5.0.1 (2016/10/21) メッセージに、BuildNumber.ENGINE_INFO を含める。
	 * @og.rev 6.9.0.1 (2018/02/05) causeの対応が中途半端だったので、修正します。
	 * @og.rev 6.9.2.1 (2018/03/12) 最小件数のbreak判定の方法を見直します。
	 *
	 * @param    msg 合成したいメッセージ
	 * @param    th 元のThrowableオブジェクト
	 *
	 * @return   Throwableの詳細メッセージ( StackTraceElement の抜粋 )
	 */
	public static String ogStackTrace( final String msg,final Throwable th ) {
//		final CaseBuilder buf = new CaseBuilder()
		C_BUF.init()
			.append( "Version: " , BuildNumber.ENGINE_INFO )				// 6.5.0.1 (2016/10/21) エンジンのバージョン
			.append( "Message: " , msg );									// 追加メッセージ

		if( th != null ) {
	//		final CaseBuilder buf = new CaseBuilder()
	//			.append( "Version: " , BuildNumber.ENGINE_INFO )				// 6.5.0.1 (2016/10/21) エンジンのバージョン
	//			.append( "Message: " , msg )									// 追加メッセージ
				// Throwable.toString() で、クラス名とメッセージを分けて、それぞれを重複チェックする。
			C_BUF.append( "Error  : " , th.getClass().getCanonicalName() )		// クラス名
			   .append( "         " , th.getLocalizedMessage() )				// メッセージ
//			   .addStackTrace( th , MIN_STACK_SIZE , UNLIMITED_SIZE );
			   .addStackTrace( th , MIN_STACK_SIZE );							// 6.9.2.1 (2018/03/12)

			// 原因の Throwable
			Throwable tmpTh = th.getCause();
			while( tmpTh != null ) {
				 C_BUF.append( "Cause  : " , tmpTh.getClass().getCanonicalName() )	// クラス名
					.append( "         " , tmpTh.getLocalizedMessage() )			// メッセージ
//					.addStackTrace( tmpTh , 0 , UNLIMITED_SIZE );					// 最小行は指定しない。
					.addStackTrace( tmpTh , MIN_STACK_SIZE );						// 6.9.2.1 (2018/03/12)

				tmpTh = tmpTh.getCause();
			}

			// 6.4.3.2 (2016/02/19) (通常try-with-resources文によって)抑制された例外も出力します。
			// このThrowable は、原因の Throwable は、求めません。
			for( final Throwable supTh : th.getSuppressed() ) {
				C_BUF.append( "Suppressed : " , supTh.getClass().getCanonicalName() )		// クラス名
				   .append( "             " , supTh.getLocalizedMessage() )				// メッセージ
//				   .addStackTrace( supTh , 0 , UNLIMITED_SIZE );
				   .addStackTrace( supTh , MIN_STACK_SIZE );							// 6.9.2.1 (2018/03/12)
			}
		}

//		return buf.toString();
		return C_BUF.toString().trim();
	}

	/**
	 * 発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列を返します。
	 *
	 * 通常、System.out.println() で済ましていたエラーメッセージに対して、
	 * エラー発生場所を特定する為の情報を付与したメッセージを作成します。
	 * 発生場所の行番号は、このメソッド（実施は、オーバーロード先）で new Throwable して、取得します。
	 * 呼ぶ場所で、new Throwable() する代わりの簡易目祖度になります。
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) 新規作成。
	 *
	 * @param    msg 合成したいメッセージ
	 *
	 * @return   発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列
	 */
	public static String ogThrowMsg( final String msg ) {
		return ogThrowMsg( msg , null );
	}

	/**
	 * 発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列を返します。
	 *
	 * 通常、System.out.println() で済ましていたエラーメッセージに対して、
	 * エラー発生場所を特定する為の情報を付与したメッセージを作成します。
	 * 行番号は、引数のThrowableの最初の情報のみ使用する為、通常は、このメソッドを
	 * 呼ぶ場所で、new Throwable() します。
	 * 発生クラスは、org.opengion か、org.apache.jsp.jsp を含む３行のみの簡易メッセージとします。
	 *
	 * @og.rev 6.3.6.1 (2015/08/28) 新規作成
	 * @og.rev 6.3.6.1 (2015/08/28) メッセージに、th.getLocalizedMessage() を含める。
	 * @og.rev 6.3.9.0 (2015/11/06) thのnullチェックを先に行う。
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動するとともに、メソッド名を、ogErrMsg → ogThrowMsg に変更。
	 * @og.rev 6.5.0.1 (2016/10/21) メッセージに、BuildNumber.ENGINE_INFO を含める。
	 * @og.rev 6.9.0.1 (2018/02/05) causeの対応が中途半端だったので、修正します。
	 * @og.rev 6.9.2.1 (2018/03/12) 最小件数のbreak判定の方法を見直します。
	 *
	 * @param    msg 合成したいメッセージ
	 * @param    th  元のThrowableオブジェクト
	 *
	 * @return   発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列
	 */
	public static String ogThrowMsg( final String msg,final Throwable th ) {
//		final CaseBuilder buf = new CaseBuilder()
		C_BUF.init()
			.append( "Version: " , BuildNumber.ENGINE_INFO )					// 6.5.0.1 (2016/10/21) エンジンのバージョン
			.append( "Message: " , msg );										// 追加メッセージ

		if( th != null ) {
			C_BUF.append( "Error  : " , th.getClass().getCanonicalName() )		// クラス名
			   .append( "         " , th.getLocalizedMessage() );				// メッセージ
//			   .addStackTrace( th , MIN_STACK_SIZE , MIN_STACK_SIZE );
		//	   .addStackTrace( th , MIN_STACK_SIZE );							// 6.9.2.1 (2018/03/12)

//			final Throwable cause = th.getCause();								// 原因の Throwable (１回だけ)
//			if( cause != null ) {
//				buf.append( "Cause  : " , cause.getClass().getCanonicalName() )		// クラス名
//				   .append( "         " , cause.getLocalizedMessage() );			// メッセージ
////				   .addStackTrace( cause , MIN_STACK_SIZE , MIN_STACK_SIZE );
//		//		   .addStackTrace( cause , MIN_STACK_SIZE );						// 6.9.2.1 (2018/03/12)
//			}

			// 原因の Throwable
			Throwable tmpTh = th.getCause();
			while( tmpTh != null ) {
				 C_BUF.append( "Cause  : " , tmpTh.getClass().getCanonicalName() )	// クラス名
					.append( "         " , tmpTh.getLocalizedMessage() );			// メッセージ

				tmpTh = tmpTh.getCause();
			}

			// 6.4.3.2 (2016/02/19) (通常try-with-resources文によって)抑制された例外も出力します。
			// このThrowable は、原因の Throwable は、求めません。
			for( final Throwable supTh : th.getSuppressed() ) {
				C_BUF.append( "Suppressed : " , supTh.getClass().getCanonicalName() )		// クラス名
				   .append( "             " , supTh.getLocalizedMessage() );			// メッセージ
			}
		}

//		return buf.toString();
	//	return buf.toString().trim();
		return C_BUF.toThrowMsg().trim();

//		final Throwable cause =    th == null ? new Throwable() : th.getCause();	// 原因の Throwable
//		final Throwable tmpTh = cause == null ? new Throwable() : cause;			// 行番号取得のため、例外を発生させる。
//
//		final CaseBuilder buf = new CaseBuilder()
//			.append( "Version: " , BuildNumber.ENGINE_INFO )					// 6.5.0.1 (2016/10/21) エンジンのバージョン
//			// Throwable.toString() で、クラス名とメッセージを分けて、それぞれを重複チェックする。
//			.append( "Error  : " , tmpTh.getClass().getCanonicalName() )		// クラス名
//			.append( "         " , tmpTh.getLocalizedMessage() )				// メッセージ
//			.append( "Message: " , msg )										// 追加メッセージ
//			.addStackTrace( tmpTh , MIN_STACK_SIZE , MIN_STACK_SIZE );
//
//		return buf.toString();
	}

//	/**
//	 * このクラスが連続で呼ばれた場合のメッセージを返します。
//	 *
//	 * この、staticクラスは、色々な箇所で呼ばれる可能性があり、しかも、
//	 * 一連のエラーで、別々に呼ばれると、メッセージが分断されます。
//	 *
//	 * そこで、暫定的に、連続にメッセージのみ、内部変数に設定していきます。
//	 * ただし、同時に異なるエラーで呼ばれた場合には、それらのメッセージが
//	 * 混在する（まざる）ので、暫定的な処置です。
//	 *
//	 * この呼び出しで、内部変数をクリアします。
//	 *
//	 * @og.rev 6.9.2.1 (2018/03/12) 連続で呼ばれた場合のメッセージを返す。
//	 *
//	 * @return   連続で呼ばれた場合のメッセージ
//	 */
//	public static String getLastMsg() {
//		final String rtn = buf.toString();
//		buf.clear();
//
//		return rtn;
//	}

	/**
	 * Throwable の getStackTrace() 結果の内、opengion に関する箇所だけのStackTraceElement配列を選別して返します。
	 *
	 * 通常、スタックトレース情報は、膨大なメッセージが含まれるため、その中の、org.opengion か、org.apache.jsp.jsp を
	 * 含む箇所だけを、抜粋します。
	 * ただし、ThrowUtilクラス(このクラス)内で、スタックトレースを new しているため、このクラスで発生した
	 * スタックトレースは、含まないようにしています。このクラスからエラーが発生しないことを望みます。
	 * 処理は、先頭から、minCnt件数までは、そのまま残し、それ以降は、関連するトレース情報のみ残します。
	 *
	 * @og.rev 6.9.2.1 (2018/03/12) 選別だけを別に用意します。
	 *
	 * @param    th		元のThrowableオブジェクト(!= null 保障済み)
	 * @param    minCnt	StackTraceElementを登録する最小件数
	 * @return   選別されたStackTraceElement配列
	 * @see		java.lang.Throwable#getStackTrace()
	 */
	public static StackTraceElement[] selectElement( final Throwable th , final int minCnt ) {
//		final StackTraceElement NULL_ELEMENT = new StackTraceElement( "","","",-1 );
		final Set<String> cacheSet = new HashSet<>();			// 同一スタックの除外用

		final List<StackTraceElement> list = new ArrayList<>();

		int idx = 0;
		for( final StackTraceElement stEle : th.getStackTrace() ) {
			final String stkMsg = stEle.toString();
			if( cacheSet.contains( stkMsg ) ) { continue; }		// 同じメッセージを出さない対応
			cacheSet.add( stkMsg );

			final String cls = stEle.getClassName();

			boolean flag = true;				// 連続で、出力しない。
			if( minCnt < 0 || idx < minCnt || 
	//				!cls.contains( "ThrowUtil" ) &&				// ここで、new Throwable しないので、判定不要。
						cls.contains( "org.opengion" ) || cls.contains( "org.apache.jsp.jsp" ) ) {
				list.add( stEle );
				flag = true;					// 省略解除
			}
			else {
				if( flag ) {
//					list.add( NULL_ELEMENT );	// StackTraceElementの省略
					list.add( NULL_STE );		// StackTraceElementの省略
					flag = false;				// 連続で、出力しない。
				}
			}
			idx++ ;
		}
		return list.toArray( new StackTraceElement[list.size()] );
	}

	/**
	 * StringBuilder を、例外処理のスタックに特化した形で作り直した内部クラスです。
	 *
	 * printStackTrace() すると、膨大なメッセージが表示されるため、その中の、"org.opengion" と
	 * "org.apache.jsp.jsp" を含む箇所だけを、抜粋します。
	 * また、同じ内容のエラーも除外します。
	 *
	 * ※ 怪しい実装
	 *    StackTrace は、内部的に、色々な箇所で呼ばれたり、同じ要因で、何度も再作成されたりします。
	 *    この内部クラスも、ひとつの例外で、何度も作成されるため、重複チェックの方法を、時間制限で、
	 *    同一メッセージの重複処理を行っています。
	 *    よって、まったく異なる要因のエラーが同時に発生した場合、重複処理の判定で、除外される可能性が
	 *    あります。（発生場所の行番号が異なれば、重複処理されても残るので、デバッグ可能です）
	 *
	 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
	 * @og.rev 6.9.2.1 (2018/03/12) 重複処理を、staticではなく、自身のオブジェクト内だけに変更します。
	 * @og.rev 6.9.9.1 (2018/08/27) StringBuilder を、List に変更したが、empty時の処理漏れ対応。
	 */
	private static final class CaseBuilder {
		private static final String USE_KEY    = "USE_KEY" ;
		private static final long   CACHE_TIME = 3000L;			// Exceptionが発生した場合、連続してCallされるため、一まとめにする。

//		private static final ConcurrentMap<String,String> MSG_MAP = new ConcurrentHashMap<>();	// 同期Setの代わり。重複の取り除き判定
		private final ConcurrentMap<String,String> MSG_MAP = new ConcurrentHashMap<>();	// 同期Setの代わり。重複の取り除き判定
//		private static volatile long lastCall = 0L;
//		private volatile long lastCall ;						// 初期値 0L
		private long lastCall ;									// 初期値 0L ( 6.9.9.4 (2018/10/01) PDM: Use of modifier volatile is not recommended.)

//		private final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		private final List<String> list = new ArrayList<>();

		/**
		 * デフォルトコンストラクタ。
		 *
		 * 時間制限が過ぎれば、キャッシュをクリアします。
		 *
		 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
		 * @og.rev 6.4.3.3 (2016/03/04) synchronized を取り除きます。
		 * @og.rev 6.9.2.1 (2018/03/12) 重複処理を、staticではなく、自身のオブジェクト内だけに変更します。
		 * @og.rev 6.9.9.1 (2018/08/27) デフォルトコンストラクタに、super(); を呼び出すようにします。
		 */
		public CaseBuilder() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
//			final long now = System.currentTimeMillis();
//			if( now - lastCall > CACHE_TIME ) {					// 前回キャッシュ時からの経過時刻が、CACHE_TIME を上回っている場合。
//				lastCall = now;
//				MSG_MAP.clear();
//			}
//		}

		/**
		 * 初期化します。
		 *
		 * @og.rev 6.9.2.1 (2018/03/12) 新規追加
		 * @og.rev 6.9.9.4 (2018/10/01) PDM: Use of modifier volatile is not recommended.
		 *
		 * @return   自分自身
		 */
		public CaseBuilder init() {
			synchronized( MSG_MAP ) {
				final long now = System.currentTimeMillis();
				if( now - lastCall > CACHE_TIME ) {					// 前回キャッシュ時からの経過時刻が、CACHE_TIME を上回っている場合。
					lastCall = now;
					MSG_MAP.clear();
//					buf.setLength( 0 );
					list.clear();
				}
			}
			return this;
		}

		/**
		 * タイトルとメッセージを内部のStringBuilderに追記していきます。
		 *
		 * メッセージが、null や、空文字の場合は、何もしません。また、同一メッセージの追加は出来ません。
		 * 戻り値に、自分自身のオブジェクトを返すので、StringBuilder と同様に、接続できます。
		 *
		 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
		 * @og.rev 6.4.3.3 (2016/03/04) 同一メッセージの判定を、trim() したキーで行います。
		 * @og.rev 6.9.2.1 (2018/03/12) タイトルがnullや空文字の場合も、なにもしません。
		 *
		 * @param    title	タイトルに相当します。
		 * @param    msg	メッセージ
		 * @return   自分自身
		 */
		public CaseBuilder append( final String title , final String msg ) {
//			if( msg != null && !msg.isEmpty() ) {
			if( title != null && !title.isEmpty() && msg != null && !msg.isEmpty() ) {
				// 超特殊処理1
				// msg に改行ｺｰﾄﾞを含む場合、最初の改行で、前後に分けて、それぞれをキャッシュ判定します。
				// SQL文など、行が多く出る場合に、何度も繰り返されるのを避けるためです。
				// 通常、１行目は、Exception発生元のため、異なるケースがありますが、２行目以降は、
				// 同じケースが多いための処置です。
				final String msg0 = msg.trim();

				// Map#putIfAbsent ： 戻り値は、以前の値。追加有り、置換なし(先勝)、削除なし
				// Map#put と何が違うかというと、置き換えが無い。速度は誤差範囲です。
				if( MSG_MAP.putIfAbsent( msg0,USE_KEY ) == null ) {		// 同期Setの代わり。未登録時は、null が戻る。
					final int adrs = msg0.indexOf( '\n' );				// よくない判定方法
					if( adrs < 0 ) {
//						buf.append( CR ).append( title ).append( msg0 );
						list.add( title + msg0 );
					}
					else {
						final String msg1 = msg0.substring( 0,adrs ).trim();		// 最初の改行で、前後に分割します。
						final String msg2 = msg0.substring( adrs+1 ).trim();
						if( MSG_MAP.putIfAbsent( msg1,USE_KEY ) == null ) {
//							buf.append( CR ).append( title ).append( msg1 );
							list.add( title + msg1 );
						}
						if( MSG_MAP.putIfAbsent( msg2,USE_KEY ) == null ) {
//							buf.append( CR ).append( title ).append( msg2 );
							list.add( title + msg2 );
						}
					}
				}
			}
			return this;
		}

		/**
		 * Throwable の getStackTrace() 結果の内、opengion に関する箇所だけのStackTraceElement配列をCaseBuilderオブジェクトに書き込みます。
		 *
		 * 通常、スタックトレース情報は、膨大なメッセージが含まれるため、その中の、org.opengion か、org.apache.jsp.jsp を
		 * 含む箇所だけを、抜粋します。
		 * ただし、ThrowUtilクラス(このクラス)内で、スタックトレースを new しているため、このクラスで発生した
		 * スタックトレースは、含まないようにしています。このクラスからエラーが発生しないことを望みます。
		 * 処理は、先頭から、MIN_STACK_SIZE 行は、元のままのメッセージを出力し、minCnt件数で、打ち切ります。
		 * minCnt が、0 か、マイナスの場合は、打ち切り制限なしです。( Integer.MAX_VALUE を指定させるのが嫌だっただけです。 )
		 *
		 * @og.rev 6.4.2.0 (2016/01/29) 新規作成
		 * @og.rev 6.9.2.1 (2018/03/12) 選別だけを別に用意します。
		 *
		 * @param    th		元のThrowableオブジェクト(!= null 保障済み)
		 * @param    minCnt	StackTraceElementを登録する最小件数
		 * @return   自分自身
		 * @see		java.lang.Throwable#getStackTrace()
		 */
//		public CaseBuilder addStackTrace( final Throwable th , final int minCnt , final int minCnt ) {
		public CaseBuilder addStackTrace( final Throwable th , final int minCnt ) {
			for( final StackTraceElement stEle : selectElement( th,minCnt ) ) {
				if( stEle == null || "".equals( stEle.getClassName() ) ) {
					append( "    at   " , "...." );					// StackTraceElement が省略されている。
				}
				else {
					append( "    at   " , stEle.toString() );
				}
			}
			return this;

//			int idx = 0;
//			for( final StackTraceElement stEle : th.getStackTrace() ) {
//				final String cls = stEle.getClassName();
//
//				boolean flag = true;		// 連続で、出力しない。
//				if( minCnt < 0 || idx < minCnt || 
//						!cls.contains( "ThrowUtil" ) &&
//							( cls.contains( "org.opengion" ) || cls.contains( "org.apache.jsp.jsp" ) ) ) {
//					append( "    at   " , stEle.toString() );
//					flag = true;			// 省略解除
//				}
//				else {
//					if( flag ) {
//						append( "    at   " , "...." );					// 連続で、出力しない。
//						flag = false;
//					}
//				}
//				idx++ ;
//
//	//			// 6.9.2.1 (2018/03/12) 最小件数のbreak判定の方法を見直します。
//	//			// ThrowUtil クラスは除外し、org.opengion か、org.apache.jsp.jsp のみ収集する。
//	//			if( idx < minCnt ||
//	//					!cls.contains( "ThrowUtil" ) &&
//	//						( cls.contains( "org.opengion" ) || cls.contains( "org.apache.jsp.jsp" ) ) ) {
//	//				append( "    at   " , stEle.toString() );
//	//			}
//	//			else {
//	//				append( "    at   " , "...." );					// 最初の１回しか、出力されません。
//	//			}
//	//			if( minCnt > 0 && idx >= minCnt ) { break; }		// minCnt 件で、処理を打ち切ります。
//	//			idx++ ;
//			}
//			return this;
		}

		/**
		 * 内部のStringBuilderを、文字列に変換して返します。
		 *
		 * 出力の直前に改行コードを出しています。
		 *
		 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
		 * @og.rev 6.9.9.1 (2018/08/27) StringBuilder を、List に変更したが、empty時の処理漏れ対応。
		 *
		 * @return   内部のStringBuilderの文字列化されたもの
		 */
		@Override
		public String toString() {
			if( list.isEmpty() ) { return CR; }			// 6.9.9.1 (2018/08/27)

//			return buf.append( CR ).toString();
//			return buf.toString().trim();
			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
			for( final String msg : list ) {
				buf.append( CR ).append( msg );
			}
			return buf.toString();
		}

		/**
		 * 内部のStringBuilderを、文字列に変換して返します。
		 *
		 * 出力の直前に改行コードを出しています。
		 *
		 * @og.rev 6.4.3.2 (2016/02/19) 新規追加
		 * @og.rev 6.9.9.1 (2018/08/27) StringBuilder を、List に変更したが、empty時の処理漏れ対応。
		 *
		 * @return   内部のStringBuilderの文字列化されたもの
		 */
		public String toThrowMsg() {
			if( list.isEmpty() ) { return CR; }			// 6.9.9.1 (2018/08/27)

			final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
			for( final String msg : list ) {
				if( ! msg.startsWith( "    at   " ) ) {
					buf.append( CR ).append( msg );
				}
			}
			return buf.toString();
		}
	}
}
