/******************************************************************************* * Copyright (c) MOBAC developers * * 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, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.utilities; import java.awt.AWTEvent; import java.awt.BorderLayout; import java.awt.Desktop; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.io.PrintWriter; import java.io.StringWriter; import javax.swing.JCheckBox; import javax.swing.JEditorPane; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.UIManager; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; import mobac.program.Logging; import mobac.program.ProgramInfo; import mobac.program.interfaces.ExceptionExtendedInfo; import org.apache.log4j.Logger; import com.sleepycat.je.ExceptionEvent; import com.sleepycat.je.ExceptionListener; public class GUIExceptionHandler implements Thread.UncaughtExceptionHandler, ExceptionListener { private static final GUIExceptionHandler instance = new GUIExceptionHandler(); private static final Logger log = Logger.getLogger(GUIExceptionHandler.class); private static final double MB_DIV = 1024d * 1024d; private static final String URL_BUGTRACKER = "http://sourceforge.net/p/mobac/bugs/"; static { Thread.setDefaultUncaughtExceptionHandler(instance); } public static void registerForCurrentThread() { Thread t = Thread.currentThread(); log.trace("Registering MOBAC exception handler for thread \"" + t.getName() + "\" [" + t.getId() + "]"); t.setUncaughtExceptionHandler(instance); } public static GUIExceptionHandler getInstance() { return instance; } private GUIExceptionHandler() { super(); } /** * Implementation for {@link com.sleepycat.je.ExceptionListener} */ public void exceptionThrown(ExceptionEvent paramExceptionEvent) { Exception e = paramExceptionEvent.getException(); log.error("Exception in tile store: " + paramExceptionEvent.toString(), e); showExceptionDialog(e); } /** * Implementation for {@link Thread.UncaughtExceptionHandler} */ public void uncaughtException(Thread t, Throwable e) { processException(t, e); } public static void processException(Throwable e) { processException(Thread.currentThread(), e); } public static void processFatalExceptionSimpleDialog(String dialogMessage, Throwable e) { log.error(dialogMessage + ": " + e.getMessage(), e); String[] options = { I18nUtils.localizedStringForKey("Exit"), I18nUtils.localizedStringForKey("dlg_download_show_error_report") }; int a = JOptionPane.showOptionDialog(null, dialogMessage, I18nUtils.localizedStringForKey("Error"), 0, JOptionPane.ERROR_MESSAGE, null, options, options[0]); if (a == 1) GUIExceptionHandler.showExceptionDialog(e); System.exit(1); } public static void processException(Thread thread, Throwable t) { log.error("Uncaught exception: ", t); showExceptionDialog(thread, t, null); } public static void processException(Thread thread, Throwable t, AWTEvent newEvent) { String eventText = newEvent.toString(); log.error("Uncaught exception on processing event " + eventText, t); if (eventText.length() > 100) { String[] parts = eventText.split(","); StringWriter sw = new StringWriter(eventText.length() + 20); sw.write(parts[0]); int len = parts[0].length(); for (int i = 1; i < parts.length; i++) { String s = parts[i]; if (s.length() + len > 80) { sw.write("\n\t"); len = 0; } sw.write(s); } eventText = "Event: " + sw.toString(); } showExceptionDialog(thread, t, eventText); } public static String prop(String key) { String s = System.getProperty(key); if (s != null) return s; else return ""; } public static void showExceptionDialog(Throwable t) { showExceptionDialog(null, Thread.currentThread(), t, null); } public static void showExceptionDialog(String message, Throwable t) { showExceptionDialog(message, Thread.currentThread(), t, null); } public static void showExceptionDialog(Thread thread, Throwable t, String additionalInfo) { showExceptionDialog(null, thread, t, additionalInfo); } public static synchronized void showExceptionDialog(String message, Thread thread, Throwable t, String additionalInfo) { try { if (t != null) { // Recursive check - in case the Exception was thrown in a Component.paint() method // this may lead to an infinite recursion with one message dialog per recursion loop String thisClassName = GUIExceptionHandler.class.getName(); for (StackTraceElement ste : t.getStackTrace()) { if (ste.getClassName().startsWith(thisClassName) && "showExceptionDialog".equals(ste.getMethodName())) { log.error("Recursive error loop detected - aborting"); return; } } } StringBuilder sb = new StringBuilder(2048); if (message != null) sb.append("Error message: " + message + "\n"); sb.append("Version: " + ProgramInfo.getCompleteTitle()); sb.append("\nPlatform: " + prop("os.name") + " (" + prop("os.version") + ")"); String windowManager = System.getProperty("sun.desktop"); if (windowManager != null) sb.append(" (" + windowManager + ")"); String dist = OSUtilities.getLinuxDistributionName(); if (dist != null) sb.append("\nDistribution name: " + dist); sb.append("\nJava VM: " + prop("java.vm.name") + " (" + prop("java.runtime.version") + ")"); Runtime r = Runtime.getRuntime(); sb.append(String.format("\nMax heap size: %3.2f MiB", r.maxMemory() / MB_DIV)); // sb.append("\nMapsources rev: " + MapSourcesUpdater.getCurrentMapSourcesRev()); sb.append("\nCPU cores: " + Runtime.getRuntime().availableProcessors()); if (thread != null) sb.append("\n\nThread: " + thread.getName()); if (additionalInfo != null) sb.append("\n\n" + additionalInfo); if (t instanceof ExceptionExtendedInfo) { ExceptionExtendedInfo ei = (ExceptionExtendedInfo) t; sb.append("\n"); sb.append(ei.getExtendedInfo()); } String guiText; String dialogTitle; JPanel panel = new JPanel(new BorderLayout()); if (t != null) { sb.append("\n\nError hierarchy:"); Throwable tmp = t; while (tmp != null) { sb.append("\n " + tmp.getClass().getSimpleName() + ": " + tmp.getMessage()); tmp = tmp.getCause(); } String exceptionName = t.getClass().getSimpleName(); dialogTitle = "Unexpected Exception: " + exceptionName; StringWriter stack = new StringWriter(); t.printStackTrace(new PrintWriter(stack)); sb.append("\n\n#############################################################\n\n"); sb.append(stack.getBuffer().toString()); sb.append("\n#############################################################"); guiText = "An unexpected exception occurred (" + exceptionName + ")<br>" + "<p>Please create a ticket in the bug tracker " + "on <a href=\"" + URL_BUGTRACKER + "\">SourceForge.net</a><br>" + "<b>Please include a detailed description of your performed actions <br>" + "before the error occurred.</b></p>Be sure to include the following information:"; } else { dialogTitle = "System report"; guiText = "This is a user generated system report.<br>" + "<p>It can be used for providing detailed version information <br>" + "in case a new ticket in the bug tracker is created " + "on <a href=\"" + URL_BUGTRACKER + "\">SourceForge.net</a><br>" + "<b>Please include a detailed description of your problem <br>" + "and what steps are necessary to reproduce the problem.</b>" + "</p>"; } JEditorPane text = new JEditorPane("text/html", ""); text.setOpaque(true); text.setBackground(UIManager.getColor("JFrame.background")); text.setEditable(false); text.addHyperlinkListener(new HyperlinkListener() { public void hyperlinkUpdate(HyperlinkEvent e) { if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return; try { Desktop.getDesktop().browse(e.getURL().toURI()); } catch (Exception e1) { log.error("", e1); } } }); panel.add(text, BorderLayout.NORTH); try { StringSelection contents = new StringSelection(sb.toString()); ClipboardOwner owner = new ClipboardOwner() { public void lostOwnership(Clipboard clipboard, Transferable contents) { } }; Toolkit.getDefaultToolkit().getSystemClipboard().setContents(contents, owner); guiText += "<p>(The following text has already been copied to your clipboard.)</p>"; } catch (RuntimeException x) { log.error("", x); } text.setText("<html>" + guiText + "</html>"); JCheckBox quickMOBACcb = new JCheckBox("Do not continue and quit program by pressing OK"); JTextArea info = new JTextArea(sb.toString(), 20, 60); info.setCaretPosition(0); info.setEditable(false); info.setMinimumSize(new Dimension(200, 150)); panel.add(new JScrollPane(info), BorderLayout.CENTER); panel.add(quickMOBACcb, BorderLayout.SOUTH); panel.setMinimumSize(new Dimension(700, 300)); panel.validate(); JOptionPane.showMessageDialog(null, panel, dialogTitle, JOptionPane.ERROR_MESSAGE); if (quickMOBACcb.isSelected()) { log.warn("User selected to quit MOBAC after an exception"); System.exit(1); } } catch (Exception e1) { e1.printStackTrace(); } } public static void installToolkitEventQueueProxy() { boolean isLikeJava7 = Float.parseFloat(System.getProperty("java.specification.version")) > 1.6; EventQueueProxy eventQueueProxy = new EventQueueProxy(); // Bug handling. See // http://stackoverflow.com/questions/3158254/how-to-replace-the-awt-eventqueue-with-own-implementation try { if (isLikeJava7) EventQueue.invokeAndWait(eventQueueProxy); else eventQueueProxy.run(); } catch (Exception e) { log.error("", e); } } /** * Catching all Runtime Exceptions in Swing * * http://ruben42.wordpress.com/2009/03/30/catching-all-runtime-exceptions-in-swing/ */ protected static class EventQueueProxy extends EventQueue implements Runnable { /** * Installs the {@link EventQueueProxy} */ @Override public void run() { EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue(); queue.push(this); } protected void dispatchEvent(AWTEvent newEvent) { try { super.dispatchEvent(newEvent); } catch (Throwable e) { if (e instanceof ArrayIndexOutOfBoundsException) { StackTraceElement[] st = e.getStackTrace(); if (st.length > 0) { if ("sun.font.FontDesignMetrics".equals(st[0].getClassName())) { log.error("Ignored JRE bug exception " + e.getMessage() + " caused by : " + st[0]); // This is a known JRE bug - we just ignore it return; } } } GUIExceptionHandler.processException(Thread.currentThread(), e); } } } public static void main(String[] args) { for (;;) { try { Logging.configureConsoleLogging(); UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); throw new RuntimeException("Test", new Exception("Inner")); } catch (Exception e) { showExceptionDialog("Test 123", e); } catch (Error e) { showExceptionDialog(e); } break; } } }