/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (the License).  You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the license at 
 * https://glassfish.dev.java.net/public/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */
package com.sun.enterprise.util;

import java.util.StringTokenizer;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.PrintStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.io.PrintWriter;

/*
** Printing/Debug Utilities
**
** This Module contains replacements for various types of print statements used for
** debugging purposes.  
** Example:
**   Replace this
**     System.out.println("test message");
**   With this
**     Print.println("test message");
**       or
**     Print.dprintln("test message");
** The advantage of using Print.println is that the message ("test message") can be 
** pre-pended with the actual location of the print statement itself.  For instance,
** Issuing a call to 'Print.dprintln("hello world")' may print a console message
** similar to the following:
**   [MyCustomClass.doSomething:291] hello world
** This indicates that the 'Print.dprintln' is located in the method 'doSomething'
** in the class 'MyCustomClass' at source file line number 291.  This will help
** show the precise location of various debug message print statements that should
** be removed prior to FCS. (Note: the package is remove from the class before
** printing the stack frame to avoid creating a print line which is excessively long)
**
** Difference between 'println' and 'dprintln':
** Each 'printXXX' method in this module has a corresponding 'dprintXXX' method.
** The 'dprintXXX' methods will only print their specified message if the System
** property "ri.debugMode" is true.  Additionally, 'dprintXXX' methods will always
** prepend the location (stackframe) of the 'Print.dprintXXX' call.
** The 'printXXX' methods always print their specified message to the console.
** However, the default is to _not_ include the location (stackframe) of the print 
** statement unless the System property "ri.print.showStackFrame" is true.  The 
** location of all print statements can then be exposed by setting this system 
** property to true.
**
** Replacements:
** Old method				  New Method
**  System.out.println("hello")	    	=> Print.println("hello")
**  <Throwable>.printStackTrace()   	=> Print.printStackTrace("Error", <Throwable>)
**  Thread.dumpStack()		    	=> Print.printStackTrace("Stack Trace");
**  if (debug) System.out.println("x")	=> Print.dprintln("x");  // not optimized
**  if (debug) System.out.println("x")	=> if (debug) Print.println("x");
**
** To turn on debug mode (to allow 'dprintXXX' to print), include system property:
**   -Dri.debugMode=true
**
** To turn have 'printXXX' methods print the stack frame location, include system property:
**   -Dri.print.showStackFrame=true
**
** -----------------------------------------------------------------------------
** @author Martin D. Flynn
*/

public class Print 
{

    /* -------------------------------------------------------------------------
    */

    /* System property keys */
    public static final String SYSTEM_DEBUG_MODE  = "ri.debugMode";
    public static final String SYSTEM_STACK_FRAME = "ri.print.showStackFrame";

    /* Throwable instance used to obtain stack information */
    private static Throwable stackTrace = null;

    /* by default the source file name is not printed in the location */
    private static boolean showStackFrameSource = false;

    /* -------------------------------------------------------------------------
    */

    /* Set the debug mode
    ** @param specified debug state
    */
    public static void setDebugMode(boolean state)
    {
	System.setProperty(SYSTEM_DEBUG_MODE, String.valueOf(state));
    }

    /* Return the current debug mode state
    ** @return debug state
    */
    public static boolean isDebugMode()
    {
	return Boolean.getBoolean(SYSTEM_DEBUG_MODE);
    }

    /* -------------------------------------------------------------------------
    */

    /* Set the state to force prepending the stack frame to 'print' statements
    ** @param specified printStackFrame state
    */
    public static void setPrintStackFrame(boolean state)
    {
	System.setProperty(SYSTEM_STACK_FRAME, String.valueOf(state));
    }

    /* return the current state of including the stack frame in 'print' statements
    ** @return printStackFrame state
    */
    public static boolean printStackFrame()
    {
	return Boolean.getBoolean(SYSTEM_STACK_FRAME);
    }

    /* -------------------------------------------------------------------------
    */

