/* * RapidMiner * * Copyright (C) 2001-2011 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.tools.dialogs; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Font; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Collection; import java.util.LinkedList; import java.util.Locale; import javax.swing.AbstractButton; import javax.swing.DefaultListModel; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JToggleButton; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.apache.xmlrpc.client.XmlRpcClient; import com.rapidminer.NoBugError; import com.rapidminer.gui.MainFrame; import com.rapidminer.gui.RapidMinerGUI; import com.rapidminer.gui.dialog.BugZillaAssistant; import com.rapidminer.gui.tools.ExtendedJScrollPane; import com.rapidminer.gui.tools.ProgressThread; import com.rapidminer.gui.tools.ResourceAction; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.gui.tools.components.LinkButton; import com.rapidminer.operator.Operator; import com.rapidminer.operator.UserError; import com.rapidminer.tools.I18N; import com.rapidminer.tools.Tools; import com.rapidminer.tools.XMLException; import com.rapidminer.tools.XmlRpcHandler; /** * The error message dialog. Several buttons are provided in addition to the error message. Details about the exception * can be shown and an edit button can jump to the source code if an editor was defined in the properties / settings. In * case of a non-expected error (i.e. all non-user errors) a button for sending a bug report is also provided. * * @author Ingo Mierswa, Simon Fischer, Tobias Malbrecht, Marco Boeck */ public class ExtendedErrorDialog extends ButtonDialog { private static final long serialVersionUID = -8136329951869702133L; private static final int SIZE = ButtonDialog.DEFAULT_SIZE; private final JButton editButton = new JButton("Edit"); private JButton sendReport; private Throwable error; private final JComponent mainComponent = new JPanel(new BorderLayout()); /** * Creates a dialog with the internationalized I18n-message from the given key and a panel for detailed stack trace. * * @param key * the I18n-key which will be used to display the internationalized message * @param error * the exception associated to this message * @param arguments * additional arguments for the internationalized message, which replace <code>{0}</code>, * <code>{1}</code>, etcpp. */ public ExtendedErrorDialog(String key, Throwable error, Object... arguments) { this(key, error, false, arguments); } /** * Creates a dialog with the internationalized I18n-message from the given key and a panel for detailed stack trace. * * @param key * the I18n-key which will be used to display the internationalized message * @param error * the exception associated to this message * @param displayExceptionMessage * indicates if the exception message can be shown using the details button. * @param arguments * additional arguments for the internationalized message, which replace <code>{0}</code>, * <code>{1}</code>, etcpp. */ public ExtendedErrorDialog(String key, Throwable error, boolean displayExceptionMessage, Object... arguments) { super("error." + key, true, arguments); this.error = error; boolean hasError = (error != null); JComponent detailedPane = hasError ? createDetailPanel(error) : null; if ((error != null) && (error instanceof UserError) && (((UserError) error).getOperator() != null)) { final String opName = ((UserError) error).getOperator().getName(); mainComponent.add(new LinkButton(new ResourceAction("show_offending_operator", opName) { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { MainFrame mainFrame = RapidMinerGUI.getMainFrame(); mainFrame.selectOperator(mainFrame.getProcess().getOperator(opName)); } }), BorderLayout.NORTH); } layoutDefault(mainComponent, SIZE, getButtons(hasError && displayExceptionMessage, isBugReportException(error), detailedPane, error)); pack(); } /** * Creates a dialog with the internationalized I18n-message from the given key. * * @param key * the I18n-key which will be used to display the internationalized message * @param errorMessage * the error message associated to this message * @param arguments * additional arguments for the internationalized message, which replace <code>{0}</code>, * <code>{1}</code>, etcpp. */ public ExtendedErrorDialog(String key, String errorMessage, Object... arguments) { super("error." + key, true, arguments); boolean hasError = (errorMessage != null) && !errorMessage.isEmpty(); JScrollPane detailedPane = hasError ? createDetailPanel(errorMessage) : null; layoutDefault(mainComponent, SIZE, getButtons(hasError, false, detailedPane, null)); } @Override protected Icon getInfoIcon() { return SwingTools.createIcon("48/" + I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.error.icon")); } /** * Creates a Panel for the error details and attaches the exception to it, but doesn't add the Panel to the dialog. * * @param error * @return */ private JComponent createDetailPanel(Throwable error) { StackTraceList stl = new StackTraceList(error); JScrollPane detailPane = new ExtendedJScrollPane(stl); detailPane.setPreferredSize(new Dimension(getWidth(), 200)); return detailPane; } /** * Creates a Panel for the error details and attaches the error message to it, but doesn't add the Panel to the * dialog. * * @param squaredError * @return */ private JScrollPane createDetailPanel(String errorMessage) { JTextArea textArea = new JTextArea(errorMessage); textArea.setLineWrap(true); textArea.setEditable(false); JScrollPane detailPane = new ExtendedJScrollPane(textArea); detailPane.setPreferredSize(new Dimension(getWidth(), 200)); return detailPane; } /** * Adds all necessary buttons to the dialog. * * @param hasError * @param isBug * @param detailedPane * the Panel which will be shown, if the user clicks on the 'Show Details' Button * @param error * The error occurred * @return */ private Collection<AbstractButton> getButtons(boolean hasError, boolean isBug, final JComponent detailedPane, final Throwable error) { Collection<AbstractButton> buttons = new LinkedList<AbstractButton>(); if (hasError) { final JToggleButton showDetailsButton = new JToggleButton(I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.error.show_details.label"), SwingTools.createIcon("24/" + I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.error.show_details.icon"))); showDetailsButton.setSelected(false); showDetailsButton.addActionListener(new ActionListener() { private boolean detailsShown = false; @Override public void actionPerformed(ActionEvent e) { if (detailsShown) { int width2 = ExtendedErrorDialog.this.getWidth(); mainComponent.remove(detailedPane); ExtendedErrorDialog.this.setPreferredSize(new Dimension(width2, ExtendedErrorDialog.this.getHeight() - 150)); pack(); } else { int width2 = ExtendedErrorDialog.this.getWidth(); mainComponent.add(detailedPane, BorderLayout.CENTER); ExtendedErrorDialog.this.setPreferredSize(new Dimension(width2, ExtendedErrorDialog.this.getHeight() + 150)); pack(); } detailsShown = !detailsShown; } }); buttons.add(showDetailsButton); } if (isBug) { sendReport = new JButton(new ResourceAction("send_bugreport") { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { // in case of UserError, ask if the user really wants to send a bugreport // because it's likely not a bug if (error instanceof UserError) { if (SwingTools.showConfirmDialog("send_bugreport.confirm", ConfirmDialog.YES_NO_OPTION) == ConfirmDialog.NO_OPTION) { return; } } // create bug report window and connect to BugZilla new ProgressThread("connect_to_bugzilla", false) { @Override public void run() { sendReport.setEnabled(false); final BugZillaAssistant bugAst; getProgressListener().setTotal(100); getProgressListener().setCompleted(10); char[] pw = new char[] { '!', 'z', '4', '8', '#', 'H', 'c', '2', '$', '%', 'm', ')', '9', '+', '*', '*' }; String email = "bugs@rapid-i.com"; try { XmlRpcClient rpcClient = XmlRpcHandler.login(XmlRpcHandler.BUGZILLA_URL, email, pw); getProgressListener().setCompleted(20); bugAst = new BugZillaAssistant(this, error, rpcClient); } catch (Exception e) { SwingTools.showVerySimpleErrorMessage("bugreport_xmlrpc_init_error"); return; } finally { for (int i=0; i<pw.length; i++) { pw[i] = 0; } getProgressListener().complete(); sendReport.setEnabled(true); } if (!isCancelled()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // if operator from import group is present, ask the user to include the data in the bug report if (RapidMinerGUI.getMainFrame() != null && RapidMinerGUI.getMainFrame().getProcess() != null) { for (Operator op : RapidMinerGUI.getMainFrame().getProcess().getAllOperators()) { if (op.getOperatorDescription().getGroup().toLowerCase(Locale.ENGLISH).contains("import") || op.getName().toLowerCase(Locale.ENGLISH).equals("retrieve")) { SwingTools.showMessageDialog("send_bugreport.import_operator_message"); break; } } } bugAst.setVisible(true); } }); } else { bugAst.dispose(); } } }.start(); } }); buttons.add(sendReport); } buttons.add(makeCloseButton()); return buttons; } /** * Returns <code>true</code> if this is a "real" bug, <code>false</code> otherwise. * * @param t * @return */ private boolean isBugReportException(Throwable t) { return !(t instanceof NoBugError || t instanceof XMLException); } /** * Overrides the {@link ButtonDialog} method to add the exception message to the internationalized message */ @Override protected String getInfoText() { if (error != null) { StringBuilder infoText = new StringBuilder(); infoText.append("<div><strong>"); infoText.append(super.getInfoText()); infoText.append("</strong></div>"); // if already arguments are given, we can expect already a detailed error message if (arguments.length == 0 && error.getMessage() != null && error.getMessage().length() > 0) { infoText.append("<p>"); infoText.append(Tools.escapeHTML(error.getMessage())); infoText.append("</p>"); } Throwable cause = error.getCause(); if (cause != null) { String message = Tools.escapeHTML(cause.getMessage()); if (message == null) { message = cause.toString(); } if (!"null".equals(message)) { infoText.append("<p><strong>Reason: </strong>"); infoText.append(message); infoText.append("</p>"); } } return infoText.toString(); } else { return super.getInfoText(); } } private static class FormattedStackTraceElement { private final StackTraceElement ste; private FormattedStackTraceElement(StackTraceElement ste) { this.ste = ste; } @Override public String toString() { return " " + ste; } } private class StackTraceList extends JList { private static final long serialVersionUID = -2482220036723949144L; public StackTraceList(Throwable t) { super(new DefaultListModel()); setFont(getFont().deriveFont(Font.PLAIN)); setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); appendAllStackTraces(t); addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { if (getSelectedIndex() >= 0) { if (!(getSelectedValue() instanceof FormattedStackTraceElement)) { editButton.setEnabled(false); } else { editButton.setEnabled(true); } } else { editButton.setEnabled(true); } } }); } private DefaultListModel model() { return (DefaultListModel) getModel(); } private void appendAllStackTraces(Throwable throwable) { while (throwable != null) { appendStackTrace(throwable); throwable = throwable.getCause(); if (throwable != null) { model().addElement(""); model().addElement("Cause"); } } } private void appendStackTrace(Throwable throwable) { model().addElement("Exception: " + throwable.getClass().getName()); model().addElement("Message: " + throwable.getMessage()); model().addElement("Stack trace:" + Tools.getLineSeparator()); for (int i = 0; i < throwable.getStackTrace().length; i++) { model().addElement(new FormattedStackTraceElement(throwable.getStackTrace()[i])); } } } }