/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.core.lang;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;


/**
 * utiltiy class for exception
 */
public abstract class Exceptions {
	public static RuntimeException unsupported() {
		throw new UnsupportedOperationException();
	}
	
	public static RuntimeException unsupported(String msg) {
		throw new UnsupportedOperationException(msg);
	}
	
	/**
	 * 用运行时异常包裹抛出对象，如果抛出对象本身就是运行时异常，则直接返回。
	 * <p>
	 * 如果是 InvocationTargetException，那么将其剥离，只包裹其 TargetException
	 * 
	 * @param e 抛出对象
	 * @return 运行时异常
	 */
	public static RuntimeException wrapThrow(Throwable e) {
		if (e instanceof RuntimeException)
			return (RuntimeException)e;
		if (e instanceof InvocationTargetException)
			return wrapThrow(((InvocationTargetException)e).getTargetException());
		return new RuntimeException(e);
	}

	/**
	 * 将抛出对象包裹成运行时异常，并增加自己的描述
	 * 
	 * @param e 抛出对象
	 * @param fmt 格式
	 * @param args 参数
	 * @return 运行时异常
	 */
	public static RuntimeException wrapThrow(Throwable e, String fmt, Object... args) {
		return new RuntimeException(String.format(fmt, args), e);
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Gets the stack trace from a Throwable as a String.
	 * </p>
	 * <p>
	 * The result of this method vary by JDK version as this method uses
	 * {@link Throwable#printStackTrace(java.io.PrintWriter)}. On JDK1.3 and earlier, the cause
	 * exception will not be shown unless the specified throwable alters printStackTrace.
	 * </p>
	 * 
	 * @param throwable the <code>Throwable</code> to be examined
	 * @return the stack trace as generated by the exception's
	 *         <code>printStackTrace(PrintWriter)</code> method
	 */
	public static String getStackTrace(Throwable throwable) {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw, true);
		throwable.printStackTrace(pw);
		return sw.getBuffer().toString();
	}

	/**
	 * <p>
	 * Captures the stack trace associated with the specified <code>Throwable</code> object,
	 * decomposing it into a list of stack frames.
	 * </p>
	 * <p>
	 * The result of this method vary by JDK version as this method uses
	 * {@link Throwable#printStackTrace(java.io.PrintWriter)}. On JDK1.3 and earlier, the cause
	 * exception will not be shown unless the specified throwable alters printStackTrace.
	 * </p>
	 * 
	 * @param throwable the <code>Throwable</code> to examine, may be null
	 * @return an array of strings describing each stack frame, never null
	 */
	public static String[] getStackFrames(Throwable throwable) {
		if (throwable == null) {
			return Arrays.EMPTY_STRING_ARRAY;
		}
		return getStackFrames(getStackTrace(throwable));
	}

	// -----------------------------------------------------------------------
	/**
	 * <p>
	 * Returns an array where each element is a line from the argument.
	 * </p>
	 * <p>
	 * The end of line is determined by the value of {@link Systems#LINE_SEPARATOR}.
	 * </p>
	 * 
	 * @param stackTrace a stack trace String
	 * @return an array where each element is a line from the argument
	 */
	static String[] getStackFrames(String stackTrace) {
		String linebreak = Systems.LINE_SEPARATOR;
		StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
		List<String> list = new ArrayList<String>();
		while (frames.hasMoreTokens()) {
			list.add(frames.nextToken());
		}
		return list.toArray(new String[list.size()]);
	}

	/**
	 * <p>
	 * Produces a <code>List</code> of stack frames - the message is not included. Only the trace of
	 * the specified exception is returned, any caused by trace is stripped.
	 * </p>
	 * <p>
	 * This works in most cases - it will only fail if the exception message contains a line that
	 * starts with: <code>&quot;&nbsp;&nbsp;&nbsp;at&quot;.</code>
	 * </p>
	 * 
	 * @param t is any throwable
	 * @return List of stack frames
	 */
	static List<String> getStackFrameList(Throwable t) {
		String stackTrace = getStackTrace(t);
		String linebreak = Systems.LINE_SEPARATOR;
		StringTokenizer frames = new StringTokenizer(stackTrace, linebreak);
		List<String> list = new ArrayList<String>();
		boolean traceStarted = false;
		while (frames.hasMoreTokens()) {
			String token = frames.nextToken();
			// Determine if the line starts with <whitespace>at
			int at = token.indexOf("at");
			if (at != -1 && token.substring(0, at).trim().length() == 0) {
				traceStarted = true;
				list.add(token);
			}
			else if (traceStarted) {
				break;
			}
		}
		return list;
	}

	// -----------------------------------------------------------------------
	/**
	 * Gets a short message summarising the exception.
	 * <p>
	 * The message returned is of the form {ClassNameWithoutPackage}: {ThrowableMessage}
	 * 
	 * @param th the throwable to get a message for, null returns empty string
	 * @return the message, non-null
	 */
	public static String getMessage(Throwable th) {
		if (th == null) {
			return "";
		}
		String clsName = Classes.getShortClassName(th, null);
		String msg = th.getMessage();
		return clsName + ": " + Strings.defaultString(msg);
	}
}