    /* Set the state to include the module source file rather than the normal stack frame
    ** @param specified showStackFrameSource state
    */
    public static void setShowStackFrameSource(boolean flag)
    {
	showStackFrameSource = flag;
    }

    /* -------------------------------------------------------------------------
    */

    /* Return the specified StackTraceElement
    ** @return the @see java.lang.StackTraceElement for the specified stack frame
    ** @param the specified stack frame to retrieve.
    */
    public static StackTraceElement getStackFrame(int frameNum)
    {
	if (stackTrace == null) { stackTrace = new Throwable(); }
	stackTrace.fillInStackTrace();
	return stackTrace.getStackTrace()[frameNum + 1];
    }

    /* Return the String form of the current StackTraceElement
    ** @return the @see java.lang.String version of the specified stack frame
    ** @param the specified stack frame to retrieve.
    */
    public static String getStackFrameString(int frameNum)
    {
	return getStackFrameString(frameNum + 1, showStackFrameSource);
    }

    /* Return the String form of the current StackTraceElement
    ** @return the @see java.lang.String version of the specified stack frame
    ** @param the StackTraceElement to convert to a string.
    */
    public static String getStackFrameString(StackTraceElement stackFrame)
    {
	return getStackFrameString(stackFrame, showStackFrameSource);
    }

    /* Return the String form of the current StackTraceElement
    ** @return the @see java.lang.String version of the specified stack frame
    ** @param the specified stack frame to retrieve.
    ** @param indicator whether the String should include the module source file name
    */
    public static String getStackFrameString(int frameNum, boolean showSrc)
    {
	return getStackFrameString(getStackFrame(frameNum + 1), showSrc);
    }

    /* Return the String form of the current StackTraceElement
    ** @return the @see java.lang.String version of the specified stack frame
    ** @param the StackTraceElement to convert to a string.
    ** @param indicator whether the String should include the module source file name
    */
    public static String getStackFrameString(StackTraceElement stackFrame, boolean showSrc)
    {
	StringBuffer sb = new StringBuffer();
	if (showSrc) {
	    String s = stackFrame.getFileName();
	    if (s != null) {
		int p = s.lastIndexOf(File.separator);
	        sb.append((p >= 0)? s.substring(p + 1) : s);
	    } else {
	        String c = stackFrame.getClassName();
	        int p = c.lastIndexOf(".");
	        sb.append("<").append((p >= 0)? c.substring(p + 1) : c).append(">");
	    }
	} else {
	    String c = stackFrame.getClassName();
	    int p = c.lastIndexOf(".");
	    sb.append((p >= 0)? c.substring(p + 1) : c);
	}
	sb.append(".").append(stackFrame.getMethodName());
	if (stackFrame.getLineNumber() >= 0) {
	    sb.append(":").append(stackFrame.getLineNumber());
	}
	return sb.toString();
    }

    /* -------------------------------------------------------------------------
    */

    /* Internal print statement
    ** @param output @see java.io.PrintStream
    ** @param the stack frame location of the real 'print' statement.
    ** @param the message to print.
    */
    protected static void _println(PrintStream out, int frameNum, String msg)
    {
	String m = (frameNum >= 0)? "[" + getStackFrameString(frameNum + 1) + "] " + msg : msg;
	print(out, m + "\n");
    }

    /* -------------------------------------------------------------------------
    */

    /* Print message to specified PrintStream (default to System.out)
    ** Does not include a linefeed at the end of the message.
    ** @param output @see java.io.PrintStream
    ** @param the message to print.
    */
    public static void print(PrintStream out, String msg)
    {
	((out != null)? out : System.out).print(msg);
    }

    /* Print message to specified PrintStream (default to System.out)
    ** Prints message only if debugMode system property is true.
    ** Does not include a linefeed at the end of the message.
    ** @param output @see java.io.PrintStream
    ** @param the message to print.
    */
    public static void dprint(PrintStream out, String msg)
    {
	if (isDebugMode()) {
	    print(out, msg);
	}
    }

