diff --git a/Ghidra/Framework/Docking/src/main/java/docking/AbstractErrDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/AbstractErrDialog.java
new file mode 100644
index 0000000000..0a97580c63
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/docking/AbstractErrDialog.java
@@ -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);
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java b/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java
index 1a12aa4974..e215803ad5 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/DockingErrorDisplay.java
@@ -17,16 +17,23 @@ package docking;
import java.awt.Component;
import java.awt.Window;
-import java.io.*;
-import docking.widgets.OkDialog;
import docking.widgets.OptionDialog;
import ghidra.util.*;
import ghidra.util.exception.MultipleCauses;
public class DockingErrorDisplay implements ErrorDisplay {
- private static final int TRACE_BUFFER_SIZE = 250;
+ /**
+ * Error dialog used to append exceptions.
+ *
+ *
While this dialog is showing all new exceptions will be added to the dialog. When
+ * this dialog is closed, this reference will be cleared.
+ *
+ *
Note: all use of this variable must be on the Swing thread to avoid thread
+ * visibility issues.
+ */
+ private static AbstractErrDialog activeDialog;
ConsoleErrorDisplay consoleDisplay = new ConsoleErrorDisplay();
@@ -52,8 +59,8 @@ public class DockingErrorDisplay implements ErrorDisplay {
private void displayMessage(MessageType messageType, ErrorLogger errorLogger, Object originator,
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 rawMessage = HTMLUtilities.fromHTML(messageString);
switch (messageType) {
@@ -75,7 +82,7 @@ public class DockingErrorDisplay implements ErrorDisplay {
break;
}
- showDialog(title, message, throwable, dialogType, messageString, getWindow(parent));
+ showDialog(title, throwable, dialogType, messageString, getWindow(parent));
}
private Component getWindow(Component component) {
@@ -85,33 +92,36 @@ public class DockingErrorDisplay implements ErrorDisplay {
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) {
- SystemUtilities.runIfSwingOrPostSwingLater(
- () -> doShowDialog(title, message, throwable, dialogType, messageString, parent));
+ Swing.runIfSwingOrRunLater(
+ () -> 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) {
- DialogComponentProvider dialog = null;
- if (throwable != null) {
- dialog = createErrorDialog(title, message, throwable, messageString);
+
+ if (activeDialog != null) {
+ activeDialog.addException(messageString, throwable);
+ return;
}
- else {
- dialog = new OkDialog(title, messageString, dialogType);
- }
- DockingWindowManager.showDialog(parent, dialog);
+
+ activeDialog = createErrorDialog(title, throwable, messageString);
+ activeDialog.setClosedCallback(() -> {
+ activeDialog.setClosedCallback(null);
+ activeDialog = null;
+ });
+ DockingWindowManager.showDialog(parent, activeDialog);
}
- private DialogComponentProvider createErrorDialog(final String title, final Object message,
- final Throwable throwable, String messageString) {
+ private AbstractErrDialog createErrorDialog(String title, Throwable throwable,
+ String messageString) {
if (containsMultipleCauses(throwable)) {
return new ErrLogExpandableDialog(title, messageString, throwable);
}
- return ErrLogDialog.createExceptionDialog(title, messageString,
- buildStackTrace(throwable, message == null ? throwable.getMessage() : messageString));
+ return ErrLogDialog.createExceptionDialog(title, messageString, throwable);
}
private boolean containsMultipleCauses(Throwable throwable) {
@@ -125,34 +135,4 @@ public class DockingErrorDisplay implements ErrorDisplay {
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();
- }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java
index 5b036ed161..b086f80f1d 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/ErrLogDialog.java
@@ -20,20 +20,35 @@ import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.net.InetAddress;
import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
import javax.swing.*;
import docking.widgets.ScrollableTextArea;
import docking.widgets.label.GHtmlLabel;
import docking.widgets.label.GIconLabel;
+import docking.widgets.table.*;
+import generic.json.Json;
import generic.util.WindowUtilities;
+import ghidra.docking.settings.Settings;
import ghidra.framework.Application;
+import ghidra.framework.plugintool.ServiceProvider;
+import ghidra.framework.plugintool.ServiceProviderStub;
import ghidra.util.HTMLUtilities;
+import ghidra.util.Swing;
+import ghidra.util.table.column.DefaultTimestampRenderer;
+import ghidra.util.table.column.GColumnRenderer;
+import utilities.util.reflection.ReflectionUtilities;
-public class ErrLogDialog extends DialogComponentProvider {
- private static final int TEXT_ROWS = 30;
+/**
+ * A dialog that takes error text and displays it with an option details button. If there is
+ * an {@link ErrorReporter}, then a button is provided to report the error.
+ */
+public class ErrLogDialog extends AbstractErrDialog {
+ private static final int TEXT_ROWS = 20;
private static final int TEXT_COLUMNS = 80;
- private static final int ERROR_BUFFER_SIZE = 1024;
private static final String SEND = "Log Error...";
private static final String DETAIL = "Details >>>";
@@ -46,32 +61,30 @@ public class ErrLogDialog extends DialogComponentProvider {
/** tracks 'details panel' open state across invocations */
private static boolean isShowingDetails = false;
+ private int errorId = 0;
+
// state-dependent gui members
- private ErrorDetailsPanel detailsPanel;
+ private ErrorDetailsSplitPane detailsPane;
private JButton detailsButton;
private JButton sendButton;
private JPanel mainPanel;
private static ErrorReporter errorReporter;
- public static ErrLogDialog createExceptionDialog(String title, String message, String details) {
- return new ErrLogDialog(title, message, details, true);
+ private List errors = new ArrayList<>();
+
+ public static ErrLogDialog createExceptionDialog(String title, String message, Throwable t) {
+ return new ErrLogDialog(title, message, t);
}
- public static ErrLogDialog createLogMessageDialog(String title, String message,
- String details) {
- return new ErrLogDialog(title, message, details, false);
- }
+ private ErrLogDialog(String title, String message, Throwable throwable) {
+ super(title != null ? title : "Error");
+
+ ErrEntry error = new ErrEntry(message, throwable);
+ errors.add(error);
- /**
- * Constructor.
- * Used by the Err class's static methods for logging various
- * kinds of errors: Runtime, System, User, Asserts
- */
- private ErrLogDialog(String title, String message, String details, boolean isException) {
- super(title != null ? title : "Error", true, false, true, false);
setRememberSize(false);
setRememberLocation(false);
- buildMainPanel(message, addUsefulReportingInfo(details), isException);
+ buildMainPanel(message);
}
private String addUsefulReportingInfo(String details) {
@@ -127,13 +140,21 @@ public class ErrLogDialog extends DialogComponentProvider {
return errorReporter;
}
- private void buildMainPanel(String message, String details, boolean isException) {
+ private void buildMainPanel(String message) {
JPanel introPanel = new JPanel(new BorderLayout(10, 10));
introPanel.add(
new GIconLabel(UIManager.getIcon("OptionPane.errorIcon"), SwingConstants.RIGHT),
BorderLayout.WEST);
- introPanel.add(new GHtmlLabel(HTMLUtilities.toHTML(message)), BorderLayout.CENTER);
+ introPanel.add(new GHtmlLabel(HTMLUtilities.toHTML(message)) {
+ @Override
+ public Dimension getPreferredSize() {
+ // rendering HTML the label can expand larger than the screen; keep it reasonable
+ Dimension size = super.getPreferredSize();
+ size.width = 300;
+ return size;
+ }
+ }, BorderLayout.CENTER);
mainPanel = new JPanel(new BorderLayout(10, 20));
mainPanel.add(introPanel, BorderLayout.NORTH);
@@ -141,21 +162,15 @@ public class ErrLogDialog extends DialogComponentProvider {
sendButton = new JButton(SEND);
sendButton.addActionListener(e -> sendDetails());
- detailsPanel = new ErrorDetailsPanel();
detailsButton = new JButton(isShowingDetails ? CLOSE : DETAIL);
detailsButton.addActionListener(e -> {
String label = detailsButton.getText();
showDetails(label.equals(DETAIL));
});
- if (isException) {
- detailsPanel.setExceptionMessage(details);
- }
- else {
- detailsPanel.setLogMessage(details);
- }
+ detailsPane = new ErrorDetailsSplitPane();
- JPanel buttonPanel = new JPanel(new GridLayout(2, 1, 5, 5));
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5));
buttonPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
if (errorReporter != null) {
buttonPanel.add(sendButton);
@@ -163,16 +178,16 @@ public class ErrLogDialog extends DialogComponentProvider {
buttonPanel.add(detailsButton);
introPanel.add(buttonPanel, BorderLayout.EAST);
- mainPanel.add(detailsPanel, BorderLayout.CENTER);
+ mainPanel.add(detailsPane, BorderLayout.CENTER);
addWorkPanel(mainPanel);
addOKButton();
+ setDefaultButton(okButton);
// show the details panel if it was showing previously
- detailsPanel.setVisible(isShowingDetails);
-
-// setHelpLocation(new HelpLocation(HelpTopics.INTRO, "Err"));
+ detailsPane.setVisible(isShowingDetails);
+ detailsPane.selectFirstError();
}
@Override
@@ -185,56 +200,156 @@ public class ErrLogDialog extends DialogComponentProvider {
cancelCallback();
}
- /**
- * Send error details from dialog.
- */
private void sendDetails() {
- String details = detailsPanel.getDetails();
+ String details = detailsPane.getDetails();
String title = getTitle();
close();
errorReporter.report(rootPanel, title, details);
}
- /**
- * opens and closes the details panel; used also by Err when
- * showLog is called from SessionGui Help menu to show details
- * when visible
- */
private void showDetails(boolean visible) {
isShowingDetails = visible;
String label = (visible ? CLOSE : DETAIL);
detailsButton.setText(label);
- detailsPanel.setVisible(visible);
+ detailsPane.setVisible(visible);
repack(); // need to re-pack so the detailsPanel can be hidden correctly
}
- // custom "pack" so the detailsPanel can be shown/hidden correctly
- @Override
- protected void repack() {
-
- // hide the dialog so that the user doesn't see us resize and then move it, which looks
- // awkward
- getDialog().setVisible(false);
-
- detailsPanel.invalidate(); // force to be invalid so resizes correctly
- rootPanel.validate();
-
- super.repack();
-
- // center the dialog after its size changes for a cleaner appearance
- DockingDialog dialog = getDialog();
- Container parent = dialog.getParent();
- Point centerPoint = WindowUtilities.centerOnComponent(parent, dialog);
- dialog.setLocation(centerPoint);
-
- getDialog().setVisible(true);
- }
-
@Override
protected void dialogShown() {
-
- // TODO test that the parent DockingDialog code handles this....
WindowUtilities.ensureOnScreen(getDialog());
+ Swing.runLater(() -> okButton.requestFocusInWindow());
+ }
+
+ @Override
+ void addException(String message, Throwable t) {
+
+ int n = errors.size();
+ if (n > MAX_EXCEPTIONS) {
+ return;
+ }
+
+ errors.add(new ErrEntry(message, t));
+
+ detailsPane.update();
+
+ // signal the new error
+ setTitle(TITLE_TEXT + " (" + n + 1 + ")");
+ }
+
+ @Override
+ int getExceptionCount() {
+ return errors.size();
+ }
+
+ private class ErrorDetailsSplitPane extends JSplitPane {
+
+ private final double TOP_PREFERRED_RESIZE_WEIGHT = .80;
+ private ErrorDetailsPanel detailsPanel;
+ private ErrorDetailsTablePanel tablePanel;
+
+ private Dimension openedSize;
+
+ ErrorDetailsSplitPane() {
+ super(VERTICAL_SPLIT);
+ setResizeWeight(TOP_PREFERRED_RESIZE_WEIGHT);
+
+ detailsPanel = new ErrorDetailsPanel();
+ tablePanel = new ErrorDetailsTablePanel();
+
+ setTopComponent(detailsPanel);
+ setBottomComponent(tablePanel);
+
+ addComponentListener(new ComponentAdapter() {
+ @Override
+ public void componentResized(ComponentEvent event) {
+ if (!isShowing()) {
+ return;
+ }
+ Rectangle localBounds = getBounds();
+ if (!detailsButton.getText().equals(DETAIL)) {
+ openedSize = new Dimension(localBounds.width, localBounds.height);
+ }
+ }
+ });
+ }
+
+ void selectFirstError() {
+ tablePanel.selectFirstError();
+ }
+
+ String getDetails() {
+ return detailsPanel.getDetails();
+ }
+
+ void setExceptionMessage(String s) {
+ detailsPanel.setExceptionMessage(s);
+ }
+
+ void update() {
+ tablePanel.update();
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension superSize = super.getPreferredSize();
+ if (detailsButton.getText().equals(DETAIL)) {
+ return superSize;
+ }
+
+ if (openedSize == null) {
+ return superSize;
+ }
+
+ return openedSize;
+ }
+
+ }
+
+ private class ErrorDetailsTablePanel extends JPanel {
+
+ private ErrEntryTableModel model;
+ private GTable errorsTable;
+ private GTableFilterPanel tableFilterPanel;
+
+ ErrorDetailsTablePanel() {
+ setLayout(new BorderLayout());
+ model = new ErrEntryTableModel();
+ errorsTable = new GTable(model);
+ tableFilterPanel = new GTableFilterPanel(errorsTable, model);
+
+ errorsTable.getSelectionManager().addListSelectionListener(e -> {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+
+ int firstIndex = errorsTable.getSelectedRow();
+ if (firstIndex == -1) {
+ return;
+ }
+ ErrEntry err = tableFilterPanel.getRowObject(firstIndex);
+ detailsPane.setExceptionMessage(err.getDetailsText());
+ });
+
+ JPanel tablePanel = new JPanel(new BorderLayout());
+ tablePanel.add(new JScrollPane(errorsTable), BorderLayout.CENTER);
+ tablePanel.add(tableFilterPanel, BorderLayout.SOUTH);
+
+ add(tablePanel, BorderLayout.CENTER);
+
+ // initialize this value to something small so the full dialog will not consume the
+ // entire screen height
+ setPreferredSize(new Dimension(400, 100));
+ }
+
+ void selectFirstError() {
+ errorsTable.selectRow(0);
+ }
+
+ private void update() {
+ model.fireTableDataChanged();
+ }
+
}
/**
@@ -244,76 +359,178 @@ public class ErrLogDialog extends DialogComponentProvider {
*/
private class ErrorDetailsPanel extends JPanel {
private ScrollableTextArea textDetails;
- private StringBuffer errorDetailsBuffer;
- private Dimension closedSize;
- private Dimension openedSize;
private ErrorDetailsPanel() {
super(new BorderLayout(0, 0));
- errorDetailsBuffer = new StringBuffer(ERROR_BUFFER_SIZE);
textDetails = new ScrollableTextArea(TEXT_ROWS, TEXT_COLUMNS);
textDetails.setEditable(false);
+
add(textDetails, BorderLayout.CENTER);
+
validate();
textDetails.scrollToBottom();
-
- // set the initial preferred size of this panel
- // when "closed"
- Rectangle bounds = getBounds();
- closedSize = new Dimension(bounds.width, 0);
-
- addComponentListener(new ComponentAdapter() {
- @Override
- public void componentResized(ComponentEvent event) {
- if (!isShowing()) {
- return;
- }
- Rectangle localBounds = getBounds();
- if (detailsButton.getText().equals(DETAIL)) {
- closedSize.width = localBounds.width;
- }
- else {
- openedSize = new Dimension(localBounds.width, localBounds.height);
- }
- }
- });
}
- @Override
- public Dimension getPreferredSize() {
- if (detailsButton.getText().equals(DETAIL)) {
- return closedSize;
- }
+ private void setExceptionMessage(String message) {
- if (openedSize == null) {
- return super.getPreferredSize();
- }
-
- return openedSize;
- }
-
- /**
- * resets the current error buffer to the contents of msg
- */
- private void setLogMessage(String msg) {
- errorDetailsBuffer = new StringBuffer(msg);
- textDetails.setText(msg);
-
- // scroll to bottom so user is viewing the last message
- textDetails.scrollToBottom();
- }
-
- private void setExceptionMessage(String msg) {
- errorDetailsBuffer = new StringBuffer(msg);
- textDetails.setText(msg);
+ String updated = addUsefulReportingInfo(message);
+ textDetails.setText(updated);
// scroll to the top the see the pertinent part of the exception
textDetails.scrollToTop();
}
- private final String getDetails() {
- return errorDetailsBuffer.toString();
+ private String getDetails() {
+ return textDetails.getText();
}
}
+ private class ErrEntry {
+
+ private String message;
+ private String details;
+ private Date timestamp = new Date();
+ private int myId = ++errorId;
+
+ ErrEntry(String message, Throwable t) {
+ String updated = message;
+ if (HTMLUtilities.isHTML(updated)) {
+ updated = HTMLUtilities.fromHTML(updated);
+ }
+ this.message = updated;
+
+ if (t != null) {
+ this.details = ReflectionUtilities.stackTraceToString(t);
+ }
+ }
+
+ int getId() {
+ return myId;
+ }
+
+ String getMessage() {
+ return message;
+ }
+
+ Date getTimestamp() {
+ return timestamp;
+ }
+
+ String getDetailsText() {
+ if (details == null) {
+ return message;
+ }
+ return details;
+ }
+
+ String getDetails() {
+ return details;
+ }
+
+ @Override
+ public String toString() {
+ return Json.toString(this);
+ }
+ }
+
+ private class ErrEntryTableModel extends GDynamicColumnTableModel {
+
+ public ErrEntryTableModel() {
+ super(new ServiceProviderStub());
+ }
+
+ @Override
+ protected TableColumnDescriptor createTableColumnDescriptor() {
+ TableColumnDescriptor descriptor = new TableColumnDescriptor();
+ descriptor.addVisibleColumn(new IdColumn(), 1, true);
+ descriptor.addVisibleColumn(new MessageColumn());
+ descriptor.addHiddenColumn(new DetailsColumn());
+ descriptor.addVisibleColumn(new TimestampColumn());
+ return descriptor;
+ }
+
+ @Override
+ public String getName() {
+ return "Unexpectd Errors";
+ }
+
+ @Override
+ public List getModelData() {
+ return errors;
+ }
+
+ @Override
+ public Object getDataSource() {
+ return null;
+ }
+
+ private class IdColumn extends AbstractDynamicTableColumnStub {
+
+ @Override
+ public Integer getValue(ErrEntry rowObject, Settings settings, ServiceProvider sp)
+ throws IllegalArgumentException {
+ return rowObject.getId();
+ }
+
+ @Override
+ public String getColumnName() {
+ return "#";
+ }
+
+ @Override
+ public int getColumnPreferredWidth() {
+ return 40;
+ }
+ }
+
+ private class MessageColumn extends AbstractDynamicTableColumnStub {
+
+ @Override
+ public String getValue(ErrEntry rowObject, Settings settings, ServiceProvider sp)
+ throws IllegalArgumentException {
+ return rowObject.getMessage();
+ }
+
+ @Override
+ public String getColumnName() {
+ return "Message";
+ }
+
+ }
+
+ private class DetailsColumn extends AbstractDynamicTableColumnStub {
+
+ @Override
+ public String getValue(ErrEntry rowObject, Settings settings, ServiceProvider sp)
+ throws IllegalArgumentException {
+ return rowObject.getDetails();
+ }
+
+ @Override
+ public String getColumnName() {
+ return "Details";
+ }
+ }
+
+ private class TimestampColumn extends AbstractDynamicTableColumnStub {
+
+ private GColumnRenderer renderer = new DefaultTimestampRenderer();
+
+ @Override
+ public Date getValue(ErrEntry rowObject, Settings settings, ServiceProvider sp)
+ throws IllegalArgumentException {
+ return rowObject.getTimestamp();
+ }
+
+ @Override
+ public String getColumnName() {
+ return "Time";
+ }
+
+ @Override
+ public GColumnRenderer getColumnRenderer() {
+ return renderer;
+ }
+ }
+ }
}
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/ErrLogExpandableDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/ErrLogExpandableDialog.java
index d91ade235a..518b229cba 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/ErrLogExpandableDialog.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/ErrLogExpandableDialog.java
@@ -32,12 +32,12 @@ import docking.widgets.label.GHtmlLabel;
import docking.widgets.tree.*;
import docking.widgets.tree.support.GTreeDragNDropHandler;
import ghidra.util.*;
-import ghidra.util.exception.*;
+import ghidra.util.exception.MultipleCauses;
import ghidra.util.html.HTMLElement;
import resources.ResourceManager;
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_EXCEPTION = ResourceManager.loadImage("images/exception.png");
public static ImageIcon IMG_FRAME_ELEMENT =
@@ -53,113 +53,21 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
private static boolean showingDetails = false;
protected ReportRootNode root;
- protected GTree excTree;
+ protected GTree tree;
+ private List errors = new ArrayList<>();
/** This spacer addresses the optical impression that the message panel changes size when showing details */
protected Component horizontalSpacer;
protected JButton detailButton;
protected JButton sendButton;
- protected boolean hasConsole = false;
protected JPopupMenu popup;
- protected static class ExcTreeTransferHandler extends TransferHandler
- implements GTreeDragNDropHandler {
+ protected ErrLogExpandableDialog(String title, String msg, Throwable throwable) {
+ super(title);
- protected ReportRootNode root;
+ errors.add(throwable);
- public ExcTreeTransferHandler(ReportRootNode root) {
- this.root = root;
- }
-
- @Override
- public DataFlavor[] getSupportedDataFlavors(List transferNodes) {
- return new DataFlavor[] { DataFlavor.stringFlavor };
- }
-
- @Override
- protected Transferable createTransferable(JComponent c) {
- ArrayList 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 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 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 report) {
- this(title, msg, report, null, false, false);
- }
-
- protected ErrLogExpandableDialog(String title, String msg, Collection report,
- String console, boolean modal, boolean hasDismiss) {
- super(title, modal);
-
- hasConsole = console != null;
popup = new JPopupMenu();
JMenuItem menuCopy = new JMenuItem("Copy");
menuCopy.setActionCommand((String) TransferHandler.getCopyAction().getValue(Action.NAME));
@@ -173,13 +81,12 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
msgPanel.setLayout(new BorderLayout(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
public Dimension getPreferredSize() {
- // when rendering HTML the label can expand larger than the screen;
- // keep it reasonable
+ // rendering HTML the label can expand larger than the screen; keep it reasonable
Dimension size = super.getPreferredSize();
- size.width = 500;
+ size.width = 300;
return size;
}
};
@@ -206,7 +113,7 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
msgPanel.add(buttonBox, BorderLayout.EAST);
horizontalSpacer = Box.createVerticalStrut(10);
- horizontalSpacer.setVisible(showingDetails | hasConsole);
+ horizontalSpacer.setVisible(showingDetails);
msgPanel.add(horizontalSpacer, BorderLayout.SOUTH);
}
workPanel.add(msgPanel, BorderLayout.NORTH);
@@ -214,29 +121,8 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
Box workBox = Box.createVerticalBox();
{
- if (hasConsole) {
- JTextArea consoleText = new JTextArea(console);
- 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) {
+ root = new ReportRootNode(getTitle(), CollectionUtils.asSet(throwable));
+ tree = new GTree(root) {
@Override
public Dimension getPreferredSize() {
@@ -249,19 +135,19 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
for (GTreeNode node : CollectionUtils.asIterable(root.iterator(true))) {
if (node instanceof ReportExceptionNode) {
- excTree.expandTree(node);
+ tree.expandTree(node);
}
}
- excTree.setSelectedNode(root.getChild(0));
- excTree.setVisible(showingDetails);
+ tree.setSelectedNode(root.getChild(0));
+ tree.setVisible(showingDetails);
ExcTreeTransferHandler handler = new ExcTreeTransferHandler(root);
- excTree.setDragNDropHandler(handler);
- excTree.setTransferHandler(handler);
- ActionMap map = excTree.getActionMap();
+ tree.setDragNDropHandler(handler);
+ tree.setTransferHandler(handler);
+ ActionMap map = tree.getActionMap();
map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
TransferHandler.getCopyAction());
- excTree.addMouseListener(new MouseAdapter() {
+ tree.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
maybeShowPopup(e);
@@ -279,22 +165,19 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
}
});
- workBox.add(excTree);
+ workBox.add(tree);
}
workPanel.add(workBox, BorderLayout.CENTER);
repack();
addWorkPanel(workPanel);
- if (hasDismiss) {
- addDismissButton();
- }
+ addDismissButton();
}
private String getHTML(String msg, Collection report) {
//
- // TODO
// Usage question: The content herein will be escaped unless you call addHTMLContenet().
// Further, clients can provide messages that contain HTML. Is there a
// use case where we want to show escaped HTML content?
@@ -332,14 +215,6 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
String htmlTMsg = addBR(tMsg);
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();
}
@@ -357,15 +232,15 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
return t.getClass().getSimpleName();
}
- void detailCallback() {
+ private void detailCallback() {
showingDetails = !showingDetails;
- excTree.setVisible(showingDetails);
- horizontalSpacer.setVisible(showingDetails | hasConsole);
+ tree.setVisible(showingDetails);
+ horizontalSpacer.setVisible(showingDetails);
detailButton.setText(showingDetails ? CLOSE : DETAIL);
repack();
}
- void sendCallback() {
+ private void sendCallback() {
String details = root.collectReportText(null, 0).trim();
String title = getTitle();
close();
@@ -379,6 +254,24 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
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 {
public String getReportText();
@@ -528,9 +421,6 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
@Override
public String getReportText() {
- if (exc instanceof HasConsoleText) {
- return getName() + "\n" + HasConsoleText.Util.get(exc);
- }
return getName();
}
@@ -661,6 +551,87 @@ public class ErrLogExpandableDialog extends DialogComponentProvider {
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 transferNodes) {
+ return new DataFlavor[] { DataFlavor.stringFlavor };
+ }
+
+ @Override
+ protected Transferable createTransferable(JComponent c) {
+ ArrayList 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 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 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 {
diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/ScrollableTextArea.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/ScrollableTextArea.java
index c1b52b84f9..221667a736 100644
--- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/ScrollableTextArea.java
+++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/ScrollableTextArea.java
@@ -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.
*/
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
+ * @return the count
*/
public int 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() {
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)
+ * @return the height
*/
public int getTextAreaHeight() {
return (textArea.getAreaHeight());
@@ -132,6 +135,7 @@ public class ScrollableTextArea extends JScrollPane {
/**
* Returns the visible height of the text area
+ * @return the height
*/
public int getTextVisibleHeight() {
return textArea.getVisibleHeight();
@@ -200,6 +204,7 @@ public class ScrollableTextArea extends JScrollPane {
/**
* Returns the text contained within the text area
+ * @return the text
*/
public String getText() {
return textArea.getText();
diff --git a/Ghidra/Framework/Docking/src/main/java/ghidra/util/table/column/DefaultTimestampRenderer.java b/Ghidra/Framework/Docking/src/main/java/ghidra/util/table/column/DefaultTimestampRenderer.java
new file mode 100644
index 0000000000..1a8de7b73e
--- /dev/null
+++ b/Ghidra/Framework/Docking/src/main/java/ghidra/util/table/column/DefaultTimestampRenderer.java
@@ -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 {
+
+ @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;
+ }
+}
diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/DockingErrorDisplayTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/DockingErrorDisplayTest.java
index 6ced1b0bf9..4cf1f91894 100644
--- a/Ghidra/Framework/Docking/src/test.slow/java/docking/DockingErrorDisplayTest.java
+++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/DockingErrorDisplayTest.java
@@ -35,7 +35,7 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
DockingErrorDisplay display = new DockingErrorDisplay();
DefaultErrorLogger logger = new DefaultErrorLogger();
Exception exception = new Exception("My test exception");
- doDisplay(display, logger, exception);
+ reportException(display, logger, exception);
assertErrLogDialog();
}
@@ -46,11 +46,29 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
DefaultErrorLogger logger = new DefaultErrorLogger();
Exception nestedException = new Exception("My nested test exception");
Exception exception = new Exception("My test exception", nestedException);
- doDisplay(display, logger, exception);
+ reportException(display, logger, exception);
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
public void testMultipleCausesErrorDisplay() {
DockingErrorDisplay display = new DockingErrorDisplay();
@@ -58,43 +76,51 @@ public class DockingErrorDisplayTest extends AbstractDockingTest {
Throwable firstCause = new Exception("My test exception - first cause");
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() {
- Window w = waitForWindow(TEST_TITLE, 2000);
- assertNotNull(w);
+ private void assertExceptionCount(AbstractErrDialog errDialog, int n) {
- final ErrLogExpandableDialog errDialog =
+ int actual = errDialog.getExceptionCount();
+ assertEquals(n, actual);
+ }
+
+ private ErrLogExpandableDialog assertErrLogExpandableDialog() {
+ Window w = waitForWindow(TEST_TITLE);
+
+ ErrLogExpandableDialog errDialog =
getDialogComponentProvider(w, ErrLogExpandableDialog.class);
assertNotNull(errDialog);
-
- runSwing(new Runnable() {
- @Override
- public void run() {
- errDialog.close();
- }
- });
+ return errDialog;
}
private void assertErrLogDialog() {
- Window w = waitForWindow(TEST_TITLE, 2000);
+ Window w = waitForWindow(TEST_TITLE);
assertNotNull(w);
- final ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
+ ErrLogDialog errDialog = getDialogComponentProvider(w, ErrLogDialog.class);
assertNotNull(errDialog);
-
- runSwing(new Runnable() {
- @Override
- public void run() {
- errDialog.close();
- }
- });
+ close(errDialog);
}
- 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) {
runSwing(new Runnable() {
@Override
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/exception/CausesImportant.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/exception/CausesImportant.java
deleted file mode 100644
index 1e274fa7c7..0000000000
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/exception/CausesImportant.java
+++ /dev/null
@@ -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();
- }
- }
-}
diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/exception/HasConsoleText.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/exception/HasConsoleText.java
deleted file mode 100644
index f87856ad25..0000000000
--- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/exception/HasConsoleText.java
+++ /dev/null
@@ -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;
- }
- }
-}
diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java
index c9ff2b264b..329ccb1f4f 100644
--- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java
+++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/data/DataTypeManagerDB.java
@@ -89,7 +89,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
protected DBHandle dbHandle;
private AddressMap addrMap;
- private ErrorHandler errHandler;
+ private ErrorHandler errHandler = new DbErrorHandler();
private DataTypeConflictHandler currentHandler;
private CategoryDB root;
@@ -168,12 +168,7 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
*/
protected 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 {
dbHandle = new DBHandle();
int id = startTransaction("");
@@ -213,13 +208,6 @@ abstract public class DataTypeManagerDB implements DataTypeManager {
") 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
boolean openSuccess = false;
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);
+ }
+ }
}
/**
diff --git a/Ghidra/Framework/Utility/src/main/java/utilities/util/reflection/ReflectionUtilities.java b/Ghidra/Framework/Utility/src/main/java/utilities/util/reflection/ReflectionUtilities.java
index d1b5a56693..1f427274cc 100644
--- a/Ghidra/Framework/Utility/src/main/java/utilities/util/reflection/ReflectionUtilities.java
+++ b/Ghidra/Framework/Utility/src/main/java/utilities/util/reflection/ReflectionUtilities.java
@@ -499,14 +499,31 @@ public class ReflectionUtilities {
* @return the string
*/
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();
PrintStream ps = new PrintStream(baos);
- String msg = t.getMessage();
- if (msg != null) {
- ps.println(msg);
+ if (message != null) {
+ ps.println(message);
+ }
+ else {
+ String throwableMessage = t.getMessage();
+ if (throwableMessage != null) {
+ ps.println(throwableMessage);
+ }
}
t.printStackTrace(ps);
diff --git a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/IntroScreenShots.java b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/IntroScreenShots.java
index 7c0a5dd59d..c54e102490 100644
--- a/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/IntroScreenShots.java
+++ b/Ghidra/Test/IntegrationTest/src/screen/java/help/screenshot/IntroScreenShots.java
@@ -55,8 +55,8 @@ public class IntroScreenShots extends GhidraScreenShotGenerator {
@Test
public void testErr_Dialog() {
runSwing(() -> {
- ErrLogDialog dialog = ErrLogDialog.createLogMessageDialog("Unexpected Error",
- "Oops, this is really bad!", "");
+ ErrLogDialog dialog = ErrLogDialog.createExceptionDialog("Unexpected Error",
+ "Oops, this is really bad!", new Throwable());
DockingWindowManager.showDialog(null, dialog);
}, false);
waitForSwing();