/*
 * BgErrorMgr --
 *
 *	This class manages the background errors for a Tcl interpreter.
 *
 * Copyright (c) 1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL
 * WARRANTIES.
 * 
 * RCS: @(#) $Id: BgErrorMgr.java,v 1.1.1.1 1998/10/14 21:09:19 cvsadmin Exp $
 *
 */

package tcl.lang;

import java.util.*;
import java.io.*;

/*
 * This class manages the background errors for a Tcl interpreter. It
 * stores the error information about the interpreter and use an idle
 * handler to report the error when the notifier is idle.
 */

class BgErrorMgr implements AssocData {

/*
 * We manage the background errors in this interp instance.
 */

Interp interp;

/*
 * A TclObject for invoking the "bgerror" command. We use a TclObject
 * instead of a String so that we don't need to look up the command
 * every time.
 */

TclObject bgerrorCmdObj;

/*
 * A list of the pending background error handlers.
 */

Vector errors = new Vector();


/*
 *----------------------------------------------------------------------
 *
 * BgErrorMgr --
 *
 *	Constructs a new BgErrorMgr instance.
 *
 * Side effects:
 *	Member fields are initialized.
 *
 *----------------------------------------------------------------------
 */

BgErrorMgr(
    Interp i)			// Manage the background errors in this interp.
{
    interp = i;
    bgerrorCmdObj = TclString.newInstance("bgerror");
    bgerrorCmdObj.preserve();

    errors = new Vector();
}

/*
 *----------------------------------------------------------------------
 *
 * addBgError --
 *
 *	Adds one background error to the list of pending background
 *	errors. The errors will be reported later as idle events.
 *
 *	The background errors will be reported in the order they were
 *	added by the addBgError() call. This order is guaranteed by the
 *	constructor IdleHandler.IdleHandler(Notifier);
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The command "bgerror" is invoked later as an idle handler to
 *	process the error, passing it the error message.  If that fails,
 *	then an error message is output on stderr.
 *
 *----------------------------------------------------------------------
 */

void
addBgError()
{
    BgError bgErr = new BgError(interp.getNotifier());

    try {

	/*
	 * The addErrorInfo() call below (with an empty string)
	 * ensures that errorInfo gets properly set.  It's needed in
	 * cases where the error came from a utility procedure like
	 * Interp.getVar() instead of Interp.eval(); in these cases
	 * errorInfo still won't have been set when this procedure is
	 * called.
	 */

	interp.addErrorInfo("");

	bgErr.errorMsg  = interp.getResult();
	bgErr.errorInfo = interp.getVar("errorInfo", null, 
	    	TCL.GLOBAL_ONLY|TCL.DONT_THROW_EXCEPTION);
	bgErr.errorCode = interp.getVar("errorCode", null, 
	    	TCL.GLOBAL_ONLY|TCL.DONT_THROW_EXCEPTION);

	bgErr.errorMsg.preserve();
	bgErr.errorInfo.preserve();
	bgErr.errorCode.preserve();
    } catch (TclException e) {
	System.out.println(interp.getResult());
	e.printStackTrace();
	throw new TclRuntimeError("unexpected TclException: " + e);
    }

    errors.addElement(bgErr);
}

/*
 *----------------------------------------------------------------------
 *
 * disposeAssocData --
 *
 *	This method is called when the interpreter is destroyed or
 *	when Interp.deleteAssocData is called on a registered
 *	AssocData instance.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Removes any bgerror's that haven't been reported.
 *
 *----------------------------------------------------------------------
 */

public void
disposeAssocData(
    Interp interp)		// The interpreter in which this AssocData
				// instance is registered in.
{
    for (int i = errors.size() - 1; i >= 0; i--) {
	BgError bgErr = (BgError)errors.elementAt(i);
	errors.removeElementAt(i);
	bgErr.cancel();

	bgErr.errorMsg.release();
	bgErr.errorMsg = null;
	bgErr.errorInfo.release();
	bgErr.errorInfo = null;
	bgErr.errorCode.release();
	bgErr.errorCode = null;
    }

    bgerrorCmdObj.release();
    bgerrorCmdObj = null;
}

/*
 * This inner class implements an idle handler which reports one
 * background error.
 */

class BgError extends IdleHandler {

/*
 * The interp's result, errorCode and errorInfo when the bgerror happened.
 */

TclObject errorMsg;
TclObject errorCode;
TclObject errorInfo;


/*
 *----------------------------------------------------------------------
 *
 * BgError --
 *
 *	Constructs a new BgError instance.
 *
 * Side effects:
 *	The idle handler is initialized by the super class's
 *	constructor.
 *
 *----------------------------------------------------------------------
 */

BgError(
    Notifier n)			// The notifier to fire the event.
{
    super(n);
}

/*
 *----------------------------------------------------------------------
 *
 * processIdleEvent --
 *
 *	Process the idle event.
 *
 * Results:	
 *	None.
 *
 * Side effects:
 *	The bgerror command is executed. It may have arbitrary side
 *	effects.
 *
 *----------------------------------------------------------------------
 */

public void
processIdleEvent()
{
    /*
     * During the execution of this method, elements may be removed from
     * the errors list (because a TCL.BREAK was returned by the bgerror
     * command, or because the interp was deleted). We remove this
     * BgError instance from the list first so that this instance won't
     * be deleted twice.
     */

    errors.removeElement(this);

    /*
     * Restore important state variables to what they were at
     * the time the error occurred.
     */

    try {
	interp.setVar("errorInfo", null, errorInfo, TCL.GLOBAL_ONLY);
    } catch (TclException e) {
	/*
	 * Ignore any TclException's, possibly caused by variable traces on
	 * the errorInfo variable. This is compatible with the behavior of
	 * the Tcl C API.
	 */
    }

    try {
	interp.setVar("errorCode", null, errorCode, TCL.GLOBAL_ONLY);
    } catch (TclException e) {
	/*
	/*
	 * Ignore any TclException's, possibly caused by variable traces on
	 * the errorCode variable. This is compatible with the behavior of
	 * the Tcl C API.
	 */
    }

    try {
	/*
	 * Invoke the bgerror command.
	 */

	TclObject argv[] = new TclObject[2];
	argv[0] = bgerrorCmdObj;
	argv[1] = errorMsg;

	Parser.evalObjv(interp, argv, 0, Parser.TCL_EVAL_GLOBAL);
    } catch (TclException e) {
	switch (e.getCompletionCode()) {
	case TCL.ERROR:
	    try {
		Channel chan = TclIO.getStdChannel(StdChannel.STDERR);
		if (interp.getResult().toString().equals(
        "\"bgerror\" is an invalid command name or ambiguous abbreviation")) {
		    chan.write(interp, errorInfo.toString());
		    chan.write(interp, "\n");
                } else {
		    chan.write(interp,
                            "bgerror failed to handle background error.\n");
                    chan.write(interp, "    Original error: ");
                    chan.write(interp, errorMsg.toString());
                    chan.write(interp, "\n");
                    chan.write(interp, "    Error in bgerror: ");
                    chan.write(interp, interp.getResult().toString());
                    chan.write(interp, "\n");
                }
                chan.flush(interp);
            } catch (TclException e1) {
		/*
		 * Ignore.
		 */
	    } catch (IOException e2) {
		/*
		 * Ignore.
		 */
	    }
	    break;

	case TCL.BREAK:
	    /*
	     * Break means cancel any remaining error reports for this
	     * interpreter.
	     */

	    for (int i = errors.size() - 1; i >=0; i--) {
		BgError bgErr = (BgError)errors.elementAt(i);
		errors.removeElementAt(i);
		bgErr.cancel();

		bgErr.errorMsg.release();
		bgErr.errorMsg = null;
		bgErr.errorInfo.release();
		bgErr.errorInfo = null;
		bgErr.errorCode.release();
		bgErr.errorCode = null;
	    }
	    break;
	}
    }

    errorMsg.release();
    errorMsg = null;
    errorInfo.release();
    errorInfo = null;
    errorCode.release();
    errorCode = null;
}

} // end BgErrorMgr.BgError

} // end BgErrorMgr