    /* Print message to specified System.out
    ** Does not include a linefeed at the end of the message.
    ** @param the message to print.
    */
    public static void print(String msg)
    {
	print(null, msg);
    }

    /* Print message to specified System.out
    ** Prints message only if debugMode system property is true.
    ** Does not include a linefeed at the end of the message.
    ** @param the message to print.
    */
    public static void dprint(String msg)
    {
	if (isDebugMode()) {
	    print(null, msg);
	}
    }

    /* Print message to specified PrintStream (default to System.out)
    ** StackFrame information is included if the specified frame is >= 0, or if
    ** the showStackFrame system property is true.  To force the stack frame info to
    ** print, set the frameNum to '0' [eg.' Print.println(null, 0, "This location");']
    ** Message is terminated with a linefeed
    ** @param output @see java.io.PrintStream
    ** @param the stack frame location of the real 'print' statement.
    ** @param the message to print.
    */
    public static void println(PrintStream out, int frameNum, String msg)
    {
	int f = (frameNum >= 0)? (frameNum + 1) : (printStackFrame()? Math.abs(frameNum) : -1);
	_println(out, f, msg);
    }

    /* Print message to specified PrintStream (default to System.out)
    ** Prints message only if debugMode system property is true.
    ** StackFrame information is included.
    ** Message is terminated with a linefeed
    ** @param output @see java.io.PrintStream
    ** @param the stack frame location of the real 'print' statement.
    ** @param the message to print.
    */
    public static void dprintln(PrintStream out, int frameNum, String msg)
    {
	if (isDebugMode()) {
	    int f = (frameNum >= 0)? (frameNum + 1) : Math.abs(frameNum);
	    _println(out, f, msg);
	}
    }

    /* Print message to System.out
    ** StackFrame information is included if the specified frame is >= 0, or if
    ** the showStackFrame system property is true.  To force the stack frame info to
    ** print, set the frameNum to '0' [eg.' Print.println(0, "This location");']
    ** Message is terminated with a linefeed
    ** @param the stack frame location of the real 'print' statement.
    ** @param the message to print.
    */
    public static void println(int frameNum, String msg)
    {
	int f = (frameNum >= 0)? (frameNum + 1) : (printStackFrame()? Math.abs(frameNum) : -1);
	_println(null, f, msg);
    }

    /* Print message to System.out
    ** Prints message only if debugMode system property is true.
    ** StackFrame information is included.
    ** Message is terminated with a linefeed
    ** @param the stack frame location of the real 'print' statement.
    ** @param the message to print.
    */
    public static void dprintln(int frameNum, String msg)
    {
	if (isDebugMode()) {
	    int f = (frameNum >= 0)? (frameNum + 1) : Math.abs(frameNum);
	    _println(null, f, msg);
	}
    }

    /* Print message to specified PrintStream (default to System.out)
    ** StackFrame information is included if the showStackFrame system property is true.
    ** Message is terminated with a linefeed
    ** @param output @see java.io.PrintStream
    ** @param the message to print.
    */
    public static void println(PrintStream out, String msg)
    {
	_println(out, (printStackFrame()? 1 : -1), msg);
    }

    /* Print message to specified PrintStream (default to System.out)
    ** Prints message only if debugMode system property is true.
    ** StackFrame information is included.
    ** Message is terminated with a linefeed
    ** @param output @see java.io.PrintStream
    ** @param the message to print.
    */
    public static void dprintln(PrintStream out, String msg)
    {
	if (isDebugMode()) {
	    _println(out, 1, msg);
	}
    }

    /* Print message to System.out
    ** StackFrame information is included if the showStackFrame system property is true.
    ** Message is terminated with a linefeed
    ** @param the message to print.
    */
    public static void println(String msg)
    {
	_println(null, (printStackFrame()? 1 : -1), msg);
    }

    /* Print message to System.out
    ** Prints message only if debugMode system property is true.
    ** StackFrame information is included.
    ** Message is terminated with a linefeed
    ** @param the message to print.
    */
    public static void dprintln(String msg)
    {
	if (isDebugMode()) {
	    _println(null, 1, msg);
	}
    }

