Refactor error handling code

This commit is contained in:
drdev
2014-04-14 20:56:40 +00:00
parent 3c7b8efb72
commit 3d39ce235f
18 changed files with 424 additions and 686 deletions

View File

@@ -3,7 +3,6 @@ package forge.deck;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import forge.GuiBase;
import forge.StaticData;
import forge.card.CardEdition;
import forge.card.ColorSet;
@@ -12,6 +11,7 @@ import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckGroup;
import forge.deck.DeckSection;
import forge.error.BugReporter;
import forge.game.GameFormat;
import forge.game.GameType;
import forge.item.InventoryItem;
@@ -289,7 +289,7 @@ public class DeckProxy implements InventoryItem {
StringBuilder errorBuilder = new StringBuilder();
deck.getMain().addAll(gen.getThemeDeck(this.getName(), 60, errorBuilder));
if (errorBuilder.length() > 0) {
GuiBase.getInterface().reportBug(errorBuilder.toString());
BugReporter.reportBug(errorBuilder.toString());
}
return deck;
}

View File

@@ -0,0 +1,230 @@
/*
* Forge: Play Magic: the Gathering.
* Copyright (C) 2011 Forge Team
*
* 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 3 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 forge.error;
import forge.FThreads;
import forge.GuiBase;
import forge.SOptionPane;
import forge.util.BuildInfo;
import org.apache.commons.lang3.StringUtils;
import java.awt.*;
import java.awt.datatransfer.StringSelection;
import java.io.*;
import java.net.URI;
import java.util.Map;
import java.util.Map.Entry;
/**
* The class ErrorViewer. Enables showing and saving error messages that
* occurred in forge.
*
* @author Clemens Koza
* @version V1.0 02.08.2009
*/
public class BugReporter {
private static final int STACK_OVERFLOW_MAX_MESSAGE_LEN = 16 * 1024;
public static final String HELP_TEXT =
"<html>A template for a post in the bug reports forum topic is shown below. Just select 'Copy and go to forum' "
+ "and the template will be copied to your system clipboard and the forum page will open in your browser. "
+ "Then all you have to do is paste the text into a forum post and edit the description line.</html>";
public static final String HELP_URL_LABEL =
"Reporting bugs in Forge is very important. We sincerely thank you for your time."
+ " For help writing a solid bug report, please see:";
public static final String HELP_URL =
"http://www.slightlymagic.net/forum/viewtopic.php?f=26&p=109925#p109925";
public static final String FORUM_URL;
static {
String forgeVersion = BuildInfo.getVersionString();
if (StringUtils.containsIgnoreCase(forgeVersion, "svn") || StringUtils.containsIgnoreCase(forgeVersion, "snapshot")) {
FORUM_URL = "http://www.slightlymagic.net/forum/viewtopic.php?f=52&t=6333&start=54564487645#bottom";
}
else {
FORUM_URL = "http://www.slightlymagic.net/forum/viewforum.php?f=26";
}
}
/**
* Shows exception information in a format ready to post to the forum as a crash report. Uses the exception's message
* as the reason if message is null.
*/
public static void reportException(final Throwable ex, final String message) {
if (ex == null) {
return;
}
if (message != null) {
System.err.printf("%s > %s%n", FThreads.debugGetCurrThreadId(), message);
}
System.err.print( FThreads.debugGetCurrThreadId() + " > " );
ex.printStackTrace();
StringBuilder sb = new StringBuilder();
sb.append("Description: [describe what you were doing when the crash occurred]\n\n");
buildSpoilerHeader(sb, ex.getClass().getSimpleName());
sb.append("\n\n");
if (null != message && !message.isEmpty()) {
sb.append(FThreads.debugGetCurrThreadId()).append(" > ").append(message).append("\n");
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
ex.printStackTrace(pw);
String swStr = sw.toString();
if (ex instanceof StackOverflowError && swStr.length() >= STACK_OVERFLOW_MAX_MESSAGE_LEN) {
// most likely a cycle. only take first portion so the message
// doesn't grow too large to post
sb.append(swStr, 0, STACK_OVERFLOW_MAX_MESSAGE_LEN);
sb.append("\n... (truncated)");
}
else {
sb.append(swStr);
}
buildSpoilerFooter(sb);
GuiBase.getInterface().showBugReportDialog("Report a crash", sb.toString(), true);
}
/**
* Alias for reportException(ex, null).
*/
public static void reportException(final Throwable ex) {
reportException(ex, null);
}
/**
* Alias for reportException(ex, String.format(format, args)).
*/
public static void reportException(final Throwable ex, final String format, final Object... args) {
reportException(ex, String.format(format, args));
}
/**
* Shows a forum post template for reporting a bug.
*/
public static void reportBug(String details) {
StringBuilder sb = new StringBuilder();
sb.append("Description: [describe the problem]\n\n");
buildSpoilerHeader(sb, "General bug report");
if (null != details && !details.isEmpty()) {
sb.append("\n\n");
sb.append(details);
}
buildSpoilerFooter(sb);
GuiBase.getInterface().showBugReportDialog("Report a bug", sb.toString(), false);
}
/**
* Shows thread stack information in a format ready to post to the forum.
*/
public static void reportThreadStacks(final String message) {
StringBuilder sb = new StringBuilder();
sb.append("Description: [describe what you were doing at the time]\n\n");
buildSpoilerHeader(sb, "Thread stack dump");
sb.append("\n\n");
if (null != message && !message.isEmpty()) {
sb.append(message);
sb.append("\n");
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
final Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
for (final Entry<Thread, StackTraceElement[]> e : traces.entrySet()) {
pw.println();
pw.printf("%s (%s):%n", e.getKey().getName(), e.getKey().getId());
for (final StackTraceElement el : e.getValue()) {
pw.println(el);
}
}
sb.append(sw.toString());
buildSpoilerFooter(sb);
GuiBase.getInterface().showBugReportDialog("Thread stack dump", sb.toString(), false);
}
/**
* Alias for reportThreadStacks(String.format(format, args))
*/
public static void reportThreadStacks(final String format, final Object... args) {
reportThreadStacks(String.format(format, args));
}
private static StringBuilder buildSpoilerHeader(StringBuilder sb, String reportTitle) {
sb.append("[spoiler=").append(reportTitle).append("][code]");
sb.append("\nForge Version: ").append(BuildInfo.getVersionString());
sb.append("\nOperating System: ").append(System.getProperty("os.name"))
.append(" ").append(System.getProperty("os.version"))
.append(" ").append(System.getProperty("os.arch"));
sb.append("\nJava Version: ").append(System.getProperty("java.version"))
.append(" ").append(System.getProperty("java.vendor"));
return sb;
}
private static StringBuilder buildSpoilerFooter(StringBuilder sb) {
sb.append("[/code][/spoiler]");
return sb;
}
public static void copyAndGoToForums(String text) {
try {
// copy text to clipboard
StringSelection ss = new StringSelection(text);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, null);
// browse to url
Desktop.getDesktop().browse(new URI(FORUM_URL));
}
catch (Exception ex) {
SOptionPane.showMessageDialog("Sorry, a problem occurred while opening the forum in your default browser.",
"A problem occurred", SOptionPane.ERROR_ICON);
}
}
public static void saveToFile(String text) {
File f;
long curTime = System.currentTimeMillis();
for (int i = 0;; i++) {
final String name = String.format("%TF-%02d.txt", curTime, i);
f = new File(name);
if (!f.exists()) {
break;
}
}
f = GuiBase.getInterface().getSaveFile(f);
try {
final BufferedWriter bw = new BufferedWriter(new FileWriter(f));
bw.write(text);
bw.close();
}
catch (final IOException ex) {
SOptionPane.showMessageDialog("There was an error during saving. Sorry!\n" + ex,
"Error saving file", SOptionPane.ERROR_ICON);
}
}
// disable instantiation
private BugReporter() { }
}

View File

@@ -20,8 +20,6 @@ package forge.error;
import com.esotericsoftware.minlog.Log;
import forge.GuiBase;
import java.lang.Thread.UncaughtExceptionHandler;
/**
@@ -50,7 +48,7 @@ public class ExceptionHandler implements UncaughtExceptionHandler {
/** {@inheritDoc} */
@Override
public final void uncaughtException(final Thread t, final Throwable ex) {
GuiBase.getInterface().reportException(ex);
BugReporter.reportException(ex);
}
/**
@@ -61,6 +59,6 @@ public class ExceptionHandler implements UncaughtExceptionHandler {
* a {@link java.lang.Throwable} object.
*/
public final void handle(final Throwable ex) {
GuiBase.getInterface().reportException(ex);
BugReporter.reportException(ex);
}
}

