/*
 * 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.Set;
import java.util.LinkedHashSet;

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) 新規作成
 *
 * @version  6.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK8.0,
 */
public final class ThrowUtil {
	private static final int MAX_NORMAL_STACK = 3;				// 先頭から、通常にスタックトレースする行数。

	private static final StackTraceElement ST_TR_EL = new StackTraceElement( "...", "", "", 0 );

	/**
	 *	デフォルトコンストラクターを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 stringStackTrace( final Throwable th ) {
	public static String ogStackTrace( final Throwable th ) {
		return ogStackTrace( null , th );
//		if( th == null ) { return null; }
//
//		final StringWriter sw = new StringWriter();
//		th.printStackTrace( new PrintWriter( sw ) );
//
//		return String.valueOf( sw );
	}

	/**
	 * Throwable の printStackTrace() 結果の内、opengion に関する箇所だけを文字列に変換して返します。
	 *
	 * printStackTrace() すると、膨大なメッセージが表示されるため、その中の、"org.opengion" を
	 * 含む箇所だけを、抜粋します。
	 * また、同じ内容のエラーも除外します。
	 * 先頭から、MAX_NORMAL_STACK 行は、元のままのメッセージを出力します。
	 * Throwable#getCause() で、再帰的に原因をさかのぼります。
	 *
	 * @og.rev 5.7.2.0 (2014/01/10) 新規作成
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動し、msg 引数を追加。
	 *
	 * @param    msg 合成したいメッセージ
	 * @param    th 元のThrowableオブジェクト
	 *
	 * @return   Throwableの詳細メッセージ( StackTraceElement の抜粋 )
	 */
//	public static String ogStackTrace( final Throwable th ) {
	public static String ogStackTrace( final String msg,final Throwable th ) {

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "Error  :" ).append( th ).append( CR )
			.append( "Version:" ).append( BuildNumber.ENGINE_INFO  );

		if( msg != null ) {
			buf.append( CR ).append( "Message:" ).append( msg );
		}

		Throwable tmpTh = th;
		while( tmpTh != null ) {									// 再帰定義の変わりにループの使用。ヘッダー制御と、重複セットの取り扱いの関係。
			for( final StackTraceElement stEle : getStackTrace( tmpTh ) ) {		// 存在しない場合は、長さゼロの配列が戻ってくる。
				buf.append( CR ).append( "    at " ).append( stEle );
			}

			tmpTh = tmpTh.getCause();			// 原因の Throwable
			if( tmpTh != null ) {
				buf.append( CR ).append( "Cause  :" ).append( tmpTh );
			}
		}

		return buf.append( CR ).toString();
	}

	/**
	 * Throwable の getStackTrace() 結果の内、opengion に関する箇所だけのStackTraceElement配列を返します。
	 *
	 * 通常、スタック・トレース情報は、膨大なメッセージが含まれるため、その中の、"org.opengion" を
	 * 含む箇所だけを、抜粋します。
	 * 先頭から、MAX_NORMAL_STACK 行は、元のままのメッセージを出力します。
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) 新規作成
	 *
	 * @param    th 元のThrowableオブジェクト
	 *
	 * @return	このスロー可能オブジェクトに関するスタック・トレースを表す、スタック・トレース要素の配列。
	 * @see		java.lang.Throwable#getStackTrace()
	 */
	private static StackTraceElement[] getStackTrace( final Throwable th ) {

		final Set<StackTraceElement> steSet = new LinkedHashSet<>();		// 重複の取り除きと、スタック配列の順序の維持

//		if( th != null && ! ( th instanceof OgRuntimeException ) ) {

//		final Throwable tmpTh = th != null && th instanceof OgRuntimeException ? th.getCause() : th ;

		if( th != null ) {
			int idx = 0;
			for( final StackTraceElement stEle : th.getStackTrace() ) {
				if( idx++ < MAX_NORMAL_STACK ) {						// MAX_NORMAL_STACK 件までは無条件に出力する。
					steSet.add( stEle );								// 同一エラー行は１度しか出さない。
					continue;
				}

				final String cls = stEle.getClassName();
				if( cls.contains( "org.opengion" ) ) {
					steSet.add( stEle );								// 同一エラー行は１度しか出さない。
				}
				else {
					steSet.add( ST_TR_EL );								// 重複が許されないので、最初に ... 出力されるだけ。
				}
			}
		}

		return steSet.toArray( new StackTraceElement[steSet.size()] );
	}

	/**
	 * 発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列を返します。
	 *
	 * 通常、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() します。
	 *
	 * @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 に変更。
	 *
	 * @param    msg 合成したいメッセージ
	 * @param    th  元のThrowableオブジェクト
	 *
	 * @return   発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列
	 */
//	public static String ogErrMsg( final String msg,final Throwable th ) {
	public static String ogThrowMsg( final String msg,final Throwable th ) {
		final Throwable tmpTh = th == null ? new Throwable() : th ;			// 行番号取得のため、例外を発生させる。

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE )
			.append( "Error  :" ).append( th  ).append( CR )				// オリジナルの th を使用。null の場合もあるため、th.toString() は使えない。
			.append( "Version:" ).append( BuildNumber.ENGINE_INFO  );

		if( msg != null ) {
			buf.append( CR ).append( "Message:" ).append( msg );
		}

		int idx = 0;
		for( final StackTraceElement stEle : getStackTrace( tmpTh ) ) {

			buf.append( CR )
				.append( "    at " ).append( stEle.getClassName()  )
				.append( '.'       ).append( stEle.getMethodName() )
				.append( '('       ).append( stEle.getFileName()   )
				.append( ':'       ).append( stEle.getLineNumber() )
				.append( ')'       );
			if( idx++ >= MAX_NORMAL_STACK ) { break; }					// 最大 MAX_NORMAL_STACK 行まで遡る。
		}

		return buf.append( CR ).toString();
	}

//	/**
//	 * 発生元を示すクラス、メソッド、行番号とともに、メッセージを合成した文字列を System.err.println します。
//	 *
//	 * これは、ogErrMsg( String ,Throwable ) の結果を、System.err.println に流しているだけの
//	 * 簡易メソッドです。
//	 *
//	 * @og.rev 6.3.8.5 (2015/10/16) 新規作成
//	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil にあったメソッドを移動するとともに廃止。
//	 *
//	 * @param    msg 合成したいメッセージ
//	 * @param    th  元のThrowableオブジェクト
//	 *
//	 * @see		#ogErrMsg( String ,Throwable )
//	 */
//	public static void ogErrMsgPrint( final String msg,final Throwable th ) {
//		System.err.println( ogErrMsg( msg,th ) );
//	}
}
