New error reporter with ability for plugins to report their exception to their bugtracking systems.

This commit is contained in:
Maxim Shafirov
2005-02-18 01:57:31 +03:00
parent eb43850bfc
commit c9eb3e9f55
15 changed files with 1304 additions and 47 deletions
@@ -0,0 +1,15 @@
package com.intellij.openapi.diagnostic;
import java.awt.*;
/**
* Created by IntelliJ IDEA.
* User: max
* Date: Feb 17, 2005
* Time: 9:11:44 PM
* To change this template use File | Settings | File Templates.
*/
public interface ErrorReportSubmitter {
String getReportActionText();
SubmittedReportInfo submit(IdeaLoggingEvent[] events, Component parentComponent);
}
@@ -0,0 +1,47 @@
package com.intellij.openapi.diagnostic;
/**
* Created by IntelliJ IDEA.
* User: max
* Date: Feb 17, 2005
* Time: 9:42:38 PM
* To change this template use File | Settings | File Templates.
*/
public class SubmittedReportInfo {
public static class SubmissionStatus {
public static SubmissionStatus NEW_ISSUE = new SubmissionStatus("NEW_ISSUE");
public static SubmissionStatus DUPLICATE = new SubmissionStatus("DUPLICATE");
public static SubmissionStatus FAILED = new SubmissionStatus("FAILED");
private String myName;
private SubmissionStatus(String name) {
myName = name;
}
public String toString() {
return "SubmissionStatus[" + myName + "]";
}
}
private String myURL;
private String myLinkText;
private SubmissionStatus myStatus;
public SubmittedReportInfo(final String URL, final String linkText, final SubmissionStatus status) {
myURL = URL;
myLinkText = linkText;
myStatus = status;
}
public String getURL() {
return myURL;
}
public String getLinkText() {
return myLinkText;
}
public SubmissionStatus getStatus() {
return myStatus;
}
}
@@ -10,69 +10,31 @@ import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.ErrorLogger;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.util.SystemInfo;
import jetbrains.fbq.diagnostic.MessagePool;
import javax.swing.*;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author kir
*/
public class DefaultIdeaErrorLogger implements ErrorLogger {
private static boolean ourOomOccured = false;
private final List<IdeaLoggingEvent> myEventsCache = Collections.synchronizedList(new ArrayList<IdeaLoggingEvent>());
public boolean canHandle(IdeaLoggingEvent event) {
return !DialogAppender.RELEASE_BUILD ||
ApplicationManagerEx.getApplicationEx().isInternal() ||
return !DialogAppender.RELEASE_BUILD || ApplicationManagerEx.getApplicationEx().isInternal() ||
event.getThrowable() instanceof OutOfMemoryError;
}
/** @noinspection ThrowablePrintStackTrace*/
public void handle(IdeaLoggingEvent event) {
showDialog(event);
}
private void showDialog(IdeaLoggingEvent event) {
try {
if (event.getThrowable() instanceof OutOfMemoryError) {
processOOMError(event);
}
else {
if (ourOomOccured) {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
JOptionPane.showMessageDialog(JOptionPane.getRootFrame(),
"OutOfMemoryException occured before this problem and " +
"it can be the reason of current one. So we recommend " +
"you to restart your IDEA.", "Error", JOptionPane.ERROR_MESSAGE);
}
});
return;
}
myEventsCache.add(event);
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
while (myEventsCache.size() > 0) {
IdeaLoggingEvent event = myEventsCache.get(0);
ErrorOccuredDialog errorOccuredDialog = new ErrorOccuredDialog(event);
errorOccuredDialog.show();
if (errorOccuredDialog.isShouldClose()) {
try {
ApplicationManager.getApplication().exit();
}
catch (Throwable e) {
System.exit(0);
}
}
myEventsCache.remove(0);
}
}
});
else if (!ourOomOccured) {
MessagePool.getInstance().addIdeFatalMessage(event);
}
}
catch (Exception e) {
@@ -0,0 +1,173 @@
package com.intellij.diagnostic;
import com.intellij.errorreport.ErrorReportSender;
import com.intellij.errorreport.bean.ErrorBean;
import com.intellij.errorreport.bean.NotifierBean;
import com.intellij.errorreport.error.InternalEAPException;
import com.intellij.errorreport.error.NewBuildException;
import com.intellij.errorreport.error.NoSuchEAPUserException;
import com.intellij.errorreport.error.ThreadClosedException;
import com.intellij.errorreport.itn.ITNProxy;
import com.intellij.ide.BrowserUtil;
import com.intellij.idea.IdeaLogger;
import com.intellij.openapi.diagnostic.ErrorReportSubmitter;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.diagnostic.SubmittedReportInfo;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.net.IOExceptionDialog;
import java.awt.*;
import java.io.IOException;
/**
* Created by IntelliJ IDEA.
* User: max
* Date: Feb 17, 2005
* Time: 11:12:23 PM
* To change this template use File | Settings | File Templates.
*/
public class ITNReporter implements ErrorReportSubmitter {
private static int previousExceptionThreadId = 0;
private static boolean wasException = false;
private static final String URL_HEADER = "http://www.intellij.net/tracker/idea/viewSCR?publicId=";
public String getReportActionText() {
return "Report to JetBrains";
}
public SubmittedReportInfo submit(IdeaLoggingEvent[] events, Component parentComponent) {
return sendError(events[0], parentComponent);
}
/**
* @noinspection ThrowablePrintStackTrace
*/
private SubmittedReportInfo sendError(IdeaLoggingEvent event, Component parentComponent) {
NotifierBean notifierBean = new NotifierBean();
ErrorBean errorBean = new ErrorBean();
errorBean.autoInit();
errorBean.setLastAction(IdeaLogger.ourLastActionId);
int threadId = 0;
SubmittedReportInfo.SubmissionStatus submissionStatus = SubmittedReportInfo.SubmissionStatus.FAILED;
do {
// prepare
try {
ErrorReportSender sender = ErrorReportSender.getInstance();
sender.prepareError(event.getThrowable());
EAPSendErrorDialog dlg = new EAPSendErrorDialog();
dlg.show();
notifierBean.setItnLogin(ErrorReportConfigurable.getInstance().ITN_LOGIN);
notifierBean.setItnPassword(ErrorReportConfigurable.getInstance().getPlainItnPassword());
String description = dlg.getErrorDescription();
String message = event.getMessage();
errorBean.setDescription("User description: " + description + "\n" +
(message != null ? "Error message: " + message + "\n" : ""));
if (dlg.isShouldSend()) {
threadId = sender.sendError(notifierBean, errorBean);
if (previousExceptionThreadId != 0) {
ITNProxy.postNewComment(ErrorReportConfigurable.getInstance().ITN_LOGIN,
ErrorReportConfigurable.getInstance().getPlainItnPassword(),
threadId, "Previous exception is: " +
URL_HEADER +
previousExceptionThreadId);
}
previousExceptionThreadId = threadId;
if (wasException) {
ITNProxy.postNewComment(ErrorReportConfigurable.getInstance().ITN_LOGIN,
ErrorReportConfigurable.getInstance().getPlainItnPassword(),
threadId, "There was at least one exception before this one.");
}
wasException = true;
submissionStatus = SubmittedReportInfo.SubmissionStatus.NEW_ISSUE;
if (Messages.showYesNoDialog(parentComponent,
"Error report successfuly sent. \nRequest #" + threadId +
" created. Do you want to open the related request in ITN?",
ReportMessages.ERROR_REPORT, Messages.getQuestionIcon()) == 0) {
try {
BrowserUtil.launchBrowser(URL_HEADER + threadId);
}
catch (IllegalThreadStateException e) {
// it's OK
// browser is not exited
}
}
break;
}
else {
break;
}
}
catch (NoSuchEAPUserException e) {
if (ReportMessages.isEAP) e.printStackTrace();
if (Messages.showYesNoDialog(parentComponent, "ITN authorization failed. Do you want to try again?",
ReportMessages.ERROR_REPORT, Messages.getErrorIcon()) != 0) {
break;
}
}
catch (InternalEAPException e) {
if (ReportMessages.isEAP) e.printStackTrace();
if (Messages.showYesNoDialog(parentComponent, "ITN posting failed. Do you want to try again?",
ReportMessages.ERROR_REPORT, Messages.getErrorIcon()) != 0) {
break;
}
}
catch (IOException e) {
if (ReportMessages.isEAP) e.printStackTrace();
if (!IOExceptionDialog.showErrorDialog(e, "Error Report", "Error report sending failed.")) {
break;
}
}
catch (NewBuildException e) {
if (ReportMessages.isEAP) e.printStackTrace();
Messages.showMessageDialog(parentComponent,
"New EAP build " + e.getMessage() + " is available.", "Warning",
Messages.getWarningIcon());
break;
}
catch (ThreadClosedException e) {
submissionStatus = SubmittedReportInfo.SubmissionStatus.DUPLICATE;
threadId = e.getThreadId();
if (ReportMessages.isEAP) e.printStackTrace();
if (Messages.showYesNoDialog(parentComponent,
"This error already closed with status: " + e.getMessage() + "." +
" You don't need to post it, thank you. Do you want to open it in tracker?",
ReportMessages.ERROR_REPORT, Messages.getQuestionIcon()) == 0) {
try {
BrowserUtil.launchBrowser(URL_HEADER + threadId);
}
catch (IllegalThreadStateException ex) {
// it's OK
// browser is not exited
}
}
break;
}
catch (Exception e) {
if (ReportMessages.isEAP) e.printStackTrace();
if (Messages.showYesNoDialog(parentComponent, "Sending failed. Do you want to try again?",
ReportMessages.ERROR_REPORT, Messages.getErrorIcon()) != 0) {
break;
}
}
}
while (true);
return new SubmittedReportInfo(submissionStatus != SubmittedReportInfo.SubmissionStatus.FAILED ? URL_HEADER + threadId : null,
String.valueOf(threadId),
submissionStatus);
}
}
@@ -1,5 +1,7 @@
package com.intellij.ide.plugins;
import com.intellij.diagnostic.ErrorHandlerExtension;
import com.intellij.diagnostic.ITNReporter;
import com.intellij.ide.plugins.cl.IdeaClassLoader;
import com.intellij.ide.plugins.cl.PluginClassLoader;
import com.intellij.ide.startup.StartupActionScriptManager;
@@ -7,6 +9,7 @@ import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.*;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.graph.CachingSemiGraph;
@@ -38,6 +41,7 @@ public class PluginManager {
//Logger is lasy-initialized in order not to use it outside the appClassLoader
private static Logger ourLogger = null;
public static final String COMPONENT_EXTENSION_POINT = "com.intellij.component";
public static final String ERROR_HANDLER_EXTENSION_POINT = "com.intellij.errorHandler";
private static Logger getLogger() {
if (ourLogger == null) {
@@ -132,6 +136,11 @@ public class PluginManager {
public void areaDisposing(String areaClass, AreaInstance areaInstance) {
}
}, LoadingOrder.FIRST);
Extensions.getRootArea().registerExtensionPoint(ERROR_HANDLER_EXTENSION_POINT, ErrorHandlerExtension.class.getName());
final ErrorHandlerExtension defaultErrorHandler = new ErrorHandlerExtension();
defaultErrorHandler.setHandlerClass(ITNReporter.class.getName());
Extensions.getRootArea().getExtensionPoint(ERROR_HANDLER_EXTENSION_POINT).registerExtension(defaultErrorHandler);
}
public static boolean shouldLoadPlugins() {
@@ -624,7 +633,7 @@ public class PluginManager {
final PluginDescriptor[] plugins = getPlugins();
for (int i = 0; i < plugins.length; i++) {
final PluginDescriptor plugin = plugins[i];
if (name.equals(plugin.getName())) {
if (Comparing.equal(name, plugin.getName())) {
return plugin;
}
}
@@ -377,7 +377,10 @@ public abstract class ComponentManagerImpl extends UserDataHolderBase implements
if (isComponentSuitable(options)) {
ClassLoader loader = findLoader(descriptor.getPluginName());
try {
registerComponent(Class.forName(descriptor.getInterface(), true, loader), Class.forName(descriptor.getImplementation(), true, loader), options, true,
registerComponent(Class.forName(descriptor.getInterface(), true, loader),
Class.forName(descriptor.getImplementation(), true, loader),
options,
true,
isTrue(options, "lazy"));
}
catch (Exception e) {
@@ -2,11 +2,13 @@ package com.intellij.openapi.wm.impl.status;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.UISettingsListener;
import com.intellij.openapi.actionSystem.impl.EmptyIcon;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.impl.EmptyIcon;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.ui.EdgeBorder;
import jetbrains.fbq.diagnostic.IdeMessagePanel;
import jetbrains.fbq.diagnostic.MessagePool;
import javax.swing.*;
import javax.swing.border.Border;
@@ -23,6 +25,7 @@ public class StatusBarImpl extends JPanel implements StatusBarEx {
protected final MemoryUsagePanel myMemoryUsagePanel = new MemoryUsagePanel();
protected final TextPanel myStatusPanel = new TextPanel(new String[]{"Insert", "Overwrite"},false);
protected final TogglePopupHintsPanel myPopupHintsPanel;
protected final IdeMessagePanel myMessagePanel = new IdeMessagePanel(MessagePool.getInstance());
protected String myInfo = "";
private final Icon myLockedIcon = IconLoader.getIcon("/nodes/lockedSingle.png");
private final Icon myUnlockedIcon = myLockedIcon != null ? EmptyIcon.create(myLockedIcon.getIconWidth(), myLockedIcon.getIconHeight()) : null;
@@ -92,8 +95,11 @@ public class StatusBarImpl extends JPanel implements StatusBarEx {
myPopupHintsPanel.setOpaque(false);
rightPanel.add(myPopupHintsPanel, gbConstraints);
myMessagePanel.setOpaque(false);
rightPanel.add(myMessagePanel, gbConstraints);
// myMemoryUsagePanel.setOpaque(false);
myMemoryUsagePanel.setBorder(BorderFactory.createEmptyBorder(4, 3, 2, 3));
// myMemoryUsagePanel.setOpaque(false);
rightPanel.add(myMemoryUsagePanel, gbConstraints);
}
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
import com.intellij.openapi.diagnostic.SubmittedReportInfo;
import java.util.Calendar;
import java.util.Date;
public abstract class AbstractMessage {
private boolean myIsRead = false;
private SubmittedReportInfo mySubmissionInfo;
private String myScrID;
private final Date myDate;
public AbstractMessage() {
myDate = Calendar.getInstance().getTime();
}
public abstract String getThrowableText();
public abstract Throwable getThrowable();
public abstract String getMessage();
public boolean isRead() {
return myIsRead;
}
public void setRead(boolean aReadFlag) {
myIsRead = aReadFlag;
}
public void setSubmitted(SubmittedReportInfo info) {
mySubmissionInfo = info;
}
public SubmittedReportInfo getSubmissionInfo() {
return mySubmissionInfo;
}
public boolean isSumbitted() {
return mySubmissionInfo != null &&
(mySubmissionInfo.getStatus() == SubmittedReportInfo.SubmissionStatus.NEW_ISSUE ||
mySubmissionInfo.getStatus() == SubmittedReportInfo.SubmissionStatus.DUPLICATE);
}
public Date getDate() {
return myDate;
}
}
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
import java.util.List;
public class GroupedLogMessage extends AbstractMessage {
private List<AbstractMessage> myMessages;
public GroupedLogMessage(List<AbstractMessage> messages) {
myMessages = messages;
}
public List<AbstractMessage> getMessages() {
return myMessages;
}
public String getThrowableText() {
StringBuffer result = new StringBuffer();
for (int i = 0; i < myMessages.size(); i++) {
AbstractMessage each = myMessages.get(i);
result.append(each.getThrowableText() + "\n\n\n");
}
return result.toString();
}
public Throwable getThrowable() {
return myMessages.get(0).getThrowable();
}
public String getMessage() {
return myMessages.get(0).getMessage();
}
}
@@ -0,0 +1,411 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
/**
* @author kir
*/
import com.intellij.diagnostic.ErrorHandlerExtension;
import com.intellij.diagnostic.PluginException;
import com.intellij.ide.BrowserUtil;
import com.intellij.ide.plugins.PluginDescriptor;
import com.intellij.ide.plugins.PluginManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.ErrorReportSubmitter;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Comparing;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.SimpleTextAttributes;
import jetbrains.fbq.ide.reporter.ScrData;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListener {
private JTextPane myDetailsPane;
private List myFatalErrors;
private JList myHeadersList;
private DefaultListModel myHeadersModel = new DefaultListModel();
private final MessagePool myMessagePool;
public IdeErrorsDialog(MessagePool messagePool) {
super(JOptionPane.getRootFrame(), false);
myMessagePool = messagePool;
myHeadersList = new JList(myHeadersModel);
init();
}
public void newEntryAdded() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
rebuildHeaders();
}
});
}
public void poolCleared() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
doOKAction();
}
});
}
protected Action[] createActions() {
return new Action[]{new SubmitBugAction(), new ShutdownAction(), new ClearFatalsAction(), new CloseAction()};
}
public JComponent getPreferredFocusedComponent() {
return myHeadersList;
}
protected JComponent createCenterPanel() {
setTitle("IDE Fatal Errors");
rebuildHeaders();
JPanel root = new JPanel(new BorderLayout());
JPanel headersPanel = new JPanel(new BorderLayout());
headersPanel.add(new JLabel("Messages:"), BorderLayout.NORTH);
myHeadersList.setCellRenderer(new CellRenderer());
headersPanel.add(new JScrollPane(myHeadersList), BorderLayout.CENTER);
JPanel detailsPanel = new JPanel(new BorderLayout());
detailsPanel.add(new JLabel("Details:"), BorderLayout.NORTH);
myDetailsPane = new JTextPane();
myDetailsPane.setEditable(false);
detailsPanel.add(new JScrollPane(myDetailsPane), BorderLayout.CENTER);
Splitter splitter = new Splitter(true, .5f);
splitter.setFirstComponent(headersPanel);
splitter.setSecondComponent(detailsPanel);
root.add(splitter, BorderLayout.CENTER);
root.setPreferredSize(new Dimension(600, 550));
myHeadersList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
if (myHeadersList.isSelectionEmpty() || myHeadersList.getModel().getSize() == 0) {
hideMessageDetails();
return;
}
showMessageDetails(getMessageAt(myHeadersList.getSelectedIndex()));
}
});
myHeadersList.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() % 2 == 0) {
final AbstractMessage logMessage = getMessageAt(myHeadersList.getSelectedIndex());
if (logMessage.isSumbitted()) {
String url = logMessage.getSubmissionInfo().getURL();
if (url != null) {
BrowserUtil.launchBrowser(url);
}
}
}
}
});
moveSelectionToEarliestMessage();
return root;
}
private AbstractMessage getMessageAt(int aIndex) {
return (AbstractMessage)((ArrayList)myHeadersList.getModel().getElementAt(aIndex)).get(0);
}
private void moveSelectionToEarliestMessage() {
for (int i = myHeadersList.getModel().getSize() - 1; i > 0; i--) {
final AbstractMessage each = getMessageAt(i);
if (!each.isRead()) {
myHeadersList.setSelectedIndex(i);
break;
}
}
if (myHeadersList.isSelectionEmpty() && myHeadersList.getModel().getSize() > 0) {
myHeadersList.setSelectedIndex(0);
}
}
private void rebuildHeaders() {
final int selectedIndex = myHeadersList.getSelectedIndex();
myHeadersModel.removeAllElements();
myFatalErrors = myMessagePool.getFatalErrors(true, true);
Collections.reverse(myFatalErrors);
Map<String, ArrayList<AbstractMessage>> hash2Messages = buildHashcode2MessageListMap(myFatalErrors);
final Iterator<ArrayList<AbstractMessage>> messageLists = hash2Messages.values().iterator();
while (messageLists.hasNext()) {
myHeadersModel.addElement(messageLists.next());
}
if (selectedIndex < myHeadersModel.getSize()) {
myHeadersList.setSelectedIndex(selectedIndex);
}
}
private Map<String, ArrayList<AbstractMessage>> buildHashcode2MessageListMap(List aErrors) {
Map<String, ArrayList<AbstractMessage>> hash2Messages = new LinkedHashMap<String, ArrayList<AbstractMessage>>();
for (int i = 0; i < aErrors.size(); i++) {
final AbstractMessage each = (AbstractMessage)aErrors.get(i);
final String hashcode = ScrData.getThrowableHashCode(each.getThrowable());
ArrayList<AbstractMessage> list;
if (hash2Messages.containsKey(hashcode)) {
list = hash2Messages.get(hashcode);
}
else {
list = new ArrayList<AbstractMessage>();
hash2Messages.put(hashcode, list);
}
list.add(0, each);
}
return hash2Messages;
}
private void showMessageDetails(AbstractMessage aMessage) {
myDetailsPane.setText(aMessage.getThrowableText());
myDetailsPane.setCaretPosition(0);
}
private void hideMessageDetails() {
myDetailsPane.setText("");
}
public static String findPluginName(Throwable t) {
StackTraceElement[] elements = t.getStackTrace();
for (int i = 0; i < elements.length; i++) {
StackTraceElement element = elements[i];
String className = element.getClassName();
if (PluginManager.isPluginClass(className) &&
!className.startsWith("com.intellij") &&
!className.startsWith("org.jetbrains")) {
return PluginManager.getPluginByClassName(className);
}
}
if (t instanceof NoSuchMethodException) {
// check is method called from plugin classes
if (t.getMessage() != null) {
String className = "";
StringTokenizer tok = new StringTokenizer(t.getMessage(), ".");
while (tok.hasMoreTokens()) {
String token = tok.nextToken();
if (token.length() > 0 && Character.isJavaIdentifierStart(token.charAt(0))) {
className += token;
}
}
if (PluginManager.isPluginClass(className)) {
return PluginManager.getPluginByClassName(className);
}
}
}
else if (t instanceof ClassNotFoundException) {
// check is class from plugin classes
if (t.getMessage() != null) {
String className = t.getMessage();
if (PluginManager.isPluginClass(className)) {
return PluginManager.getPluginByClassName(className);
}
}
}
else if (t instanceof PluginException) {
return ((PluginException)t).getDescriptor().getName();
}
return null;
}
private static class CellRenderer extends DefaultListCellRenderer {
private SimpleColoredComponent myComponent = new SimpleColoredComponent();
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
ArrayList<AbstractMessage> messageList = (ArrayList<AbstractMessage>)value;
AbstractMessage message = messageList.get(0);
String prefix = messageList.size() > 1 ? "(" + messageList.size() + ") " : "";
myComponent.clear();
final Color unimportantColor = new Color(102, 102, 102);
final String text = message.getMessage();
final SimpleTextAttributes grayed = new SimpleTextAttributes(Font.PLAIN, unimportantColor);
final SimpleTextAttributes blue = new SimpleTextAttributes(Font.PLAIN, Color.blue);
if (!message.isRead() && !message.isSumbitted()) {
myComponent.append(prefix + text, new SimpleTextAttributes(Font.BOLD, Color.black));
}
else if (message.isSumbitted()) {
myComponent.append(prefix, grayed);
myComponent.append("Submitted: #" + message.getSubmissionInfo().getLinkText(), blue);
myComponent.append(":" + text, blue);
myComponent.append(" (double-click to open in browser)", grayed);
}
else if (message.isRead()) {
myComponent.append(prefix + text, grayed);
}
myComponent.setBackground(isSelected ? list.getSelectionBackground() : list.getBackground());
return myComponent;
}
}
private class ShutdownAction extends AbstractAction {
public ShutdownAction() {
super("Shut_down");
}
public void actionPerformed(ActionEvent e) {
myMessagePool.setJvmIsShuttingDown();
ApplicationManager.getApplication().exit();
}
}
private class ClearFatalsAction extends AbstractAction {
public ClearFatalsAction() {
super("_Clear And Close");
}
public void actionPerformed(ActionEvent e) {
myMessagePool.clearFatals();
doOKAction();
}
}
private class SubmitBugAction extends AbstractAction {
public SubmitBugAction() {
super("_Report To JetBrains");
putValue(DEFAULT, Boolean.TRUE);
}
public void actionPerformed(ActionEvent e) {
if (myHeadersList.isSelectionEmpty()) {
Messages.showMessageDialog("Please first select error to report", "Cannot Report", Messages.getInformationIcon());
myHeadersList.requestFocus();
return;
}
final int selectedIndex = myHeadersList.getSelectedIndex();
final AbstractMessage logMessage = getMessageAt(selectedIndex);
if (logMessage.isSumbitted()) {
// Messages.showMessageDialog("This error was already submitted: #" + logMessage.getScrID(), "Already Submitted", Messages.getInformationIcon());
Messages.showMessageDialog("This error was already submitted", "Already Submitted", Messages.getInformationIcon());
return;
}
reportMessage(logMessage);
rebuildHeaders();
myHeadersList.setSelectedIndex(selectedIndex);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myHeadersList.requestFocus();
}
});
}
private void reportMessage(final AbstractMessage logMessage) {
final String pluginName = findPluginName(logMessage.getThrowable());
final Object[] reporters = Extensions.getRootArea().getExtensionPoint(PluginManager.ERROR_HANDLER_EXTENSION_POINT).getExtensions();
ErrorReportSubmitter submitter = null;
for (int i = 0; i < reporters.length; i++) {
ErrorHandlerExtension reporterBean = (ErrorHandlerExtension)reporters[i];
if (Comparing.equal(pluginName, reporterBean.getPluginName())) {
final PluginDescriptor plugin = PluginManager.getPlugin(pluginName);
final ClassLoader loader;
if (plugin != null) {
loader = plugin.getLoader();
}
else {
loader = getClass().getClassLoader();
}
try {
final Class submitterClass = Class.forName(reporterBean.getHandlerClass(), true, loader);
submitter = (ErrorReportSubmitter)submitterClass.newInstance();
}
catch (Exception e) {
break;
}
}
}
if (submitter != null) {
logMessage.setSubmitted(submitter.submit(getEvents(logMessage), getContentPane()));
}
}
private IdeaLoggingEvent[] getEvents(final AbstractMessage logMessage) {
if (logMessage instanceof GroupedLogMessage) {
final List<AbstractMessage> messages = ((GroupedLogMessage)logMessage).getMessages();
IdeaLoggingEvent[] res = new IdeaLoggingEvent[messages.size()];
for (int i = 0; i < res.length; i++) {
res[i] = getEvent(messages.get(i));
}
return res;
}
return new IdeaLoggingEvent[]{getEvent(logMessage)};
}
private IdeaLoggingEvent getEvent(final AbstractMessage logMessage) {
return new IdeaLoggingEvent(logMessage.getMessage(), logMessage.getThrowable());
}
}
protected void doOKAction() {
markAllAsRead();
super.doOKAction();
}
private void markAllAsRead() {
for (int i = 0; i < myFatalErrors.size(); i++) {
AbstractMessage each = (AbstractMessage)myFatalErrors.get(i);
each.setRead(true);
}
}
public void doCancelAction() {
markAllAsRead();
super.doCancelAction();
}
protected class CloseAction extends AbstractAction {
public CloseAction() {
putValue(Action.NAME, "C_lose");
}
public void actionPerformed(ActionEvent e) {
doOKAction();
}
}
protected String getDimensionServiceKey() {
return "IdeErrosDialog";
}
}
@@ -0,0 +1,275 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.IconLoader;
import jetbrains.fbq.ui.messager.Callout;
import jetbrains.fbq.util.awt.RelativePoint;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class IdeMessagePanel extends JPanel implements MessagePoolListener {
private static final Logger LOG = Logger.getInstance("#jetbrains.fbq.diagnostic.IdeMessagePanel");
private IconPane myIdeFatal;
private IconPane[] myIcons;
private static final String INTERNAL_ERROR_NOTICE =
"<html><body><b>IDE internal error occured."+
"<br>Click to see details and post bug report to JetBrains.</b></body></html>";
private long myPreviousExceptionTimeStamp;
private IdeErrorsDialog myDialog;
private boolean myOpeningInProgress;
private final MessagePool myMessagePool;
public IdeMessagePanel(MessagePool messagePool) {
super(new BorderLayout());
myIdeFatal = new IconPane(IconLoader.getIcon("/general/ideFatalError.png"), "No new IDE fatal errors", new ActionListener() {
public void actionPerformed(ActionEvent e) {
openFatals();
}
});
myIdeFatal.setVerticalAlignment(JLabel.CENTER);
myIcons = new IconPane[]{myIdeFatal};
add(myIdeFatal, BorderLayout.CENTER);
myMessagePool = messagePool;
messagePool.addListener(this);
new Blinker().start();
updateFatalErrorsIcon();
}
private void openFatals() {
if (myDialog != null) return;
if (myOpeningInProgress) return;
myOpeningInProgress = true;
new Thread() {
public void run() {
try {
while(isOtherModalWindowActive()) {
if (myDialog != null) return;
sleep(300);
}
_openFatals();
} catch (InterruptedException e) {}
finally {
myOpeningInProgress = false;
}
}
}.start();
}
private void _openFatals() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myDialog = new IdeErrorsDialog(myMessagePool) {
protected void doOKAction() {
super.doOKAction();
disposeDialog(this);
}
public void doCancelAction() {
super.doCancelAction();
disposeDialog(this);
}
};
myMessagePool.addListener(myDialog);
if (!isOtherModalWindowActive()) {
myDialog.show();
}
else {
myDialog.close(0);
disposeDialog(myDialog);
}
}
});
}
private void disposeDialog(final IdeErrorsDialog listDialog) {
myMessagePool.removeListener(listDialog);
updateFatalErrorsIcon();
myDialog = null;
}
public void newEntryAdded() {
updateFatalErrorsIcon();
long lastExceptionTimestamp = System.currentTimeMillis();
if ((lastExceptionTimestamp - myPreviousExceptionTimeStamp > 1000) && myMessagePool.hasUnreadMessages()){
showErrorCallout();
}
/*
if (isOtherModalWindowActive()) {
showErrorCallout();
openFatals();
} else if ((lastExceptionTimestamp - myPreviousExceptionTimeStamp > 1000)){
openFatals();
}
*/
myPreviousExceptionTimeStamp = lastExceptionTimestamp;
}
private void showErrorCallout() {
if (isShowing()) {
Callout.showText(RelativePoint.getCenterOf(myIdeFatal), Callout.NORTH_WEST, INTERNAL_ERROR_NOTICE);
}
}
public void poolCleared() {
updateFatalErrorsIcon();
}
private boolean isOtherModalWindowActive() {
final Window window = getActiveModalWindow();
if (window == null) return false;
if (myDialog != null) {
return myDialog.getWindow() != window;
}
return true;
}
private Window getActiveModalWindow() {
final KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
final Window activeWindow = manager.getActiveWindow();
if (activeWindow instanceof JDialog) {
if (((JDialog) activeWindow).isModal()) {
return activeWindow;
}
}
return null;
}
private void updateFatalErrorsIcon() {
if (myMessagePool.getFatalErrors(true, true).size() == 0) {
myIdeFatal.deactivate();
} else {
myIdeFatal.activate(INTERNAL_ERROR_NOTICE, true);
}
}
private class Blinker extends Thread {
/** @noinspection BusyWait*/
public void run() {
while(true) {
try {
sleep(1000);
setBlinkedIconsVisibilityTo(false);
sleep(1000);
setBlinkedIconsVisibilityTo(true);
}
catch (InterruptedException e) {
LOG.error(e);
}
}
}
private void setBlinkedIconsVisibilityTo(boolean aVisible) {
for (int i = 0; i < myIcons.length; i++) {
final IdeMessagePanel.IconPane each = myIcons[i];
each.getIconWrapper().setVisible(aVisible || !each.shouldBlink());
each.paintImmediately(each.getBounds());
}
}
}
private class IconPane extends JLabel {
private IconWrapper myIcon;
private String myEmptyText;
private boolean myIsActive;
private ActionListener myListener;
public IconPane(Icon aIcon, String aEmptyText, ActionListener aListener) {
myIcon = new IconWrapper(aIcon);
myEmptyText = aEmptyText;
myListener = aListener;
setIcon(myIcon);
addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (myIsActive) {
myListener.actionPerformed(null);
}
}
});
deactivate();
}
public IconWrapper getIconWrapper() {
return myIcon;
}
public void activate(String aDisplayingText, boolean aShouldBlink) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
myIsActive = true;
myIcon.setIconPainted(true);
setToolTipText(aDisplayingText);
repaint();
}
public void deactivate() {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
myIsActive = false;
myIcon.setIconPainted(false);
setToolTipText(myEmptyText);
repaint();
}
public boolean shouldBlink() {
return myMessagePool.hasUnreadMessages();
}
}
private static class IconWrapper implements Icon {
private Icon myIcon;
private boolean myEnabled;
private boolean myShouldPaint = true;
public IconWrapper(Icon aIcon) {
myIcon = aIcon;
}
public void setIconPainted(boolean aPainted) {
myEnabled = aPainted;
}
public int getIconHeight() {
return myIcon.getIconHeight();
}
public int getIconWidth() {
return myIcon.getIconWidth();
}
public void paintIcon(Component c, Graphics g, int x, int y) {
if (myEnabled && myShouldPaint) {
myIcon.paintIcon(c, g, x, y);
}
}
public void setVisible(final boolean visible) {
myShouldPaint = visible;
}
}
}
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import com.intellij.openapi.util.text.StringUtil;
import org.apache.log4j.spi.LoggingEvent;
public class LogMessage extends AbstractMessage {
static final String NO_MESSAGE = "No message";
private String myHeader = NO_MESSAGE;
private final Throwable myThrowable;
public LogMessage(LoggingEvent aEvent) {
super();
myThrowable = aEvent.getThrowableInformation() == null ? null : aEvent.getThrowableInformation().getThrowable();
if (aEvent.getMessage().toString().length() == 0) {
myHeader = getThrowable().toString();
} else {
myHeader = aEvent.getMessage().toString();
}
}
public LogMessage(IdeaLoggingEvent aEvent) {
super();
myThrowable = aEvent.getThrowable();
if (StringUtil.isNotEmpty(aEvent.getMessage())) {
myHeader = aEvent.getMessage();
}
if (myThrowable != null && StringUtil.isNotEmpty(myThrowable.getMessage())) {
if (myHeader != NO_MESSAGE ) {
if (!myHeader.endsWith(": ") && !myHeader.endsWith(":")) {
myHeader += ": ";
}
myHeader += myThrowable.getMessage();
}
else {
myHeader = myThrowable.getMessage();
}
}
}
public Throwable getThrowable() {
return myThrowable;
}
public String getMessage() {
return myHeader;
}
public String getThrowableText() {
return StringUtil.getThrowableText(getThrowable());
}
}
@@ -0,0 +1,168 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
import com.intellij.openapi.diagnostic.IdeaLoggingEvent;
import org.apache.log4j.Category;
import org.apache.log4j.Priority;
import org.apache.log4j.spi.LoggingEvent;
import java.util.*;
public class MessagePool {
private static int MAX_POOL_SIZE_FOR_FATALS = 100;
private static MessagePool ourInstance;
private List<AbstractMessage> myIdeFatals = new ArrayList<AbstractMessage>();
private Set<MessagePoolListener> myListeners = new HashSet<MessagePoolListener>();
private MessageGrouper myFatalsGrouper;
private boolean ourJvmIsShuttingDown = false;
MessagePool(int maxGroupSize, int timeout) {
myFatalsGrouper = new MessageGrouper("Fatal Errors Grouper", timeout, maxGroupSize);
}
public static MessagePool getInstance() {
if (ourInstance == null) {
ourInstance = new MessagePool(20, 1000);
}
return ourInstance;
}
public void addIdeFatalMessage(LoggingEvent aEvent) {
addIdeFatalMessage(new LogMessage(aEvent));
}
public void addIdeFatalMessage(IdeaLoggingEvent aEvent) {
addIdeFatalMessage(new LogMessage(aEvent));
}
private void addIdeFatalMessage(LogMessage message) {
if (myIdeFatals.size() < MAX_POOL_SIZE_FOR_FATALS) {
myFatalsGrouper.add(message);
} else if (myIdeFatals.size() == MAX_POOL_SIZE_FOR_FATALS) {
myFatalsGrouper.add(new LogMessage(new LoggingEvent("Too many IDE fatal errors. Monitoring stoppped.", Category.getRoot(), Priority.ERROR, null, new Exception())));
}
}
public boolean isFatalErrorsPoolEmpty() {
return myIdeFatals.isEmpty();
}
public boolean hasUnreadMessages() {
for (int i = 0; i < myIdeFatals.size(); i++) {
AbstractMessage message = myIdeFatals.get(i);
if (!message.isRead()) return true;
}
return false;
}
public List<AbstractMessage> getFatalErrors(boolean aIncludeReadMessages, boolean aIncludeSubmittedMessages) {
List<AbstractMessage> result = new ArrayList<AbstractMessage>();
for (int i = 0; i < myIdeFatals.size(); i++) {
AbstractMessage each = myIdeFatals.get(i);
if (!each.isRead() && !each.isSumbitted()) {
result.add(each);
} else if ((each.isRead() && aIncludeReadMessages) || (each.isSumbitted() && aIncludeSubmittedMessages)) {
result.add(each);
}
}
return result;
}
public void clearFatals() {
myIdeFatals.clear();
notifyListenersClear();
}
public void addListener(MessagePoolListener aListener) {
myListeners.add(aListener);
}
public void removeListener(MessagePoolListener aListener) {
myListeners.remove(aListener);
}
private void notifyListenersAdd() {
if (ourJvmIsShuttingDown) return;
for (Iterator<MessagePoolListener> iterator = myListeners.iterator(); iterator.hasNext();) {
iterator.next().newEntryAdded();
}
}
private void notifyListenersClear() {
final MessagePoolListener[] messagePoolListeners = myListeners.toArray(new MessagePoolListener[myListeners.size()]);
for (int i = 0; i < messagePoolListeners.length; i++) {
messagePoolListeners[i].poolCleared();
}
}
public void setJvmIsShuttingDown() {
ourJvmIsShuttingDown = true;
}
private class MessageGrouper extends Thread {
private int myTimeOut;
private int myMaxGroupSize;
private final List<AbstractMessage> myMessages = new ArrayList<AbstractMessage>();
private int myAccumulatedTime;
public MessageGrouper(String name, int timeout, int maxGroupSize) {
setName(name);
myTimeOut = timeout;
myMaxGroupSize = maxGroupSize;
start();
}
/** @noinspection BusyWait*/
public void run() {
while (true) {
try {
sleep(50);
myAccumulatedTime += 50;
if (myAccumulatedTime > myTimeOut) {
synchronized(myMessages) {
if (myMessages.size() > 0) {
post();
}
}
}
} catch (InterruptedException e) {
return;
}
}
}
private void post() {
AbstractMessage message;
if (myMessages.size() == 1) {
message = myMessages.get(0);
} else {
message = new GroupedLogMessage(new ArrayList<AbstractMessage>(myMessages));
}
myMessages.clear();
myIdeFatals.add(message);
notifyListenersAdd();
myAccumulatedTime = 0;
}
public void add(AbstractMessage message) {
myAccumulatedTime = 0;
synchronized(myMessages) {
myMessages.add(message);
if (myMessages.size() >= myMaxGroupSize) {
post();
}
}
}
}
}
@@ -0,0 +1,12 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.diagnostic;
public interface MessagePoolListener {
void newEntryAdded();
void poolCleared();
}
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2000-2004 by JetBrains s.r.o. All Rights Reserved.
* Use is subject to license terms.
*/
package jetbrains.fbq.ide.reporter;
public class ConnectionException extends Exception {
public ConnectionException(String message) {
super(message);
}
public ConnectionException(Throwable cause) {
super(cause);
}
public ConnectionException(String message, Throwable cause) {
super(message, cause);
}
}