/*
* 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 jpa.tools.swing;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
/**
* A dialog to display runtime error.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class ErrorDialog extends JDialog {
private static List<String> filters = Arrays.asList(
"java.awt.",
"javax.swing.",
"sun.reflect.",
"java.util.concurrent.");
private static Dimension MESSAGE_SIZE = new Dimension(600,200);
private static Dimension STACKTRACE_SIZE = new Dimension(600,300);
private static Dimension TOTAL_SIZE = new Dimension(600,500);
static String NEWLINE = "\r\n";
static String INDENT = " ";
private boolean _showingDetails;
private boolean _isFiltering = true;
private JComponent _message;
private JComponent _main;
private JScrollPane _details;
private JTextPane _stacktrace;
private final Throwable _error;
/**
* Creates a modal dialog to display the given exception message.
*
* @param t the exception to display
*/
public ErrorDialog(Throwable t) {
this(null, null, t);
}
public ErrorDialog(JComponent owner, Throwable t) {
this(owner, null, t);
}
/**
* Creates a modal dialog to display the given exception message.
*
* @param owner if non-null, then the dialog is positioned (centered) w.r.t. this component
* @param t the exception to display
*/
public ErrorDialog(JComponent owner, Icon icon, Throwable t) {
super();
setTitle(t.getClass().getName());
setModal(true);
if (icon != null && icon instanceof ImageIcon)
setIconImage(((ImageIcon)icon).getImage());
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
_error = t;
_message = createErrorMessage(_error);
_main = createContent();
getContentPane().add(_main);
pack();
SwingHelper.position(this, owner);
}
/**
* Creates the display with the top-level exception message
* followed by a pane (that toggles) for detailed stack traces.
*
* @param t a non-null exception
*/
JComponent createContent() {
final JButton showDetails = new JButton("Show Details >>");
showDetails.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (_showingDetails) {
_main.remove(_details);
_main.validate();
_main.setPreferredSize(MESSAGE_SIZE);
} else {
if (_details == null) {
_details = createDetailedMessage(_error);
StringBuilder buffer = new StringBuilder();
_stacktrace.setText(generateStackTrace(_error, buffer).toString());
_stacktrace.setBackground(_main.getBackground());
_stacktrace.setPreferredSize(STACKTRACE_SIZE);
}
_main.add(_details, BorderLayout.CENTER);
_main.validate();
_main.setPreferredSize(TOTAL_SIZE);
}
_showingDetails = !_showingDetails;
showDetails.setText(_showingDetails ? "<< Hide Details" : "Show Details >>");
ErrorDialog.this.pack();
}
});
JPanel messagePanel = new JPanel();
final JCheckBox filter = new JCheckBox("Filter stack traces");
filter.setSelected(_isFiltering);
filter.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
_isFiltering = filter.isSelected();
StringBuilder buffer = new StringBuilder();
_stacktrace.setText(generateStackTrace(_error, buffer).toString());
_stacktrace.repaint();
}
});
_message.setBackground(messagePanel.getBackground());
JPanel buttonPanel = new JPanel();
buttonPanel.add(Box.createHorizontalStrut(20));
buttonPanel.add(showDetails);
buttonPanel.add(filter);
buttonPanel.add(Box.createHorizontalGlue());
messagePanel.setLayout(new BorderLayout());
messagePanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
messagePanel.add(_message, BorderLayout.CENTER);
messagePanel.add(buttonPanel, BorderLayout.SOUTH);
messagePanel.setPreferredSize(MESSAGE_SIZE);
JPanel main = new JPanel();
main.setLayout(new BorderLayout());
main.add(messagePanel, BorderLayout.NORTH);
return main;
}
/**
* Creates a non-editable widget to display the error message.
*
*/
JComponent createErrorMessage(Throwable t) {
String txt = t.getLocalizedMessage();
JEditorPane message = new JEditorPane();
message.setContentType("text/plain");
message.setEditable(false);
message.setText(txt);
return message;
}
/**
* Creates a non-editable widget to display the detailed stack trace.
*/
JScrollPane createDetailedMessage(Throwable t) {
_stacktrace = new JTextPane();
_stacktrace.setEditable(false);
JScrollPane pane = new JScrollPane(_stacktrace,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
return pane;
}
/**
* Recursively print the stack trace on the given buffer.
*/
StringBuilder generateStackTrace(Throwable t, StringBuilder buffer) {
buffer.append(t.getClass().getName() + ": " + t.getMessage() + NEWLINE);
buffer.append(toString(t.getStackTrace()));
Throwable cause = t.getCause();
if (cause !=null && cause != t) {
generateStackTrace(cause, buffer);
}
return buffer;
}
StringBuilder toString(StackTraceElement[] traces) {
StringBuilder error = new StringBuilder();
for (StackTraceElement e : traces) {
if (!_isFiltering || !isSuppressed(e.getClassName())) {
String str = e.toString();
error.append(INDENT).append(str).append(NEWLINE);
}
}
return error;
}
/**
* Affirms if the error messages from the given class name is to be suppressed.
*/
private boolean isSuppressed(String className) {
for (String s : filters) {
if (className.startsWith(s))
return true;
}
return false;
}
public static void main(String[] args) {
String m1 = "This is test error with very very very very very long line of error message that "
+ " should not be in a single line. Another message string that shoul dbe split across word." +
"The quick brown fox jumpled over the lazy dog";
String m2 = "This is another test error with very long line of error message that "
+ " should not be in a single line";
Throwable nested = new NumberFormatException(m2);
Throwable top = new IllegalArgumentException(m1, nested);
new ErrorDialog(top).setVisible(true);
}
}