001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.tools; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.awt.GridBagLayout; 008import java.io.ByteArrayOutputStream; 009import java.io.IOException; 010import java.io.PrintWriter; 011import java.io.StringWriter; 012import java.net.URL; 013import java.nio.ByteBuffer; 014import java.util.zip.GZIPOutputStream; 015 016import javax.swing.JCheckBox; 017import javax.swing.JLabel; 018import javax.swing.JOptionPane; 019import javax.swing.JPanel; 020import javax.swing.JScrollPane; 021import javax.swing.SwingUtilities; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.actions.ShowStatusReportAction; 025import org.openstreetmap.josm.gui.ExtendedDialog; 026import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 027import org.openstreetmap.josm.gui.widgets.JosmTextArea; 028import org.openstreetmap.josm.gui.widgets.UrlLabel; 029import org.openstreetmap.josm.plugins.PluginHandler; 030 031/** 032 * An exception handler that asks the user to send a bug report. 033 * 034 * @author imi 035 */ 036public final class BugReportExceptionHandler implements Thread.UncaughtExceptionHandler { 037 038 private static boolean handlingInProgress = false; 039 private static int exceptionCounter = 0; 040 private static boolean suppressExceptionDialogs = false; 041 042 @Override 043 public void uncaughtException(Thread t, Throwable e) { 044 handleException(e); 045 } 046 047 //http://stuffthathappens.com/blog/2007/10/15/one-more-note-on-uncaught-exception-handlers/ 048 /** 049 * Handles the given throwable object 050 * @param t The throwable object 051 */ 052 public void handle(Throwable t) { 053 handleException(t); 054 } 055 056 /** 057 * Handles the given exception 058 * @param e the exception 059 */ 060 public static void handleException(final Throwable e) { 061 if (handlingInProgress) 062 return; // we do not handle secondary exceptions, this gets too messy 063 if (suppressExceptionDialogs) 064 return; 065 handlingInProgress = true; 066 exceptionCounter++; 067 try { 068 e.printStackTrace(); 069 if (Main.parent != null) { 070 if (e instanceof OutOfMemoryError) { 071 // do not translate the string, as translation may raise an exception 072 JOptionPane.showMessageDialog(Main.parent, "JOSM is out of memory. " + 073 "Strange things may happen.\nPlease restart JOSM with the -Xmx###M option,\n" + 074 "where ### is the number of MB assigned to JOSM (e.g. 256).\n" + 075 "Currently, " + Runtime.getRuntime().maxMemory()/1024/1024 + " MB are available to JOSM.", 076 "Error", 077 JOptionPane.ERROR_MESSAGE 078 ); 079 return; 080 } 081 082 083 SwingUtilities.invokeLater(new Runnable() { 084 @Override 085 public void run() { 086 // Give the user a chance to deactivate the plugin which threw the exception (if it 087 // was thrown from a plugin) 088 // 089 PluginHandler.disablePluginAfterException(e); 090 091 // Then ask for submitting a bug report, for exceptions thrown from a plugin too 092 // 093 ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Unexpected Exception"), new String[] {tr("Do nothing"), tr("Report Bug")}); 094 ed.setIcon(JOptionPane.ERROR_MESSAGE); 095 JPanel pnl = new JPanel(new GridBagLayout()); 096 pnl.add(new JLabel( 097 "<html>" 098 + tr("An unexpected exception occurred.<br>" + 099 "This is always a coding error. If you are running the latest<br>" + 100 "version of JOSM, please consider being kind and file a bug report." 101 ) 102 + "</html>"), GBC.eol()); 103 JCheckBox cbSuppress = null; 104 if (exceptionCounter > 1) { 105 cbSuppress = new JCheckBox(tr("Suppress further error dialogs for this session.")); 106 pnl.add(cbSuppress, GBC.eol()); 107 } 108 ed.setContent(pnl); 109 ed.showDialog(); 110 if (cbSuppress != null && cbSuppress.isSelected()) { 111 suppressExceptionDialogs = true; 112 } 113 if (ed.getValue() != 2) return; 114 115 try { 116 final int maxlen = 6000; 117 StringWriter stack = new StringWriter(); 118 e.printStackTrace(new PrintWriter(stack)); 119 120 String text = ShowStatusReportAction.getReportHeader() 121 + stack.getBuffer().toString(); 122 String urltext = text.replaceAll("\r",""); /* strip useless return chars */ 123 if(urltext.length() > maxlen) 124 { 125 urltext = urltext.substring(0,maxlen); 126 int idx = urltext.lastIndexOf('\n'); 127 /* cut whole line when not loosing too much */ 128 if(maxlen-idx < 200) { 129 urltext = urltext.substring(0,idx+1); 130 } 131 urltext += "...<snip>...\n"; 132 } 133 134 JPanel p = new JPanel(new GridBagLayout()); 135 p.add(new JMultilineLabel( 136 tr("You have encountered an error in JOSM. Before you file a bug report " + 137 "make sure you have updated to the latest version of JOSM here:")), GBC.eol()); 138 p.add(new UrlLabel(Main.JOSM_WEBSITE,2), GBC.eop().insets(8,0,0,0)); 139 p.add(new JMultilineLabel( 140 tr("You should also update your plugins. If neither of those help please " + 141 "file a bug report in our bugtracker using this link:")), GBC.eol()); 142 p.add(getBugReportUrlLabel(urltext), GBC.eop().insets(8,0,0,0)); 143 p.add(new JMultilineLabel( 144 tr("There the error information provided below should already be " + 145 "filled in for you. Please include information on how to reproduce " + 146 "the error and try to supply as much detail as possible.")), GBC.eop()); 147 p.add(new JMultilineLabel( 148 tr("Alternatively, if that does not work you can manually fill in the information " + 149 "below at this URL:")), GBC.eol()); 150 p.add(new UrlLabel(Main.JOSM_WEBSITE+"/newticket",2), GBC.eop().insets(8,0,0,0)); 151 if (Utils.copyToClipboard(text)) { 152 p.add(new JLabel(tr("(The text has already been copied to your clipboard.)")), GBC.eop()); 153 } 154 155 JosmTextArea info = new JosmTextArea(text, 18, 60); 156 info.setCaretPosition(0); 157 info.setEditable(false); 158 p.add(new JScrollPane(info), GBC.eop()); 159 160 for (Component c: p.getComponents()) { 161 if (c instanceof JMultilineLabel) { 162 ((JMultilineLabel)c).setMaxWidth(400); 163 } 164 } 165 166 JOptionPane.showMessageDialog(Main.parent, p, tr("You have encountered a bug in JOSM"), JOptionPane.ERROR_MESSAGE); 167 } catch (Exception e1) { 168 e1.printStackTrace(); 169 } 170 } 171 }); 172 } 173 } finally { 174 handlingInProgress = false; 175 } 176 } 177 178 /** 179 * Determines if an exception is currently being handled 180 * @return {@code true} if an exception is currently being handled, {@code false} otherwise 181 */ 182 public static boolean exceptionHandlingInProgress() { 183 return handlingInProgress; 184 } 185 186 /** 187 * Replies the URL to create a JOSM bug report with the given debug text 188 * @param debugText The debug text to provide us 189 * @return The URL to create a JOSM bug report with the given debug text 190 * @since 5849 191 */ 192 public static URL getBugReportUrl(String debugText) { 193 try { 194 ByteArrayOutputStream out = new ByteArrayOutputStream(); 195 GZIPOutputStream gzip = new GZIPOutputStream(out); 196 gzip.write(debugText.getBytes("UTF-8")); 197 Utils.close(gzip); 198 199 return new URL(Main.JOSM_WEBSITE+"/josmticket?" + 200 "gdata="+Base64.encode(ByteBuffer.wrap(out.toByteArray()), true)); 201 } catch (IOException e) { 202 e.printStackTrace(); 203 return null; 204 } 205 } 206 207 /** 208 * Replies the URL label to create a JOSM bug report with the given debug text 209 * @param debugText The debug text to provide us 210 * @return The URL label to create a JOSM bug report with the given debug text 211 * @since 5849 212 */ 213 public static final UrlLabel getBugReportUrlLabel(String debugText) { 214 URL url = getBugReportUrl(debugText); 215 if (url != null) { 216 return new UrlLabel(url.toString(), Main.JOSM_WEBSITE+"/josmticket?...", 2); 217 } 218 return null; 219 } 220}