View File

@@ -7,8 +7,8 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import forge.GuiBase;
import forge.deck.CardPool;
import forge.error.BugReporter;
import forge.item.PaperCard;
import forge.model.FModel;
import forge.properties.ForgeConstants;
@@ -95,7 +95,7 @@ public class GauntletIO {
return data;
} catch (final Exception ex) {
GuiBase.getInterface().reportException(ex, "Error loading Gauntlet Data");
BugReporter.reportException(ex, "Error loading Gauntlet Data");
throw new RuntimeException(ex);
} finally {
if (null != zin) {
@@ -110,7 +110,7 @@ public class GauntletIO {
final XStream xStream = GauntletIO.getSerializer(false);
GauntletIO.savePacked(xStream, gd0);
} catch (final Exception ex) {
GuiBase.getInterface().reportException(ex, "Error saving Gauntlet Data.");
BugReporter.reportException(ex, "Error saving Gauntlet Data.");
throw new RuntimeException(ex);
}
}

View File

@@ -1,5 +1,6 @@
package forge.interfaces;
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@@ -35,16 +36,15 @@ public interface IGuiBase {
String getInstallRoot();
String getAssetsDir();
boolean mayShowCard(Card card);
void reportBug(String details);
void reportException(Throwable ex);
void reportException(Throwable ex, String message);
ISkinImage getUnskinnedIcon(String path);
void showBugReportDialog(String title, String text, boolean showExitAppBtn);
int showOptionDialog(String message, String title, FSkinProp icon, String[] options, int defaultOption);
<T> T showInputDialog(String message, String title, FSkinProp icon, T initialInput, T[] inputOptions);
<T> List<T> getChoices(final String message, final int min, final int max, final Collection<T> choices, final T selected, final Function<T, String> display);
<T> List<T> order(final String title, final String top, final int remainingObjectsMin, final int remainingObjectsMax,
final List<T> sourceChoices, final List<T> destChoices, final Card referenceCard, final boolean sideboardingMode);
String showFileDialog(String title, String defaultDir);
File getSaveFile(File defaultFile);
void showCardList(final String title, final String message, final List<PaperCard> list);
void fireEvent(UiEvent e);
void setCard(Card card);

View File

@@ -2,6 +2,7 @@ package forge.match.input;
import forge.FThreads;
import forge.GuiBase;
import forge.error.BugReporter;
import java.util.concurrent.CountDownLatch;
@@ -17,8 +18,9 @@ public abstract class InputSyncronizedBase extends InputBase implements InputSyn
FThreads.assertExecutedByEdt(false);
try{
cdlDone.await();
} catch (InterruptedException e) {
GuiBase.getInterface().reportException(e);
}
catch (InterruptedException e) {
BugReporter.reportException(e);
}
}

View File

@@ -1,6 +1,6 @@
package forge.net;
import forge.GuiBase;
import forge.error.BugReporter;
import forge.net.client.NetClient;
import org.eclipse.jetty.server.Connector;
@@ -69,8 +69,9 @@ public class NetServer {
public void send(String data) {
try {
_connection.sendMessage(data);
} catch (IOException e) {
GuiBase.getInterface().reportException(e);
}
catch (IOException e) {
BugReporter.reportException(e);
}
}
@@ -109,7 +110,7 @@ public class NetServer {
String host = connector.getHost();
serverUri = new URI(String.format("ws://%s:%d/", host == null ? "localhost" : host ,port));
} catch (Exception e) {
GuiBase.getInterface().reportException(e);
BugReporter.reportException(e);
}
System.out.println("Server started @ " + serverUri);
@@ -124,7 +125,7 @@ public class NetServer {
srv.stop();
portNumber = -1;
} catch (Exception e) {
GuiBase.getInterface().reportException(e);
BugReporter.reportException(e);
}
}

View File

@@ -24,11 +24,11 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import forge.GuiBase;
import forge.card.CardEdition;
import forge.deck.CardPool;
import forge.deck.Deck;
import forge.deck.DeckSection;
import forge.error.BugReporter;
import forge.item.*;
import forge.model.FModel;
import forge.properties.ForgeConstants;
@@ -120,15 +120,16 @@ public class QuestDataIO {
if (data.getVersionNumber() != QuestData.CURRENT_VERSION_NUMBER) {
try {
QuestDataIO.updateSaveFile(data, bigXML, xmlSaveFile.getName().replace(".dat", ""));
} catch (final Exception e) {
GuiBase.getInterface().reportException(e);
}
catch (final Exception e) {
BugReporter.reportException(e);
}
}
return data;
}
catch (final Exception ex) {
GuiBase.getInterface().reportException(ex, "Error loading Quest Data");
BugReporter.reportException(ex, "Error loading Quest Data");
throw new RuntimeException(ex);
}
}
@@ -358,7 +359,7 @@ public class QuestDataIO {
}
catch (final Exception ex) {
GuiBase.getInterface().reportException(ex, "Error saving Quest Data.");
BugReporter.reportException(ex, "Error saving Quest Data.");
throw new RuntimeException(ex);
}
}