Merge remote-tracking branch 'origin/GP-65-dragonmacher-error-dialog'

This commit is contained in:
ghidravore
2020-08-21 11:19:51 -04:00
12 changed files with 690 additions and 436 deletions
@@ -0,0 +1,47 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package docking;
import utility.function.Callback;
/**
* A dialog that is meant to be extended for showing exceptions
*/
abstract class AbstractErrDialog extends DialogComponentProvider {
// at some point, there are too many exceptions to show
protected static final int MAX_EXCEPTIONS = 100;
protected static final String TITLE_TEXT = "Multiple Errors";
private Callback closedCallback = Callback.dummy();
protected AbstractErrDialog(String title) {
super(title, true, false, true, false);
}
@Override
protected final void dialogClosed() {
closedCallback.call();
}
abstract void addException(String message, Throwable t);
abstract int getExceptionCount();
void setClosedCallback(Callback callback) {
closedCallback = Callback.dummyIfNull(callback);
}
}
@@ -17,16 +17,23 @@ package docking;
import java.awt.Component; import java.awt.Component;
import java.awt.Window; import java.awt.Window;
import java.io.*;
import docking.widgets.OkDialog;
import docking.widgets.OptionDialog; import docking.widgets.OptionDialog;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.MultipleCauses; import ghidra.util.exception.MultipleCauses;
public class DockingErrorDisplay implements ErrorDisplay { public class DockingErrorDisplay implements ErrorDisplay {
private static final int TRACE_BUFFER_SIZE = 250; /**
* Error dialog used to append exceptions.
*
* <p>While this dialog is showing all new exceptions will be added to the dialog. When
* this dialog is closed, this reference will be cleared.
*
* <p>Note: all use of this variable <b>must be on the Swing thread</b> to avoid thread
* visibility issues.
*/
private static AbstractErrDialog activeDialog;
ConsoleErrorDisplay consoleDisplay = new ConsoleErrorDisplay(); ConsoleErrorDisplay consoleDisplay = new ConsoleErrorDisplay();
@@ -52,8 +59,8 @@ public class DockingErrorDisplay implements ErrorDisplay {
private void displayMessage(MessageType messageType, ErrorLogger errorLogger, Object originator, private void displayMessage(MessageType messageType, ErrorLogger errorLogger, Object originator,
Component parent, String title, Object message, Throwable throwable) { Component parent, String title, Object message, Throwable throwable) {
int dialogType = OptionDialog.PLAIN_MESSAGE;
int dialogType = OptionDialog.PLAIN_MESSAGE;
String messageString = message != null ? message.toString() : null; String messageString = message != null ? message.toString() : null;
String rawMessage = HTMLUtilities.fromHTML(messageString); String rawMessage = HTMLUtilities.fromHTML(messageString);
switch (messageType) { switch (messageType) {
@@ -75,7 +82,7 @@ public class DockingErrorDisplay implements ErrorDisplay {
break; break;
} }
showDialog(title, message, throwable, dialogType, messageString, getWindow(parent)); showDialog(title, throwable, dialogType, messageString, getWindow(parent));
} }
private Component getWindow(Component component) { private Component getWindow(Component component) {
@@ -85,33 +92,36 @@ public class DockingErrorDisplay implements ErrorDisplay {
return component; return component;
} }
private void showDialog(final String title, final Object message, final Throwable throwable, private void showDialog(final String title, final Throwable throwable,
final int dialogType, final String messageString, final Component parent) { final int dialogType, final String messageString, final Component parent) {
SystemUtilities.runIfSwingOrPostSwingLater( Swing.runIfSwingOrRunLater(
() -> doShowDialog(title, message, throwable, dialogType, messageString, parent)); () -> showDialogOnSwing(title, throwable, dialogType, messageString, parent));
} }
private void doShowDialog(final String title, final Object message, final Throwable throwable, private void showDialogOnSwing(String title, Throwable throwable,
int dialogType, String messageString, Component parent) { int dialogType, String messageString, Component parent) {
DialogComponentProvider dialog = null;
if (throwable != null) { if (activeDialog != null) {
dialog = createErrorDialog(title, message, throwable, messageString); activeDialog.addException(messageString, throwable);
return;
} }
else {
dialog = new OkDialog(title, messageString, dialogType); activeDialog = createErrorDialog(title, throwable, messageString);
} activeDialog.setClosedCallback(() -> {
DockingWindowManager.showDialog(parent, dialog); activeDialog.setClosedCallback(null);
activeDialog = null;
});
DockingWindowManager.showDialog(parent, activeDialog);
} }
private DialogComponentProvider createErrorDialog(final String title, final Object message, private AbstractErrDialog createErrorDialog(String title, Throwable throwable,
final Throwable throwable, String messageString) { String messageString) {
if (containsMultipleCauses(throwable)) { if (containsMultipleCauses(throwable)) {
return new ErrLogExpandableDialog(title, messageString, throwable); return new ErrLogExpandableDialog(title, messageString, throwable);
} }
return ErrLogDialog.createExceptionDialog(title, messageString, return ErrLogDialog.createExceptionDialog(title, messageString, throwable);
buildStackTrace(throwable, message == null ? throwable.getMessage() : messageString));
} }
private boolean containsMultipleCauses(Throwable throwable) { private boolean containsMultipleCauses(Throwable throwable) {
@@ -125,34 +135,4 @@ public class DockingErrorDisplay implements ErrorDisplay {
return containsMultipleCauses(throwable.getCause()); return containsMultipleCauses(throwable.getCause());
} }
/**
* Build a displayable stack trace from a Throwable
*
* @param t the throwable
* @param msg message prefix
* @return multi-line stack trace
*/
private String buildStackTrace(Throwable t, String msg) {
StringBuffer sb = new StringBuffer(TRACE_BUFFER_SIZE);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos);
if (msg != null) {
ps.println(msg);
}
t.printStackTrace(ps);
sb.append(baos.toString());
ps.close();
try {
baos.close();
}
catch (IOException e) {
// shouldn't happen--not really connected to the system
}
return sb.toString();
}
} }
File diff suppressed because it is too large Load Diff
@@ -32,12 +32,12 @@ import docking.widgets.label.GHtmlLabel;
import docking.widgets.tree.*; import docking.widgets.tree.*;
import docking.widgets.tree.support.GTreeDragNDropHandler; import docking.widgets.tree.support.GTreeDragNDropHandler;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.exception.*; import ghidra.util.exception.MultipleCauses;
import ghidra.util.html.HTMLElement; import ghidra.util.html.HTMLElement;
import resources.ResourceManager; import resources.ResourceManager;
import util.CollectionUtils; import util.CollectionUtils;
public class ErrLogExpandableDialog extends DialogComponentProvider { public class ErrLogExpandableDialog extends AbstractErrDialog {
public static ImageIcon IMG_REPORT = ResourceManager.loadImage("images/report.png"); public static ImageIcon IMG_REPORT = ResourceManager.loadImage("images/report.png");
public static ImageIcon IMG_EXCEPTION = ResourceManager.loadImage("images/exception.png"); public static ImageIcon IMG_EXCEPTION = ResourceManager.loadImage("images/exception.png");
public static ImageIcon IMG_FRAME_ELEMENT = public static ImageIcon IMG_FRAME_ELEMENT =
@@ -53,113 +53,21 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
private static boolean showingDetails = false; private static boolean showingDetails = false;
protected ReportRootNode root; protected ReportRootNode root;
protected GTree excTree; protected GTree tree;
private List<Throwable> errors = new ArrayList<>();
/** This spacer addresses the optical impression that the message panel changes size when showing details */ /** This spacer addresses the optical impression that the message panel changes size when showing details */
protected Component horizontalSpacer; protected Component horizontalSpacer;
protected JButton detailButton; protected JButton detailButton;
protected JButton sendButton; protected JButton sendButton;
protected boolean hasConsole = false;
protected JPopupMenu popup; protected JPopupMenu popup;
protected static class ExcTreeTransferHandler extends TransferHandler protected ErrLogExpandableDialog(String title, String msg, Throwable throwable) {
implements GTreeDragNDropHandler { super(title);
protected ReportRootNode root; errors.add(throwable);
public ExcTreeTransferHandler(ReportRootNode root) {
this.root = root;
}
@Override
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> transferNodes) {
return new DataFlavor[] { DataFlavor.stringFlavor };
}
@Override
protected Transferable createTransferable(JComponent c) {
ArrayList<GTreeNode> nodes = new ArrayList<>();
for (TreePath path : ((JTree) c).getSelectionPaths()) {
nodes.add((GTreeNode) path.getLastPathComponent());
}
try {
return new StringSelection(
(String) getTransferData(nodes, DataFlavor.stringFlavor));
}
catch (UnsupportedFlavorException e) {
Msg.debug(this, e.getMessage(), e);
}
return null;
}
@Override
public Object getTransferData(List<GTreeNode> transferNodes, DataFlavor flavor)
throws UnsupportedFlavorException {
if (flavor != DataFlavor.stringFlavor) {
throw new UnsupportedFlavorException(flavor);
}
if (transferNodes.isEmpty()) {
return null;
}
if (transferNodes.size() == 1) {
GTreeNode node = transferNodes.get(0);
if (node instanceof NodeWithText) {
return ((NodeWithText) node).collectReportText(transferNodes, 0).trim();
}
return null;
}
return root.collectReportText(transferNodes, 0).trim();
}
@Override
public boolean isStartDragOk(List<GTreeNode> dragUserData, int dragAction) {
for (GTreeNode node : dragUserData) {
if (node instanceof NodeWithText) {
return true;
}
}
return false;
}
@Override
public int getSupportedDragActions() {
return DnDConstants.ACTION_COPY;
}
@Override
public int getSourceActions(JComponent c) {
return COPY;
}
@Override
public boolean isDropSiteOk(GTreeNode destUserData, DataFlavor[] flavors, int dropAction) {
return false;
}
@Override
public void drop(GTreeNode destUserData, Transferable transferable, int dropAction) {
throw new UnsupportedOperationException();
}
}
public ErrLogExpandableDialog(String title, String msg, MultipleCauses mc) {
this(title, msg, mc.getCauses(), null, true, true);
}
public ErrLogExpandableDialog(String title, String msg, Throwable exc) {
this(title, msg, Collections.singletonList(exc), HasConsoleText.Util.get(exc), true, true);
}
public ErrLogExpandableDialog(String title, String msg, Collection<Throwable> report) {
this(title, msg, report, null, false, false);
}
protected ErrLogExpandableDialog(String title, String msg, Collection<Throwable> report,
String console, boolean modal, boolean hasDismiss) {
super(title, modal);
hasConsole = console != null;
popup = new JPopupMenu(); popup = new JPopupMenu();
JMenuItem menuCopy = new JMenuItem("Copy"); JMenuItem menuCopy = new JMenuItem("Copy");
menuCopy.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME)); menuCopy.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME));
@@ -173,13 +81,12 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
msgPanel.setLayout(new BorderLayout(16, 16)); msgPanel.setLayout(new BorderLayout(16, 16));
msgPanel.setBorder(new EmptyBorder(16, 16, 16, 16)); msgPanel.setBorder(new EmptyBorder(16, 16, 16, 16));
{ {
JLabel msgText = new GHtmlLabel(getHTML(msg, report)) { JLabel msgText = new GHtmlLabel(getHTML(msg, CollectionUtils.asSet(throwable))) {
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
// when rendering HTML the label can expand larger than the screen; // rendering HTML the label can expand larger than the screen; keep it reasonable
// keep it reasonable
Dimension size = super.getPreferredSize(); Dimension size = super.getPreferredSize();
size.width = 500; size.width = 300;
return size; return size;
} }
}; };
@@ -206,7 +113,7 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
msgPanel.add(buttonBox, BorderLayout.EAST); msgPanel.add(buttonBox, BorderLayout.EAST);
horizontalSpacer = Box.createVerticalStrut(10); horizontalSpacer = Box.createVerticalStrut(10);
horizontalSpacer.setVisible(showingDetails | hasConsole); horizontalSpacer.setVisible(showingDetails);
msgPanel.add(horizontalSpacer, BorderLayout.SOUTH); msgPanel.add(horizontalSpacer, BorderLayout.SOUTH);
} }
workPanel.add(msgPanel, BorderLayout.NORTH); workPanel.add(msgPanel, BorderLayout.NORTH);
@@ -214,29 +121,8 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
Box workBox = Box.createVerticalBox(); Box workBox = Box.createVerticalBox();
{ {
if (hasConsole) { root = new ReportRootNode(getTitle(), CollectionUtils.asSet(throwable));
JTextArea consoleText = new JTextArea(console); tree = new GTree(root) {
JScrollPane consoleScroll =
new JScrollPane(consoleText, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED) {
@Override
public Dimension getPreferredSize() {
Dimension dim = super.getPreferredSize();
dim.height = 400;
dim.width = 800; // trial and error?
return dim;
}
};
consoleText.setEditable(false);
consoleText.setBackground(Color.BLACK);
consoleText.setForeground(Color.WHITE);
consoleText.setFont(Font.decode("Monospaced"));
workBox.add(consoleScroll);
}
root = new ReportRootNode(getTitle(), report);
excTree = new GTree(root) {
@Override @Override
public Dimension getPreferredSize() { public Dimension getPreferredSize() {
@@ -249,19 +135,19 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
for (GTreeNode node : CollectionUtils.asIterable(root.iterator(true))) { for (GTreeNode node : CollectionUtils.asIterable(root.iterator(true))) {
if (node instanceof ReportExceptionNode) { if (node instanceof ReportExceptionNode) {
excTree.expandTree(node); tree.expandTree(node);
} }
} }
excTree.setSelectedNode(root.getChild(0)); tree.setSelectedNode(root.getChild(0));
excTree.setVisible(showingDetails); tree.setVisible(showingDetails);
ExcTreeTransferHandler handler = new ExcTreeTransferHandler(root); ExcTreeTransferHandler handler = new ExcTreeTransferHandler(root);
excTree.setDragNDropHandler(handler); tree.setDragNDropHandler(handler);
excTree.setTransferHandler(handler); tree.setTransferHandler(handler);
ActionMap map = excTree.getActionMap(); ActionMap map = tree.getActionMap();
map.put(TransferHandler.getCopyAction().getValue(Action.NAME), map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
TransferHandler.getCopyAction()); TransferHandler.getCopyAction());
excTree.addMouseListener(new MouseAdapter() { tree.addMouseListener(new MouseAdapter() {
@Override @Override
public void mousePressed(MouseEvent e) { public void mousePressed(MouseEvent e) {
maybeShowPopup(e); maybeShowPopup(e);
@@ -279,22 +165,19 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
} }
}); });
workBox.add(excTree); workBox.add(tree);
} }
workPanel.add(workBox, BorderLayout.CENTER); workPanel.add(workBox, BorderLayout.CENTER);
repack(); repack();
addWorkPanel(workPanel); addWorkPanel(workPanel);
if (hasDismiss) { addDismissButton();
addDismissButton();
}
} }
private String getHTML(String msg, Collection<Throwable> report) { private String getHTML(String msg, Collection<Throwable> report) {
// //
// TODO
// Usage question: The content herein will be escaped unless you call addHTMLContenet(). // Usage question: The content herein will be escaped unless you call addHTMLContenet().
// Further, clients can provide messages that contain HTML. Is there a // Further, clients can provide messages that contain HTML. Is there a
// use case where we want to show escaped HTML content? // use case where we want to show escaped HTML content?
@@ -332,14 +215,6 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
String htmlTMsg = addBR(tMsg); String htmlTMsg = addBR(tMsg);
body.addElement("p").addHTMLContent(htmlTMsg); body.addElement("p").addHTMLContent(htmlTMsg);
if (t instanceof CausesImportant) { // I choose not to recurse
HTMLElement ul = body.addElement("ul");
for (Throwable ts : MultipleCauses.Util.iterCauses(t)) {
String tsMsg = getMessage(ts);
String htmlTSMsg = addBR(tsMsg);
ul.addElement("li").addHTMLContent(htmlTSMsg);
}
}
} }
return html.toString(); return html.toString();
} }
@@ -357,15 +232,15 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return t.getClass().getSimpleName(); return t.getClass().getSimpleName();
} }
void detailCallback() { private void detailCallback() {
showingDetails = !showingDetails; showingDetails = !showingDetails;
excTree.setVisible(showingDetails); tree.setVisible(showingDetails);
horizontalSpacer.setVisible(showingDetails | hasConsole); horizontalSpacer.setVisible(showingDetails);
detailButton.setText(showingDetails ? CLOSE : DETAIL); detailButton.setText(showingDetails ? CLOSE : DETAIL);
repack(); repack();
} }
void sendCallback() { private void sendCallback() {
String details = root.collectReportText(null, 0).trim(); String details = root.collectReportText(null, 0).trim();
String title = getTitle(); String title = getTitle();
close(); close();
@@ -379,6 +254,24 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return dim; return dim;
} }
@Override
public void addException(String message, Throwable t) {
int n = errors.size();
if (n > MAX_EXCEPTIONS) {
return;
}
errors.add(t);
setTitle(TITLE_TEXT + " (" + n + 1 + ")"); // signal the new error
root.addNode(new ReportExceptionNode(t));
}
@Override
int getExceptionCount() {
return root.getChildCount();
}
static interface NodeWithText { static interface NodeWithText {
public String getReportText(); public String getReportText();
@@ -528,9 +421,6 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
@Override @Override
public String getReportText() { public String getReportText() {
if (exc instanceof HasConsoleText) {
return getName() + "\n" + HasConsoleText.Util.get(exc);
}
return getName(); return getName();
} }
@@ -661,6 +551,87 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return false; return false;
} }
} }
private static class ExcTreeTransferHandler extends TransferHandler
implements GTreeDragNDropHandler {
protected ReportRootNode root;
public ExcTreeTransferHandler(ReportRootNode root) {
this.root = root;
}
@Override
public DataFlavor[] getSupportedDataFlavors(List<GTreeNode> transferNodes) {
return new DataFlavor[] { DataFlavor.stringFlavor };
}
@Override
protected Transferable createTransferable(JComponent c) {
ArrayList<GTreeNode> nodes = new ArrayList<>();
for (TreePath path : ((JTree) c).getSelectionPaths()) {
nodes.add((GTreeNode) path.getLastPathComponent());
}
try {
return new StringSelection(
(String) getTransferData(nodes, DataFlavor.stringFlavor));
}
catch (UnsupportedFlavorException e) {
Msg.debug(this, e.getMessage(), e);
}
return null;
}
@Override
public Object getTransferData(List<GTreeNode> transferNodes, DataFlavor flavor)
throws UnsupportedFlavorException {
if (flavor != DataFlavor.stringFlavor) {
throw new UnsupportedFlavorException(flavor);
}
if (transferNodes.isEmpty()) {
return null;
}
if (transferNodes.size() == 1) {
GTreeNode node = transferNodes.get(0);
if (node instanceof NodeWithText) {
return ((NodeWithText) node).collectReportText(transferNodes, 0).trim();
}
return null;
}
return root.collectReportText(transferNodes, 0).trim();
}
@Override
public boolean isStartDragOk(List<GTreeNode> dragUserData, int dragAction) {
for (GTreeNode node : dragUserData) {
if (node instanceof NodeWithText) {
return true;
}
}
return false;
}
@Override
public int getSupportedDragActions() {
return DnDConstants.ACTION_COPY;
}
@Override
public int getSourceActions(JComponent c) {
return COPY;
}
@Override
public boolean isDropSiteOk(GTreeNode destUserData, DataFlavor[] flavors, int dropAction) {
return false;
}
@Override
public void drop(GTreeNode destUserData, Transferable transferable, int dropAction) {
throw new UnsupportedOperationException();
}
}
} }
class TransferActionListener implements ActionListener, PropertyChangeListener { class TransferActionListener implements ActionListener, PropertyChangeListener {
@@ -102,7 +102,7 @@ public class ScrollableTextArea extends JScrollPane {
} }
/** /**
* Appends the text to the text area maintained in this scrollpane * Appends the text to the text area maintained in this scroll pane
* @param text the text to append. * @param text the text to append.
*/ */
public void append(String text) { public void append(String text) {
@@ -111,13 +111,15 @@ public class ScrollableTextArea extends JScrollPane {
/** /**
* Returns the number of lines current set in the text area * Returns the number of lines current set in the text area
* @return the count
*/ */
public int getLineCount() { public int getLineCount() {
return textArea.getLineCount(); return textArea.getLineCount();
} }
/** /**
* Returns the tabsize set in the text area * Returns the tab size set in the text area
* @return the size
*/ */
public int getTabSize() { public int getTabSize() {
return textArea.getTabSize(); return textArea.getTabSize();
@@ -125,6 +127,7 @@ public class ScrollableTextArea extends JScrollPane {
/** /**
* Returns the total area height of the text area (row height * line count) * Returns the total area height of the text area (row height * line count)
* @return the height
*/ */
public int getTextAreaHeight() { public int getTextAreaHeight() {
return (textArea.getAreaHeight()); return (textArea.getAreaHeight());
@@ -132,6 +135,7 @@ public class ScrollableTextArea extends JScrollPane {
/** /**
* Returns the visible height of the text area * Returns the visible height of the text area
* @return the height
*/ */
public int getTextVisibleHeight() { public int getTextVisibleHeight() {
return textArea.getVisibleHeight(); return textArea.getVisibleHeight();
@@ -200,6 +204,7 @@ public class ScrollableTextArea extends JScrollPane {
/** /**
* Returns the text contained within the text area * Returns the text contained within the text area
* @return the text
*/ */
public String getText() { public String getText() {
return textArea.getText(); return textArea.getText();
@@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.table.column;
import java.awt.Component;
import java.util.Date;
import javax.swing.JLabel;
import docking.widgets.table.GTableCellRenderingData;
import ghidra.docking.settings.Settings;
import ghidra.util.DateUtils;
/**
* A renderer for clients that wish to display a {@link Date} as a timestamp with the
* date and time.
*/
public class DefaultTimestampRenderer extends AbstractGColumnRenderer<Date> {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel label = (JLabel) super.getTableCellRendererComponent(data);
Date value = (Date) data.getValue();
if (value != null) {
label.setText(DateUtils.formatDateTimestamp(value));
}
return label;
}
@Override
public String getFilterString(Date t, Settings settings) {
return DateUtils.formatDateTimestamp(t);
}
@Override
public ColumnConstraintFilterMode getColumnConstraintFilterMode() {
// This allows for text filtering in the table and date filtering on columns
return ColumnConstraintFilterMode.ALLOW_ALL_FILTERS;
}
}
@@ -35,7 +35,7 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
DockingErrorDisplay display = new DockingErrorDisplay(); DockingErrorDisplay display = new DockingErrorDisplay();
DefaultErrorLogger logger = new DefaultErrorLogger(); DefaultErrorLogger logger = new DefaultErrorLogger();
Exception exception = new Exception("My test exception"); Exception exception = new Exception("My test exception");
doDisplay(display, logger, exception); reportException(display, logger, exception);
assertErrLogDialog(); assertErrLogDialog();
} }
@@ -46,11 +46,29 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
DefaultErrorLogger logger = new DefaultErrorLogger(); DefaultErrorLogger logger = new DefaultErrorLogger();
Exception nestedException = new Exception("My nested test exception"); Exception nestedException = new Exception("My nested test exception");
Exception exception = new Exception("My test exception", nestedException); Exception exception = new Exception("My test exception", nestedException);
doDisplay(display, logger, exception); reportException(display, logger, exception);
assertErrLogDialog(); assertErrLogDialog();
} }
@Test
public void testDefaultErrorDisplay_MultipleAsynchronousExceptions() {
DockingErrorDisplay display = new DockingErrorDisplay();
DefaultErrorLogger logger = new DefaultErrorLogger();
Exception exception = new Exception("My test exception");
reportException(display, logger, exception);
ErrLogDialog dialog = getErrLogDialog();
assertExceptionCount(dialog, 1);
reportException(display, logger, new NullPointerException("It is null!"));
assertExceptionCount(dialog, 2);
close(dialog);
}
@Test @Test
public void testMultipleCausesErrorDisplay() { public void testMultipleCausesErrorDisplay() {
DockingErrorDisplay display = new DockingErrorDisplay(); DockingErrorDisplay display = new DockingErrorDisplay();
@@ -58,43 +76,51 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
Throwable firstCause = new Exception("My test exception - first cause"); Throwable firstCause = new Exception("My test exception - first cause");
MultipleCauses exception = new MultipleCauses(Collections.singletonList(firstCause)); MultipleCauses exception = new MultipleCauses(Collections.singletonList(firstCause));
doDisplay(display, logger, exception); reportException(display, logger, exception);
assertErrLogExpandableDialog(); ErrLogExpandableDialog dialog = assertErrLogExpandableDialog();
assertExceptionCount(dialog, 1);
reportException(display, logger, new NullPointerException("It is null!"));
assertExceptionCount(dialog, 2);
close(dialog);
} }
private void assertErrLogExpandableDialog() { private void assertExceptionCount(AbstractErrDialog errDialog, int n) {
Window w = waitForWindow(TEST_TITLE, 2000);
assertNotNull(w);
final ErrLogExpandableDialog errDialog = int actual = errDialog.getExceptionCount();
assertEquals(n, actual);
}
private ErrLogExpandableDialog assertErrLogExpandableDialog() {
Window w = waitForWindow(TEST_TITLE);
ErrLogExpandableDialog errDialog =
getDialogComponentProvider(w, ErrLogExpandableDialog.class); getDialogComponentProvider(w, ErrLogExpandableDialog.class);
assertNotNull(errDialog); assertNotNull(errDialog);
return errDialog;
runSwing(new Runnable() {
@Override
public void run() {
errDialog.close();
}
});
} }
private void assertErrLogDialog() { private void assertErrLogDialog() {
Window w = waitForWindow(TEST_TITLE, 2000); Window w = waitForWindow(TEST_TITLE);
assertNotNull(w); assertNotNull(w);
final ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class); ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
assertNotNull(errDialog); assertNotNull(errDialog);
close(errDialog);
runSwing(new Runnable() {
@Override
public void run() {
errDialog.close();
}
});
} }
private void doDisplay(final DockingErrorDisplay display, final DefaultErrorLogger logger, private ErrLogDialog getErrLogDialog() {
Window w = waitForWindow(TEST_TITLE);
assertNotNull(w);
ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
assertNotNull(errDialog);
return errDialog;
}
private void reportException(final DockingErrorDisplay display, final DefaultErrorLogger logger,
final Throwable throwable) { final Throwable throwable) {
runSwing(new Runnable() { runSwing(new Runnable() {
@Override @Override
@@ -1,36 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.exception;
import java.util.Arrays;
import org.apache.commons.lang3.StringUtils;
public interface CausesImportant {
public static class Util {
public static String getMessages(Throwable exc) {
if (exc instanceof CausesImportant) {
StringBuilder result = new StringBuilder(exc.getMessage());
for (Throwable cause : MultipleCauses.Util.iterCauses(exc)) {
result.append(
StringUtils.join(Arrays.asList(cause.getMessage().split("\n")), "\n\t"));
}
return result.toString();
}
return exc.getMessage();
}
}
}
@@ -1,30 +0,0 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.exception;
public interface HasConsoleText {
public String getConsoleText();
public static class Util {
public static String get(Throwable exc) {
if (exc instanceof HasConsoleText) {
return ((HasConsoleText) exc).getConsoleText();
}
return null;
}
}
}
@@ -89,7 +89,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
protected DBHandle dbHandle; protected DBHandle dbHandle;
private AddressMap addrMap; private AddressMap addrMap;
private ErrorHandler errHandler; private ErrorHandler errHandler = new DbErrorHandler();
private DataTypeConflictHandler currentHandler; private DataTypeConflictHandler currentHandler;
private CategoryDB root; private CategoryDB root;
@@ -168,12 +168,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
*/ */
protected DataTypeManagerDB() { protected DataTypeManagerDB() {
this.lock = new Lock("DataTypeManagerDB"); this.lock = new Lock("DataTypeManagerDB");
errHandler = new ErrorHandler() {
@Override
public void dbError(IOException e) {
Msg.showError(this, null, "IO ERROR", e.getMessage(), e);
}
};
try { try {
dbHandle = new DBHandle(); dbHandle = new DBHandle();
int id = startTransaction(""); int id = startTransaction("");
@@ -213,13 +208,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
") for read-only Datatype Archive: " + packedDBfile.getAbsolutePath()); ") for read-only Datatype Archive: " + packedDBfile.getAbsolutePath());
} }
errHandler = new ErrorHandler() {
@Override
public void dbError(IOException e) {
Msg.showError(this, null, "IO ERROR", e.getMessage(), e);
}
};
// Open packed database archive // Open packed database archive
boolean openSuccess = false; boolean openSuccess = false;
PackedDatabase pdb = null; PackedDatabase pdb = null;
@@ -4089,6 +4077,20 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
} }
} }
} }
private class DbErrorHandler implements ErrorHandler {
@Override
public void dbError(IOException e) {
String message = e.getMessage();
if (e instanceof ClosedException) {
message = "Data type archive is closed: " + getName();
}
Msg.showError(this, null, "IO ERROR", message, e);
}
}
} }
/** /**
@@ -499,14 +499,31 @@ public class ReflectionUtilities {
* @return the string * @return the string
*/ */
public static String stackTraceToString(Throwable t) { public static String stackTraceToString(Throwable t) {
StringBuffer sb = new StringBuffer(); return stackTraceToString(t.getMessage(), t);
}
/**
* Turns the given {@link Throwable} into a String version of its
* {@link Throwable#printStackTrace()} method.
*
* @param message the preferred message to use. If null, the throwable message will be used
* @param t the throwable
* @return the string
*/
public static String stackTraceToString(String message, Throwable t) {
StringBuilder sb = new StringBuilder();
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(baos); PrintStream ps = new PrintStream(baos);
String msg = t.getMessage(); if (message != null) {
if (msg != null) { ps.println(message);
ps.println(msg); }
else {
String throwableMessage = t.getMessage();
if (throwableMessage != null) {
ps.println(throwableMessage);
}
} }
t.printStackTrace(ps); t.printStackTrace(ps);
@@ -55,8 +55,8 @@ public class IntroScreenShots extends GhidraScreenShotGenerator {
@Test @Test
public void testErr_Dialog() { public void testErr_Dialog() {
runSwing(() -> { runSwing(() -> {
ErrLogDialog dialog = ErrLogDialog.createLogMessageDialog("Unexpected Error", ErrLogDialog dialog = ErrLogDialog.createExceptionDialog("Unexpected Error",
"Oops, this is really bad!", ""); "Oops, this is really bad!", new Throwable());
DockingWindowManager.showDialog(null, dialog); DockingWindowManager.showDialog(null, dialog);
}, false); }, false);
waitForSwing(); waitForSwing();