    /* -------------------------------------------------------------------------
    */

    private static PrintStream printStackTrace_Stream   = null;
    private static File        printStackTrace_LogFile  = null;

    /* Set default output file for logging stack traces (logging defaults to System.out)
    ** @param the output file to log stack traces
    */
    public static void setStackTraceLogFile(File logFile)
    {

	/* close old log file */
	if (printStackTrace_Stream != null) {
	    printStackTrace_Stream.close();
	    printStackTrace_Stream = null;
	    printStackTrace_LogFile = null;
	}

	/* open new log file */
	if ((logFile != null) && logFile.isAbsolute()) {
	    try {
	        printStackTrace_Stream = new PrintStream(new FileOutputStream(logFile), true);
		printStackTrace_LogFile = logFile;
	    } catch (IOException ioe) {
		Print.println(0, "Unable to open StackTrace log file: " + logFile);
	    }
	}

    }

    /* Format and print stack trace information
    ** @param output @see java.io.PrintStream
    ** @param the stack frame location of the real 'print' statement.
    ** @param the title of the stack trace.
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    private static void _printStackTrace(PrintStream out, int frame, String title, String msg, Throwable excp)
    {

	/* header vars */
	final String _dash = "----------------------------------------"; // 40
	final String dashLine = _dash + _dash + "\n"; // 80
	final String _ttl = " " + title + " ";
	final String errTitle = dashLine.substring(0, 16) + _ttl + dashLine.substring(16 + _ttl.length());

	/* default PrintStream */
	if (out == null) {
	    out = System.out;
	}

	/* header */
	Print.print(out, "\n");
	Print.print(out, dashLine + errTitle);
	Print.print(out, "[" + Print.getStackFrameString(frame + 1) + "]\n");
	if ((msg != null) && !msg.equals("")) { Print.print(out, msg + "\n"); }

	/* stack trace */
	if (excp != null) {
	    Print.print(out, excp.toString() + "\n");
	    Print.print(out, dashLine);
	    excp.printStackTrace(out);
	} else {
	    Print.print(out, dashLine);
	    Print.print(out, "Stack Trace:\n");
	    Throwable t = new Throwable();
	    t.fillInStackTrace();
	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
	    PrintStream ps = new PrintStream(bos);
	    t.printStackTrace(ps);
	    StringTokenizer st = new StringTokenizer(bos.toString(), "\n");
	    st.nextToken(); // "java.lang.Throwable"
	    for (int i = 0; i < frame + 1; i++) { st.nextToken(); } // discard superfluous stack frames
	    for (;st.hasMoreTokens();) { Print.println(out, st.nextToken()); }
	}

