/*******************************************************************************
 * Copyright (c) 2007, 2010 The original authors/Contributors listed below.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * 		Bruce Eckel - initial code
 *		Bruno Medeiros - modifications
 *******************************************************************************/
package melnorme.utilbox.core;


import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;

import java.io.IOException;


/**
 * Exception adapter to make checked exceptions less annoying. 
 * Based on Bruce Eckel's article:
 * http://www.mindview.net/Etc/Discussions/CheckedExceptions
 */
@SuppressWarnings("serial")
public class ExceptionAdapter extends RuntimeException {
	
	// Number of frames that originalException traveled while checked
	protected int checkedLength; 
	
	protected ExceptionAdapter(Exception e) {
		super(e);
		assertNotNull(e);
		
		// Determine checkedLength based on the difference to this stack trace
		StackTraceElement[] est = e.getStackTrace();
		checkedLength = est.length - getStackTrace().length;
		
		StackTraceElement ste = getStackTrace()[0];
		String firstMethod = ste.getClassName() +"."+ ste.getMethodName();
		// Adjust checkedLength if EA was created in method unchecked
		if(firstMethod.endsWith("ExceptionAdapter.unchecked")) {
			checkedLength++;
		}
	}
	
	
	protected void printStackTraceAppendable(Appendable pr) {
		synchronized(pr) {
			try {
				pr.append(this.toString());
				StackTraceElement[] trace = getCause().getStackTrace();
				for (int i=0; i < trace.length; i++) {
					pr.append("\tat " + trace[i]);
					if(i == checkedLength) {
						pr.append(" [UNCHECKED]");
					}
					pr.append("\n");
				}
			} catch (IOException e) {
				assertFail();
			}
		}
	}
	
	@Override
	public void printStackTrace(java.io.PrintStream ps) {		
		printStackTraceAppendable(ps);
	}
	
	@Override
	public void printStackTrace(java.io.PrintWriter pw) {
		printStackTraceAppendable(pw);
	}
	
	@Override
	public Exception getCause() {
		return (Exception) super.getCause();
	}
	
	public void rethrow() throws Exception {
		throw getCause();
	}
	
	@Override
	public String toString() {
		String className = getClass().getSimpleName();
		return "["+className+"] " + getLocalizedMessage() + "\n";
	}
	
	/** Creates an unchecked Throwable, if not unchecked already. */
	public static final RuntimeException unchecked(Throwable e) {
		if(e instanceof RuntimeException) {
			throw (RuntimeException) e;
		} else if(e instanceof Exception) {
			throw new ExceptionAdapter((Exception) e);
		} else if(e instanceof Error) {
			throw (Error) e;
		} else {
			assertFail("uncheck: Unsupported Throwable: " + e);
			return null;
		}
	}
	
	/** Same as {@link #unchecked(Throwable)} but used during development for temporary code only. 
	 * Uses the Deprecated annotation to cause a warning in IDEs, to warn that code should be reviewed. */
	@Deprecated 
	public static RuntimeException uncheckedTODO(Throwable e) {
		return unchecked(e);
	}
}
