/* *******************************************************************
 * Copyright (c) 1999-2001 Xerox Corporation, 
 *               2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Common Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/cpl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 * ******************************************************************/

package org.aspectj.bridge;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.ArrayList;

/**
 * Signal that a process was aborted before completion.
 * This may contain a structured IMessage which indicates
 * why the process was aborted (e.g., the underlying exception).
 * For processes using try/catch to complete a method abruptly
 * but complete the process normally (e.g., a test failure
 * causes the test to abort but the reporting and testing continues 
 * normally), use the static methods to borrow and return a "porter"
 * to avoid the expense of constructing a stack trace each time.
 * A porter stack trace is invalid, and it should only be used
 * to convey a message.  E.g., to print the stack of the 
 * AbortException and any contained message:
 * <pre>catch (AbortException ae) {
 *     IMessage m = ae.getMessage();
 *     if (!ae.isPorter()) ae.printStackTrace(System.err);
 *     Throwable thrown = ae.getThrown();
 *     if (null != thrown) thrown.printStackTrace(System.err);
 * }</pre>
 */
public class AbortException extends RuntimeException { // XXX move porters out, handle proxy better

	private boolean isSilent = false;

    /** used when message text is null */
    public static final String NO_MESSAGE_TEXT 
        = "AbortException (no message)";
    
    private static final ArrayList porters = new ArrayList();
    
    /**
     * Get a porter exception from the pool.
     * Porter exceptions do <b>not</b> have valid stack traces.
     * They are used only to avoid generating stack traces when
     * using throw/catch to abruptly complete but continue.
     */
    public static AbortException borrowPorter(IMessage message) {
        AbortException result;
        synchronized(porters) {
            if (porters.size() > 0) {
                result = (AbortException) porters.get(0);    
            } else {
                result = new AbortException();
                result.setIsSilent(false);
            }
        }
        result.setIMessage(message);
        result.isPorter = true;
        return result;
    }    

    /**
     * Return (or add) a porter exception to the pool.
     */
    public static void returnPorter(AbortException porter) {
        synchronized(porters) {
            if (porters.contains(porter)) {
                throw new IllegalStateException("already have " + porter);
            } else {
                porters.add(porter);
            }
        }
    }

    /** @return NO_MESSAGE_TEXT or message.getMessage() if not null */
    private static String extractMessage(IMessage message) {
        if (null == message) {
            return NO_MESSAGE_TEXT;
        } else {
            String m = message.getMessage();
            if (null == m) {
                return NO_MESSAGE_TEXT;
            } else {
                return m;
            }            
        }
    }
    
    /** structured message abort */
    protected IMessage message;
    
    /** true if this is a porter exception - only used to hold message */
    protected boolean isPorter;

    /** abort with default String message */
    public AbortException() {
        this("ABORT");
        isSilent = true;
    }

    /** abort with message */
    public AbortException(String s) {
        super(null != s ? s : NO_MESSAGE_TEXT);
        this.message = null;
    }
    
    /** abort with structured message */
    public AbortException(IMessage message) {
        super(extractMessage(message));
        this.message = message;
    }

    /** @return IMessage structured message, if set */
    public IMessage getIMessage() {
        return message;
    }
    
    /** 
     * The stack trace of a porter is invalid; it is only used
     * to carry a message (which may itself have a wrapped exception).
     * @return true if this exception is only to carry exception 
     */
    public boolean isPorter() {
        return isPorter;
    }
    
    /** @return Throwable at bottom of IMessage chain, if any */
    public Throwable getThrown() {
        Throwable result = null;
        IMessage m = getIMessage();
        if (null != m) {
            result = m.getThrown();
            if (result instanceof AbortException) {
                return ((AbortException) result).getThrown();
            }
        }
        return result;
    }

    private void setIMessage(IMessage message) {
        this.message = message;
    }

    // ----------- proxy attempts
    /**
     * Get message for this AbortException,
     * either associated explicitly as message
     * or implicitly as IMessage message or 
     * its thrown message.
	 * @see java.lang.Throwable#getMessage()
	 */
	public String getMessage() {
        String message = super.getMessage();
        if ((null == message) || (NO_MESSAGE_TEXT == message)) {
            IMessage m = getIMessage();
            if (null != m) {
                message = m.getMessage();
                if (null == message) {
                    Throwable thrown = m.getThrown();
                    if (null != thrown) {
                        message = thrown.getMessage();   
                    }
                }
            }
            if (null == message) {
                message = NO_MESSAGE_TEXT; // better than nothing
            }
        }
        return message;
	}

    /**
	 * @see java.lang.Throwable#printStackTrace()
	 */
	public void printStackTrace() {
        printStackTrace(System.out);
	}

    /**
     * Print the stack trace of any enclosed thrown
     * or this otherwise.
	 * @see java.lang.Throwable#printStackTrace(PrintStream)
	 */
	public void printStackTrace(PrintStream s) {
        IMessage m = getIMessage();
        Throwable thrown = (null == m? null : m.getThrown());
        if (!isPorter() || (null == thrown)) {
            s.println("Message: " + m);
            super.printStackTrace(s);
        } else {
            thrown.printStackTrace(s);
        }            
	}

    /**
     * Print the stack trace of any enclosed thrown
     * or this otherwise.
     * @see java.lang.Throwable#printStackTrace(PrintWriter)
     */
    public void printStackTrace(PrintWriter s) {
        IMessage m = getIMessage();
        Throwable thrown = (null == m? null : m.getThrown());
        if (null == thrown) { // Always print 
            if (isPorter()) {
                s.println("(Warning porter AbortException without thrown)");
            }
            s.println("Message: " + m);
            super.printStackTrace(s);
        } else {
            thrown.printStackTrace(s);
        }            
    }

	public boolean isSilent() {
		return isSilent;
	}
	
	public void setIsSilent(boolean isSilent) {
		this.isSilent = isSilent;
	}

}
