FileDocCategorySizeDatePackage
ErrorHandler.javaAPI DocExample8857Sat Jan 24 10:44:34 GMT 2004je3.gui

ErrorHandler.java

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.com/javaexamples3.
 */
package je3.gui;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.net.*;
import java.io.*;

/**
 * This class defines three static methods useful for coping with unrecoverable
 * exceptions and errors.  
 * 
 * getHTMLDetails() returns an HTML-formatted stack trace suitable for display.
 *
 * reportThrowable() serializes the exception and POSTs it to a web server
 * through a java.net.URLConnection.
 *
 * displayThrowable() displays the exception's message in a dialog box that
 *   includes buttons that invoke getHTMLDetails() and reportThrowable().
 *
 * This example demonstrates: StackTraceElement, chained exceptions, 
 * Swing dialogs with JOptionPane, object serialization and URLConnection.
 */
public class ErrorHandler {
    /**
     * Display details about throwable in a simple modal dialog.  Title is the
     * title of the dialog box.  If submissionURL is not null, allow the user
     * to report the exception to that URL.  Component is the "owner" of the
     * dialog, and may be null for non-graphical applications.
     **/
    public static void displayThrowable(final Throwable throwable,
					String title,
					final String submissionURL,
					Component component)
    {
	// Get throwable class name minus the package name
	String className = throwable.getClass().getName();
	className = className.substring(className.lastIndexOf('.')+1);

	// Basic error message is className plus exception message if any.
	String msg = throwable.getMessage();
	final String basicMessage = className + ((msg != null)?(": "+msg):"");

	// Here is a JLabel to display the message.  We create the component
	// explicitly so we can manipulate it with the buttons.  Note final
	final JLabel messageLabel = new JLabel(basicMessage);

	// Here are buttons for the dialog.  They are final for use in 
	// the event listeners below.  The "Send Report" button is only
	// enabled if we have a URL to send a bug report to.
	final JButton detailsButton = new JButton("Show Details");
	final JButton reportButton = new JButton("Send Report");
	reportButton.setEnabled(submissionURL != null);

	// Our dialog will display a JOptionPane.  Note that we don't 
	// have to create the "Exit" button ourselves.  JOptionPane will
	// create it for us, and will cause it to close the dialog.
	JOptionPane pane =
	    new JOptionPane(messageLabel, JOptionPane.ERROR_MESSAGE,
			    JOptionPane.YES_NO_OPTION, null,
			    new Object[] {detailsButton,reportButton,"Exit"});

	// This is the dialog box containing the pane.
	final JDialog dialog = pane.createDialog(component, title);

	// Add an event handler for the Details button
	detailsButton.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    // Show or hide error details based on state of button.
		    String label = detailsButton.getText();
		    if (label.startsWith("Show")) {
			// JLabel can display simple HTML text
			messageLabel.setText(getHTMLDetails(throwable));
			detailsButton.setText("Hide Details");
			dialog.pack();  // Make dialog resize to fit details
		    }
		    else {
			messageLabel.setText(basicMessage);
			detailsButton.setText("Show Details");
			dialog.pack();
		    }
		}
	    });


	// Event handler for the "Report" button.
	reportButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent event) {
		try {
		    // Report the error, get response.  See below.
		    String response=reportThrowable(throwable, submissionURL);
		    // Tell the user about the report
		    messageLabel.setText("<html>Error reported to:<pre>" +
					 submissionURL +
					 "</pre>Server responds:<p>" +
					 response + "</html>");
		    dialog.pack();  // Resize dialog to fit new message
		    // Don't allow it to be reported again
		    reportButton.setText("Error Reported");
		    reportButton.setEnabled(false);
		}
		catch (IOException e) {  // If error reporting fails
		    messageLabel.setText("Error not reported: " + e);
		    dialog.pack();
		}
	    }
	});

	// Display the dialog modally.  This method will return only when the
	// user clicks the "Exit" button of the JOptionPane.
	dialog.show();     
    }

    /**
     * Return an HTML-formatted stack trace for the specified Throwable, 
     * including any exceptions chained to the exception.  Note the use of 
     * the Java 1.4 StackTraceElement to get stack details.  The returned 
     * string begins with "<html>" and is therefore suitable for display in
     * Swing components such as JLabel.
     */
    public static String getHTMLDetails(Throwable throwable) {
	StringBuffer b = new StringBuffer("<html>");
	int lengthOfLastTrace = 1;  // initial value

	// Start with the specified throwable and loop through the chain of
	// causality for the throwable.
	while(throwable != null) {
	    // Output Exception name and message, and begin a list 
	    b.append("<b>" + throwable.getClass().getName() + "</b>: " +
		     throwable.getMessage() + "<ul>");
	    // Get the stack trace and output each frame.  
	    // Be careful not to repeat stack frames that were already reported
	    // for the exception that this one caused.
	    StackTraceElement[] stack = throwable.getStackTrace();
	    for(int i = stack.length-lengthOfLastTrace; i >= 0; i--) {
		b.append("<li> in " +stack[i].getClassName() + ".<b>" +
			 stack[i].getMethodName() + "</b>() at <tt>"+
			 stack[i].getFileName() + ":" +
			 stack[i].getLineNumber() + "</tt>");
	    }
	    b.append("</ul>");  // end list
	    // See if there is a cause for this exception
	    throwable = throwable.getCause();
	    if (throwable != null) {
		// If so, output a header
		b.append("<i>Caused by: </i>");
		// And remember how many frames to skip in the stack trace
		// of the cause exception
		lengthOfLastTrace = stack.length;  
	    }
	}
	b.append("</html>"); 
	return b.toString();
    }

    /**
     * Serialize the specified Throwable, and use an HttpURLConnection to POST
     * it to the specified URL.  Return the response of the web server.
     */
    public static String reportThrowable(Throwable throwable,
					 String submissionURL) 
	throws IOException
    {
	URL url = new URL(submissionURL);        // Parse the URL
	URLConnection c = url.openConnection();  // Open unconnected Connection
	c.setDoOutput(true);
	c.setDoInput(true);
	// Tell the server what kind of data we're sending
	c.addRequestProperty("Content-type",
			     "application/x-java-serialized-object");
	
	// This code might work for other URL protocols, but it is intended
	// for HTTP.  We use a POST request to send data with the request.
	if (c instanceof HttpURLConnection)
	    ((HttpURLConnection)c).setRequestMethod("POST");
	
	c.connect();  // Now connect to the server
	
	// Get a stream to write to the server from the URLConnection.
	// Wrap an ObjectOutputStream around it and serialize the Throwable.
	ObjectOutputStream out =
	    new ObjectOutputStream(c.getOutputStream());
	out.writeObject(throwable);
	out.close();
	
	// Now get the response from the URLConnection.  We expect it to be
	// an InputStream from which we read the server's response.
	Object response = c.getContent();
	StringBuffer message = new StringBuffer();
	if (response instanceof InputStream) {
	    BufferedReader in =
	      new BufferedReader(new InputStreamReader((InputStream)response));
	    String line;
	    while((line = in.readLine()) != null) message.append(line);
	}
	return message.toString();
    }

    // A test program to demonstrate the class
    public static class Test {
	public static void main(String[] args) {
	    String url = (args.length > 0)?args[0]:null;
	    try { foo(); }
	    catch(Throwable e) {
		ErrorHandler.displayThrowable(e, "Fatal Error", url, null);
		System.exit(1);
	    }
	}
	// These methods purposely throw an exception
	public static void foo() { bar(null); }
	public static void bar(Object o) {
	    try { blah(o); }
	    catch(NullPointerException e) {
		// Catch the null pointer exception and throw a new exception
		// that has the NPE specified as its cause.
		throw (IllegalArgumentException)
		    new IllegalArgumentException("null argument").initCause(e);
	    }
	}
	public static void blah(Object o) {
	    Class c = o.getClass();  // throws NPE if o is null
	}
    }
}