/* File: Debug.java
 * Contains: Debugging tools
 * Author: Jeff Dalton <J.Dalton@ed.ac.uk>
 * Created: January 1998
 * Updated: Mon Nov 19 15:15:45 2001 by Jeff Dalton
 * Copyright: (c) 1998, AIAI, University of Edinburgh
 */

package ix.util;

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


/** Class for useful static debugging tools */

public abstract class Debug {

    private Debug() { }		// no instantiation


    /*
     * Simple debugging output
     */

    /** 
     * Global on/off control over the debugging output produced by
     * the note and noteln methods.
     */
    public static boolean on = true;

    /**
     * The output destination used by note and noteln.  Do not
     * assign directly to this variable.  Call setNoteStream or
     * setNoteFile instead.
     */
    public static PrintStream out = System.out;

    /**
     * note writes a string to Debug.out if Debug.on is true.
     * It is intetended to replace System.out.print calls, for
     * debugging output.<p>
     *
     * Unlike noteln, note does not print a newline after the message
     * and does not have variants that take different arguments.
     * The typical use of note is to use several calls to write a line
     * of output.  One potential problem with using note is that
     * a different thread may print some other output between those
     * calls.
     */
    public static void note(String message) {
	if (on) printNote(message);
    }

    /**
     * noteln writes a string, followed by a newline, to Debug.out
     * if Debug.on is true.  It is intended to replace System.out.println
     * calls, for debugging output.  noteln has variants that take more
     * than one argument.  The arguments will be printed as strings
     * with single spaces between.  The aim here is to avoid string
     * concatenation in the calls to noteln, so that the concatenation
     * will not occur when debugging output is turned off.
     */
    public static void noteln(String message) {
	if (on) printlnNote(message);
    }

    public static void noteln(String message, Object whatever) {
	if (on) printlnNote(message + " " + whatever);
    }

    // Some things are not objects, alas

    public static void noteln(String message, int i) {
	if (on) printlnNote(message + " " + i);
    }

    // The following messages are synchronized, to ensure that each call
    // to note or noteln completes before a new one starts, and to ensure
    // that Debug.out isn't changed during one of these calls.  The
    // stream classes may already provide enough synchronization for
    // the first case.

    private static synchronized void printNote(String message) {
	out.print(message);
	out.flush();
    }

    private static synchronized void printlnNote(String message) {
	out.println(message);
	// Do we need out.flush()?
    }


    /*
     * Debugging output redirection
     */

    /**
     * Sets the output destination for debugging notes.
     */
    public static synchronized void setNoteStream(PrintStream s) {
	out = s;
    }

    /**
     * Sets the output destination for debugging notes.
     */
    public static synchronized void setNoteFile(String filename) {
	try {
	    // First, a stream to the file.
	    FileOutputStream fout = new FileOutputStream(filename);
	    // Then a PrintStream with flush-on-newline = true.
	    out = new PrintStream(fout, true);
	}
	catch (IOException e) {
	    Debug.warn("Failed to setNoteFile to \"" + filename + "\"\n"
		       + "because " + e + "\n"
		       + "so switching to System.out.");
	    out = System.out;
	}
    }


    /*
     * Special-purpose debugging output routines
     */

    /**
     * Note an exception, together with a backtrace.  The note is always
     * printed to System.out or System.err, even if debug output has been
     * turned off or redirected to a file.
     */
    public static synchronized void noteException(Exception e) {
	noteException(e, true);
    }

    /**
     * Note an exception, optionally with a backtrace.  The note is always
     * printed to System.out or System.err, even if debug output has been
     * turned off or redirected to a file.
     */
    public static synchronized void noteException(Exception e,
						  boolean backtrace) {
	if (on) {
	    noteln("\nException:", e);
	    if (backtrace) e.printStackTrace(out);
	    noteln("");
	}
	if (!on || out != System.out) {
	    System.err.println("\nException: " + e);
	    if (backtrace) e.printStackTrace(System.err);
	    System.err.println("");
	}
    }

    /** 
     * Use this to tell the user about minor problems.  Warn prints
     * a message to System.err, followed by a backtrace for the current
     * thread.
     */
    public static void warn(String message) {
	System.err.println("\nWarning: " + message + "\n");
	Thread.dumpStack();
	System.err.println("");
    }

    /**
     * Use this to tell the user about problems that should not be
     * ignored and are not handled locally.
     *
     * @throws RuntimeException as notification of the problem.
     */
    public static void error(String message) {
	System.err.println("\nWarning: " + message + "\n");
	throw new RuntimeException(message);
    }


    /**
     * Numbers and prints the elements of an Enumeration on separate lines.
     */
    public static synchronized void noteEnumeration(Enumeration e) {
	Debug.note("[");
	for (int i = 0; e.hasMoreElements(); i++) {
	    Debug.note(i + ": " + e.nextElement() + "\n ");
	}
	Debug.noteln("]");
    }

    /**
     * Prints the elements of an Enumeration on separate lines, with
     * an index number and and class name at the start of each line.
     */
    public static synchronized void noteEnumerationClasses(Enumeration e) {
	Debug.note("[");
	for (int i = 0; e.hasMoreElements(); i++) {
	    Object elt = e.nextElement();
	    Debug.note(i + " " + elt.getClass() + ": " + elt + "\n ");
	}
	Debug.noteln("]");
    }

    /**
     * Prints the elements of an enumeration on separate lines with
     * a specified prefix at the start of each line.
     */
    public static synchronized void noteElements(Enumeration e,
						 String prefix) {
	while (e.hasMoreElements())
	    Debug.noteln(prefix, e.nextElement());

    }


    /*
     * Assertions
     */

    /** 
     * assert checks a condition that should always be true and
     * throws an AssertionFailure if it is not.  assert is typically
     * used when all of the following apply: the subsequent code
     * requires a condition to be true; in the absence of bugs,
     * the condition always will be true at that point; and it is
     * not obvious that the condition will be true.<p>
     *
     * AssertionFailure is a RuntimeException and so does not need to be
     * listed in the "throws" clauses of method definitions.  One reason
     * for that is to avoid discouraging the use of assertions.  If
     * AssertionFailure had to be declared, then adding an assertion
     * in a method that had none before would require nonlocal changes
     * in the code.
     *
     * @throws AssertionFailure as notification of the problem
     * @see AssertionFailure
     */
    public static void assert(boolean cond) {
	if (!cond) {
	    noteln("Assertion failed.");
	    throw new AssertionFailure();
	}
    }

    /**
     * A variant that allows a message that describes the assertion.
     * The message will be printed when the assertion fails and will
     * be included in the AssertionFailure exception.
     */
    public static void assert(boolean cond, String message) {
	if (!cond) {
	    noteln("Assertion failed:", message);
	    throw new AssertionFailure(message);
	}
    }

    /**
     * A variant that allows a message that describes the assertion
     * plus an Object that the message is about.
     */
    public static void assert(boolean cond, String message, Object item) {
	if (!cond) {
	    String itemMessage = message + " " + item;
	    noteln("Assertion failed:", itemMessage);
	    throw new AssertionFailure(itemMessage);
	}
    }


}

/* ---------------------------- Change History ----------------------------
 *  (Who)   (When)                   (What)
 *
 */