	/* final dashed line */
	Print.print(out, dashLine);
	Print.print(out, "\n");

    }

    /* Print stack trace information to specified PrintStream.
    ** @param output @see java.io.PrintStream
    ** @param the stack frame location of the real 'print' statement.
    ** @param the title of the stack trace.
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    public static void printStackTrace(PrintStream out, int frame, String title, String msg, Throwable excp)
    {
	if (out == null) { out = printStackTrace_Stream; }
	if ((out != null) && (out != System.out) && (out != System.err)) {
	    Print.print("\n");
	    if (excp != null) {
	        String m = ((msg != null) && !msg.equals(""))? (msg + " - ") : "";
		Print.println(null, frame + 1, "Error: " + m + excp + "");
		StackTraceElement ste[] = excp.getStackTrace();
		if (ste.length > 0) {
	            Print.print("  ==> at " + getStackFrameString(ste[0]) + "\n");
		}
	    } else {
	        Print.println(null, frame + 1, msg);
	    }
	    if (printStackTrace_LogFile != null) {
	        Print.print("(Stack trace logged to '" + printStackTrace_LogFile + "')\n");
	    }
	    Print.print("\n");
	}
	_printStackTrace(out, frame + 1, title, msg, excp);
    }

    /* Print stack trace information to specified PrintStream.
    ** Prints stack trace only if debugMode system property is true.
    ** @param output @see java.io.PrintStream
    ** @param the stack frame location of the real 'print' statement.
    ** @param the title of the stack trace.
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    public static void dprintStackTrace(PrintStream out, int frame, String title, String msg, Throwable excp)
    {
	if (isDebugMode()) {
	    if (out == null) { out = printStackTrace_Stream; }
	    if ((out != null) && (out != System.out) && (out != System.err)) {
	        String m = (excp != null)? (msg + " (" + excp + ")") : msg;
	        Print.println(null, frame + 1, "{log} " + m);
	    }
	    _printStackTrace(out, frame + 1, title, msg, excp); 
	}
    }

    /* Print stack trace information to default PrintStream.
    ** @param the title of the stack trace.
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    public static void printStackTrace(String title, String msg, Throwable excp)
    {
	printStackTrace(null, 1, title, msg, excp);
    }

    /* Print stack trace information to default PrintStream.
    ** Prints stack trace only if debugMode system property is true.
    ** @param the title of the stack trace.
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    public static void dprintStackTrace(String title, String msg, Throwable excp)
    {
	if (isDebugMode()) {
	    dprintStackTrace(null, 1, title, msg, excp);
	}
    }

    /* Print stack trace information to default PrintStream.
    ** Use this method instead of '<Throwable>.printStackTrace'
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    public static void printStackTrace(String msg, Throwable excp)
    {
	String title = (excp != null)? "Exception" : "StackTrace";
	printStackTrace(null, 1, title, msg, excp);
    }

    /* Print stack trace information to default PrintStream.
    ** Prints stack trace only if debugMode system property is true.
    ** Use this method instead of '<Throwable>.printStackTrace'
    ** @param the message to print.
    ** @param the Throwable containing the stack trace to print.
    */
    public static void dprintStackTrace(String msg, Throwable excp)
    {
	if (isDebugMode()) {
	    String title = (excp != null)? "(DEBUG) Exception" : "(DEBUG) StackTrace";
	    dprintStackTrace(null, 1, title, msg, excp);
	}
    }

    /* Print stack trace information to default PrintStream.
    ** Use this method instead of 'Thread.dumpStack'
    ** @param the message to print.
    */
    public static void printStackTrace(String msg)
    {
	printStackTrace(null, 1, "StackTrace", msg, null);
    }

    /* Print stack trace information to default PrintStream.
    ** Prints stack trace only if debugMode system property is true.
    ** Use this method instead of 'Thread.dumpStack'
    ** @param the message to print.
    */
    public static void dprintStackTrace(String msg)
    {
	if (isDebugMode()) {
	    dprintStackTrace(null, 1, "(DEBUG) StackTrace", msg, null);
	}
    }

    /* Print stack trace information to default PrintStream.
    ** Allows specifying frame
    ** @param the stack frame to print.
    ** @param the message to print.
    */
    public static void printStackTrace(int frame, String msg)
    {
	printStackTrace(null, frame + 1, "StackTrace", msg, null);
    }

    /* Print stack trace information to default PrintStream.
    ** Prints stack trace only if debugMode system property is true.
    ** Allows specifying frame
    ** @param the stack frame to print.
    ** @param the message to print.
    */
    public static void dprintStackTrace(int frame, String msg)
    {
	if (isDebugMode()) {
	    dprintStackTrace(null, frame + 1, "(DEBUG) StackTrace", msg, null);
	}
    }

    public static String printStackTraceToString()
    {
        Throwable t = new Throwable("printStackTraceToString");
        t.fillInStackTrace();

        StringWriter stringWriter = new StringWriter();
        PrintWriter printWriter = new PrintWriter(stringWriter);
        t.printStackTrace(printWriter);
        String stackTrace = stringWriter.toString();

        printWriter.close();

        return stackTrace;
    }
}
