// // @(#)ErrorDialog.java 4/2002 // // Copyright 2002 Zachary DelProposto. All rights reserved. // Use is subject to license terms. // // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. // Or from http://www.gnu.org/ // package dip.gui.dialog; import dip.misc.Utils; import dip.misc.Log; import dip.world.World; import dip.gui.*; import java.io.InvalidClassException; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.BufferedReader; import java.net.URLEncoder; import java.net.UnknownHostException; import java.net.URL; import java.net.HttpURLConnection; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Iterator; import javax.swing.JFrame; import java.awt.Dimension; import java.awt.event.*; import java.awt.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.text.*; import javax.swing.text.html.*; import java.text.MessageFormat; /** * Various error dialogs, which use HTML templates to display errors. * */ public class ErrorDialog extends TextViewer { // i18n constants private static final String NO_MESSAGE = Utils.getLocalString("ErrorDlg.nomessage"); private static final String UNKNOWN = Utils.getLocalString("ErrorDlg.unknown"); private static final String GENERAL_TEMPLATE = "ErrorDlg.general.template"; private static final String GENERAL_TITLE = "ErrorDlg.general.title"; private static final String GENERAL_BUTTON = "ErrorDlg.general.button"; private static final String FNF_TEMPLATE = "ErrorDlg.filenotfound.template"; private static final String FILE_TEMPLATE = "ErrorDlg.file.template"; private static final String FILE_TITLE = "ErrorDlg.file.title"; private static final String FILE_BUTTON = "ErrorDlg.file.button"; private static final String FATAL_TEMPLATE = "ErrorDlg.fatal.template"; private static final String FATAL_TITLE = "ErrorDlg.fatal.title"; private static final String FATAL_BUTTON = "ErrorDlg.fatal.button"; private static final String SERIOUS_TEMPLATE = "ErrorDlg.serious.template"; private static final String SERIOUS_TITLE = "ErrorDlg.serious.title"; private static final String SERIOUS_BUTTON_QUIT = "ErrorDlg.serious.button.quit"; private static final String SERIOUS_BUTTON_CONTINUE = "ErrorDlg.serious.button.continue"; private static final String NOVARIANT_TEMPLATE = "ErrorDlg.novariant.template"; private static final String NOVARIANT_TITLE = "ErrorDlg.novariant.title"; private static final String NOVARIANT_BUTTON = "ErrorDlg.novariant.button"; private static final String NET_TEMPLATE = "ErrorDlg.net.template"; private static final String NET_UNKNOWN_HOST_TEMPLATE = "ErrorDlg.net.unknownhost.template"; private static final String NET_TITLE = "ErrorDlg.net.title"; private static final String NET_BUTTON = "ErrorDlg.net.button"; private static final String VERSION_MISMATCH_TEMPLATE = "ErrorDlg.file.version.template"; private static final String VERSION_MISMATCH_TITLE = "ErrorDlg.file.version.title"; private static final String VERSION_MISMATCH_BUTTON = "ErrorDlg.file.version.button"; // bug submission parameters private static final String SUBMIT_BUTTON = "ErrorDlg.submit.button"; private static final String SUBMIT_TITLE = "ErrorDlg.submit.text.title"; private static final String SUBMIT_SUCCESS = "ErrorDlg.submit.text.success"; private static final String SUBMIT_FAILED = "ErrorDlg.submit.text.failed"; private final static String ACTION_SUBMIT = "ACTION_SUBMIT"; // simple testing /* public static void main(String args[]) { Exception ex = new NegativeArraySizeException("just a test"); java.io.IOException ioe = new java.io.FileNotFoundException("just a test"); // display each dialog displayFileIO(null, new FileNotFoundException("file_not_exist"), "TestFile.test"); displayFileIO(null, ioe, "TestFile.test"); displayFileIO(null, new InvalidClassException("bad version"), "TestFile.test"); displayNetIO(null, new UnknownHostException("Unknown host"), "127.0.0.1"); displayNetIO(null, new IOException("Some Exception Occured"), "http://127.0.0.1/whoknows.html"); displayGeneral(null, ex); displaySerious(null, ex); displayFatal(null, ex); } */ /** Convenience Method (no BugReportInfo required) */ public static void displaySerious(JFrame parent, Throwable e) { ErrorDialog.displaySerious(parent, e, null); }// displaySerious() /** * This should be used whenever a SERIOUS error occurs. * <p> * A Serious error is defined as an error from which may * be irrecoverable, but gives the user a chance to continue * working in the program if possible. The user may also quit * the program from this dialog. * */ public static void displaySerious(JFrame parent, Throwable e, BugReportInfo bri) { Object[] args = new Object[3]; args[0] = e.getClass().getName(); args[1] = getMsg(e); args[2] = getStackTrace(e); Log.println("SeriousError: ", args[0]); Log.println(" message: ", args[1]); Log.println(" stack trace:\n", args[2]); String text = Utils.getText(Utils.getLocalString(SERIOUS_TEMPLATE)); ErrorDialog ed = getForcedExitDialog(parent, Utils.getLocalString(SERIOUS_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(SERIOUS_BUTTON_QUIT), Utils.getLocalString(SERIOUS_BUTTON_CONTINUE), Utils.getScreenSize(0.4f), true, true, bri); ed.setVisible(true); }// displaySerious() /** Convenience Method (no BugReportInfo required) */ public static void displayFatal(JFrame parent, Throwable e) { ErrorDialog.displayFatal(parent, e, null); }// displayFatal() /** * This should be used whenever a FATAL error occurs. * <p> * A Fatal error is defined as an error from which recovery * is impossible. The only option is to exit the program, with * error code 1. * */ public static void displayFatal(JFrame parent, Throwable e, BugReportInfo bri) { Object[] args = new Object[3]; args[0] = e.getClass().getName(); args[1] = getMsg(e); args[2] = getStackTrace(e); Log.println("FatalError: ", args[0]); Log.println(" message: ", args[1]); Log.println(" stack trace:\n", args[2]); String text = Utils.getText(Utils.getLocalString(FATAL_TEMPLATE)); ErrorDialog ed = ErrorDialog.getOneButtonDialog(parent, Utils.getLocalString(FATAL_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(FATAL_BUTTON), Utils.getScreenSize(0.4f), true, true, bri); ed.setVisible(true); System.exit(1); // ALWAYS exit }// displayFatal() /** * This should be used whenever a FILE error occurs. * <p> * A File error is any error (not nescessarily an IOException) * that occurs when reading, writing, or processing a file. * <p> * Special handling exists FileNotFoundException, * which is common and not a program issue. */ public static void displayFileIO(JFrame parent, Exception e, String fileName) { Object[] args = new Object[4]; args[0] = getCleanName(e.getClass().getName()); args[1] = getMsg(e); args[2] = getStackTrace(e); args[3] = (fileName == null) ? UNKNOWN : fileName; Log.println("FileIOError: ", args[0]); Log.println(" message: ", args[1]); Log.println(" file: ", args[3]); Log.println(" stack trace:\n", args[2]); String text = null; if(e instanceof FileNotFoundException) { text = Utils.getText(Utils.getLocalString(FNF_TEMPLATE)); } else { text = Utils.getText(Utils.getLocalString(FILE_TEMPLATE)); } ErrorDialog ed = getOneButtonDialog(parent, Utils.getLocalString(FILE_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(FILE_BUTTON), Utils.getScreenSize(0.4f), true, false, null); ed.setVisible(true); }// displayFileIO() /** Convenience Method (no BugReportInfo required) */ public static void displayNetIO(JFrame parent, IOException e, String connection) { ErrorDialog.displayNetIO(parent, e, connection, null); }// displayNetIO() /** * This should be used whenever a NETWORK error occurs. * <p> * Network errors are all assumed to be IOException instances or * subclasses. The <code>connection</code> parameter may be null, or, * if specified, the connection text (URL or IP address, for example). * <p> * Special handling exists for an UnknownHostException. * */ public static void displayNetIO(JFrame parent, IOException e, String connection, BugReportInfo bri) { Object[] args = new Object[4]; args[0] = getCleanName(e.getClass().getName()); args[1] = getMsg(e); args[2] = getStackTrace(e); args[3] = (connection == null) ? UNKNOWN : connection; Log.println("NetworkIOError: ", args[0]); Log.println(" message: ", args[1]); Log.println(" connection: ", args[3]); Log.println(" stack trace:\n", args[2]); String text = null; boolean submittable = false; if(e instanceof UnknownHostException) { text = Utils.getText(Utils.getLocalString(NET_UNKNOWN_HOST_TEMPLATE)); } else { text = Utils.getText(Utils.getLocalString(NET_TEMPLATE)); submittable = true; } ErrorDialog ed = getOneButtonDialog(parent, Utils.getLocalString(NET_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(NET_BUTTON), Utils.getScreenSize(0.4f), true, submittable, bri); ed.setVisible(true); }// displayNetIO() /** Convenience Method (no BugReportInfo required) */ public static void displayGeneral(JFrame parent, Exception e) { ErrorDialog.displayGeneral(parent, e, null); }// displayGeneral() /** * This should be used whenever a GENERAL error occurs. * <p> * A General error is an error that does not fit any of the * other categories. */ public static void displayGeneral(JFrame parent, Exception e, BugReportInfo bri) { Object[] args = new Object[3]; args[0] = getCleanName(e.getClass().getName()); args[1] = getMsg(e); args[2] = getStackTrace(e); Log.println("GeneralError: ", args[0]); Log.println(" message: ", args[1]); Log.println(" stack trace:\n", args[2]); String text = Utils.getText(Utils.getLocalString(GENERAL_TEMPLATE)); ErrorDialog ed = getOneButtonDialog(parent, Utils.getLocalString(GENERAL_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(GENERAL_BUTTON), Utils.getScreenSize(0.4f), true, true, bri); ed.setVisible(true); }// displayGeneral() /** * This should be used whenever there is a Variant version mismatch. */ public static void displayVariantVersionMismatch(JFrame parent, World.VariantInfo vi, float availableVersion) { Object[] args = new Object[3]; args[0] = vi.getVariantName(); args[1] = new Float( vi.getVariantVersion() ); args[2] = new Float( availableVersion ); String text = Utils.getText(Utils.getLocalString(VERSION_MISMATCH_TEMPLATE)); ErrorDialog ed = getOneButtonDialog(parent, Utils.getLocalString(VERSION_MISMATCH_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(VERSION_MISMATCH_BUTTON), Utils.getScreenSize(0.4f), true, false, null); ed.setVisible(true); }// displayVariantNotAvailable() /** * This should be used whenever a Variant is not available. */ public static void displayVariantNotAvailable(JFrame parent, World.VariantInfo vi) { Object[] args = new Object[3]; args[0] = vi.getVariantName(); args[1] = new Float( vi.getVariantVersion() ); args[2] = vi.getVariantName(); String text = Utils.getText(Utils.getLocalString(NOVARIANT_TEMPLATE)); ErrorDialog ed = getOneButtonDialog(parent, Utils.getLocalString(NOVARIANT_TITLE), Utils.format(text, args), (String) args[2], Utils.getLocalString(NOVARIANT_BUTTON), Utils.getScreenSize(0.4f), true, false, null); ed.setVisible(true); }// displayVariantNotAvailable() /** Prepends the jDip and Java version/etc info to the stack trace */ private static String getStackTrace(Throwable t) { StringBuffer sb = new StringBuffer(2048); StackTraceElement[] ste = t.getStackTrace(); sb.append("jDip version: "); sb.append(ClientFrame.getVersion()); try { sb.append("<br>\nJava version: "); sb.append(System.getProperty("java.version", "?")); sb.append("<br>\nJava vendor: "); sb.append(System.getProperty("java.vendor", "?")); sb.append("<br>\nJava runtime version: "); sb.append(System.getProperty("java.runtime.version", "?")); sb.append("<br>\nOS name: "); sb.append(System.getProperty("os.name", "?")); sb.append("<br>\nOS version: "); sb.append(System.getProperty("os.version", "?")); sb.append("<br>\nOS arch: "); sb.append(System.getProperty("os.arch", "?")); // some runtime info... Runtime rt = Runtime.getRuntime(); sb.append("<br>\nMemory Free: "); sb.append(rt.freeMemory()); sb.append("<br>\nMemory Total: "); sb.append(rt.totalMemory()); sb.append("<br>\nMemory Max: "); sb.append(rt.maxMemory()); } catch(Exception e) { sb.append("<br>\n[Exception occured while getting system/runtime info]"); } sb.append("<br>\n"); sb.append(t.getClass().getName()); sb.append("<br>\n"); sb.append(t.getMessage()); sb.append("<br>\n"); appendBatikInfo(sb, t); int len = ste.length; for(int i=(len - 1); i>=0; i--) { sb.append(ste[i].toString()); sb.append('\n'); } return sb.toString(); }// getStackTrace() private static String getMsg(Throwable t) { String msg = t.getLocalizedMessage(); if(msg != null) { if(!"".equals(msg)) { return msg; } } return NO_MESSAGE; }// getMsg() /** given a name like xxx.x.xxx.x.x.x.aaaa returns 'aaaa' */ private static String getCleanName(String in) { return in.substring(in.lastIndexOf(".")+1); } /** Create an ErrorDialog */ private ErrorDialog(JFrame frame, String title) { super(frame, true); setTitle(title); }// ErrorDialog() /** * Create an ErrorDialog that is setup with a single button. * However, if submittable is set to true, a "submit" button is also present, * to send the bug report to the jDip bug report database. */ private static ErrorDialog getOneButtonDialog(final JFrame parent, String title, final String text, final String rawText, String buttonText, Dimension size, boolean resizable, boolean submittable, final BugReportInfo bri) { ErrorDialog ed = new ErrorDialog(parent, title) { protected void close(String actionCommand) { if(ACTION_SUBMIT.equals(actionCommand)) { setButtonEnabled(ACTION_SUBMIT, false); if(!submitBug(parent, rawText, bri)) { setButtonEnabled(ACTION_SUBMIT, true); } } else { super.close(actionCommand); } }// close(); }; ed.setContentType("text/html"); ed.setEditable(false); ed.setText(text); ed.setHeaderVisible(false); if(submittable) { ed.addTwoButtons( ed.makeButton(buttonText, ACTION_CLOSE, true), ed.makeButton(Utils.getLocalString(SUBMIT_BUTTON), ACTION_SUBMIT, true), false, true); } else { ed.addSingleButton( ed.makeButton(buttonText, ACTION_CLOSE, true) ); } ed.pack(); if(size != null) { ed.setSize(size); } ed.setResizable(resizable); Utils.centerInScreen(ed); return ed; }// getOneButtonDialog() /** * Create an ErrorDialog that is setup with two buttons, * the second of which exits the program. * However, if submittable is set to true, a "submit" button is also present, * to send the bug report to the jDip bug report database. * rawText is the error-message alone (no dialog text) */ private static ErrorDialog getForcedExitDialog(final JFrame parent, String title, final String text, final String rawText, String exitText, String continueText, Dimension size, boolean resizable, boolean submittable, final BugReportInfo bri) { ErrorDialog ed = new ErrorDialog(parent, title) { protected void close(String actionCommand) { if(ACTION_SUBMIT.equals(actionCommand)) { if(submitBug(parent, rawText, bri)) { setButtonEnabled(ACTION_SUBMIT, false); } } else if(isOKorAccept(actionCommand)) { setVisible(false); dispose(); // attempt to continue } else { setVisible(false); System.exit(1); // exit the program, with an error condition } }// close(); }; ed.setContentType("text/html"); ed.setEditable(false); ed.setText(text); ed.setHeaderVisible(false); JButton bR = ed.makeButton(exitText, ACTION_CANCEL, true); JButton bC = ed.makeButton(continueText, ACTION_OK, true); JButton bL = ed.makeButton(Utils.getLocalString(SUBMIT_BUTTON), ACTION_SUBMIT, true); ed.addThreeButtons(bL, bC, bR, bC, bR); ed.pack(); if(size != null) { ed.setSize(size); } ed.setResizable(resizable); Utils.centerInScreen(ed); return ed; }// getForcedExitDialog() /** If exception is a Batik exception, with line # info, append */ private static void appendBatikInfo(StringBuffer sb, Throwable e) { if(e instanceof org.apache.batik.bridge.BridgeException) { org.apache.batik.bridge.BridgeException be = (org.apache.batik.bridge.BridgeException) e; sb.append("\nBridgeException:"); sb.append("\n Code: "); sb.append(be.getCode()); sb.append("\n Element: "); sb.append(be.getElement().getTagName()); sb.append("\n"); } else if(e instanceof org.w3c.css.sac.CSSParseException) { org.w3c.css.sac.CSSParseException pe = (org.w3c.css.sac.CSSParseException) e; sb.append("\nCSSParseException:"); sb.append("\n URI: "); sb.append(pe.getURI()); sb.append("\n Line: "); sb.append(String.valueOf(pe.getLineNumber())); sb.append("\n Column: "); sb.append(String.valueOf(pe.getColumnNumber())); sb.append("\n"); } else if(e instanceof org.apache.batik.script.InterpreterException) { org.apache.batik.script.InterpreterException ie = (org.apache.batik.script.InterpreterException) e; sb.append("\nInterpreterException:"); sb.append("\n Line: "); sb.append(String.valueOf(ie.getLineNumber())); sb.append("\n Column: "); sb.append(String.valueOf(ie.getColumnNumber())); sb.append("\n"); } else if(e instanceof org.apache.batik.css.parser.ParseException) { org.apache.batik.css.parser.ParseException pe = (org.apache.batik.css.parser.ParseException) e; sb.append("\ncss.ParseException:"); sb.append("\n Line Number: "); sb.append(String.valueOf(pe.getLineNumber())); sb.append("\n Column: "); sb.append(String.valueOf(pe.getColumnNumber())); sb.append("\n"); } else if(e instanceof org.apache.batik.parser.ParseException) { org.apache.batik.parser.ParseException pe = (org.apache.batik.parser.ParseException) e; sb.append("\nParseException:"); sb.append("\n Line: "); sb.append(String.valueOf(pe.getLineNumber())); sb.append("\n Column: "); sb.append(String.valueOf(pe.getColumnNumber())); sb.append("\n"); } }// appendBatikInfo() /** * GUI version of sendBugReport. Popup error message with result. * Does not enable or disable the submit button. BugReportInfo * may be null. */ private static boolean submitBug(JFrame parent, String text, BugReportInfo bri) { if(sendBugReport(text, bri)) { Utils.popupInfo(parent, Utils.getLocalString(SUBMIT_TITLE), Utils.getLocalString(SUBMIT_SUCCESS)); return true; } else { Utils.popupError(parent, Utils.getLocalString(SUBMIT_TITLE), Utils.getLocalString(SUBMIT_FAILED)); return false; } }// submitBug() /** * Send bug report to jDip website. Synchronous. Will not * throw further exceptions (unless a null String is passed). * BugReportInfo is allowed to be null. */ private static boolean sendBugReport(String text, BugReportInfo bri) { if(text == null) { throw new IllegalArgumentException(); } OutputStreamWriter wr = null; BufferedReader rd = null; try { URL url = new URL("http://jdip.sourceforge.net/forms/data/bugFormProc.php"); HttpURLConnection urlConn = (HttpURLConnection) url.openConnection(); urlConn.setRequestMethod("POST"); urlConn.setDoInput(true); urlConn.setDoOutput(true); urlConn.setUseCaches(false); urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); urlConn.setRequestProperty("Accept-Charset", "*"); urlConn.setRequestProperty("User-Agent", "jDip"); wr = new OutputStreamWriter(urlConn.getOutputStream()); final String header = "brHeader="+URLEncoder.encode( "JDIP_REMOTE_BUG_REPORT", "UTF-8"); final String time = "&brTime="+URLEncoder.encode( String.valueOf(new Date()), "UTF-8"); final String message = "&brText="+URLEncoder.encode(text, "UTF-8"); wr.write(header); wr.write(time); if(bri != null) { wr.write(bri.getInfo()); } wr.write(message); wr.flush(); wr.close(); rd = new BufferedReader(new InputStreamReader(urlConn.getInputStream())); String line; while ((line = rd.readLine()) != null) { if(line.indexOf("Thanks.") != -1) { return true; } } } catch(Exception e) { Log.println("ERROR: could not send bug report."); Log.println(e); } finally { try { if(wr != null) { wr.close(); } } catch(Exception e) { Log.println("ErrorDialog: ", e); } try { if(rd != null) { rd.close(); } } catch(Exception e) { Log.println("ErrorDialog: ", e); } } return false; }// sendBugReport() /** * Extra debug information that can be sent, and is included in debug * logs or transmitted bug reports. */ public static class BugReportInfo { private final List list; public BugReportInfo() { list = new LinkedList(); }// BugInfo() /** * Add a line; name/value. This is typically displayed as:<br> * <code>name: value</code><br> * on a single line. */ public void add(String name, String value) { StringBuffer sb = new StringBuffer(); sb.append( ((name == null) ? "" : name) ); sb.append(": "); sb.append( value ); list.add(sb.toString()); }// add() /** * Returns all name-value pairs, as a String. * Lines are terminated with newline charater(s). */ public String getInfo() { StringBuffer sb = new StringBuffer(); Iterator iter = list.iterator(); while(iter.hasNext()) { String line = (String) iter.next(); sb.append(line); sb.append('\n'); } return sb.toString(); }// getInfo() }// nested class BugReportInfo }// class ErrorDialog