diff --git a/openapi/src/com/intellij/openapi/diagnostic/ErrorReportSubmitter.java b/openapi/src/com/intellij/openapi/diagnostic/ErrorReportSubmitter.java new file mode 100644 index 000000000000..ee0a98ad88e8 --- /dev/null +++ b/openapi/src/com/intellij/openapi/diagnostic/ErrorReportSubmitter.java @@ -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); +} diff --git a/openapi/src/com/intellij/openapi/diagnostic/SubmittedReportInfo.java b/openapi/src/com/intellij/openapi/diagnostic/SubmittedReportInfo.java new file mode 100644 index 000000000000..ea82a8ad5150 --- /dev/null +++ b/openapi/src/com/intellij/openapi/diagnostic/SubmittedReportInfo.java @@ -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; + } +} diff --git a/source/com/intellij/diagnostic/DefaultIdeaErrorLogger.java b/source/com/intellij/diagnostic/DefaultIdeaErrorLogger.java index 212ad3dc6bc2..1d6d33d47b2f 100644 --- a/source/com/intellij/diagnostic/DefaultIdeaErrorLogger.java +++ b/source/com/intellij/diagnostic/DefaultIdeaErrorLogger.java @@ -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 myEventsCache = Collections.synchronizedList(new ArrayList()); 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) { diff --git a/source/com/intellij/diagnostic/ITNReporter.java b/source/com/intellij/diagnostic/ITNReporter.java new file mode 100644 index 000000000000..8f1bc153f9b6 --- /dev/null +++ b/source/com/intellij/diagnostic/ITNReporter.java @@ -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); + } +} diff --git a/source/com/intellij/ide/plugins/PluginManager.java b/source/com/intellij/ide/plugins/PluginManager.java index 90e52fbc04c8..01c057d55c6e 100644 --- a/source/com/intellij/ide/plugins/PluginManager.java +++ b/source/com/intellij/ide/plugins/PluginManager.java @@ -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; } } diff --git a/source/com/intellij/openapi/components/impl/ComponentManagerImpl.java b/source/com/intellij/openapi/components/impl/ComponentManagerImpl.java index 19653ddee7a6..04c6b20fb8ab 100644 --- a/source/com/intellij/openapi/components/impl/ComponentManagerImpl.java +++ b/source/com/intellij/openapi/components/impl/ComponentManagerImpl.java @@ -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) { diff --git a/source/com/intellij/openapi/wm/impl/status/StatusBarImpl.java b/source/com/intellij/openapi/wm/impl/status/StatusBarImpl.java index e7832b9ea6fd..eaed76b5f7d4 100644 --- a/source/com/intellij/openapi/wm/impl/status/StatusBarImpl.java +++ b/source/com/intellij/openapi/wm/impl/status/StatusBarImpl.java @@ -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); } diff --git a/source/jetbrains/fbq/diagnostic/AbstractMessage.java b/source/jetbrains/fbq/diagnostic/AbstractMessage.java new file mode 100644 index 000000000000..88b14b6c9edf --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/AbstractMessage.java @@ -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; + } + +} diff --git a/source/jetbrains/fbq/diagnostic/GroupedLogMessage.java b/source/jetbrains/fbq/diagnostic/GroupedLogMessage.java new file mode 100644 index 000000000000..4af52a56d8c5 --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/GroupedLogMessage.java @@ -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 myMessages; + + public GroupedLogMessage(List messages) { + myMessages = messages; + } + + public List 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(); + } + +} diff --git a/source/jetbrains/fbq/diagnostic/IdeErrorsDialog.java b/source/jetbrains/fbq/diagnostic/IdeErrorsDialog.java new file mode 100644 index 000000000000..a4e5144abd4f --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/IdeErrorsDialog.java @@ -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> hash2Messages = buildHashcode2MessageListMap(myFatalErrors); + + final Iterator> messageLists = hash2Messages.values().iterator(); + while (messageLists.hasNext()) { + myHeadersModel.addElement(messageLists.next()); + } + + if (selectedIndex < myHeadersModel.getSize()) { + myHeadersList.setSelectedIndex(selectedIndex); + } + } + + private Map> buildHashcode2MessageListMap(List aErrors) { + Map> hash2Messages = new LinkedHashMap>(); + for (int i = 0; i < aErrors.size(); i++) { + final AbstractMessage each = (AbstractMessage)aErrors.get(i); + final String hashcode = ScrData.getThrowableHashCode(each.getThrowable()); + ArrayList list; + if (hash2Messages.containsKey(hashcode)) { + list = hash2Messages.get(hashcode); + } + else { + list = new ArrayList(); + 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 messageList = (ArrayList)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 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"; + } + +} diff --git a/source/jetbrains/fbq/diagnostic/IdeMessagePanel.java b/source/jetbrains/fbq/diagnostic/IdeMessagePanel.java new file mode 100644 index 000000000000..a395ba4b04be --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/IdeMessagePanel.java @@ -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 = + "IDE internal error occured."+ + "
Click to see details and post bug report to JetBrains.
"; + + 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; + } + } +} diff --git a/source/jetbrains/fbq/diagnostic/LogMessage.java b/source/jetbrains/fbq/diagnostic/LogMessage.java new file mode 100644 index 000000000000..ae41448fe09c --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/LogMessage.java @@ -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()); + } + +} diff --git a/source/jetbrains/fbq/diagnostic/MessagePool.java b/source/jetbrains/fbq/diagnostic/MessagePool.java new file mode 100644 index 000000000000..6c0e6cc61a11 --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/MessagePool.java @@ -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 myIdeFatals = new ArrayList(); + + private Set myListeners = new HashSet(); + + 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 getFatalErrors(boolean aIncludeReadMessages, boolean aIncludeSubmittedMessages) { + List result = new ArrayList(); + 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 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 myMessages = new ArrayList(); + 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(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(); + } + } + } + } +} diff --git a/source/jetbrains/fbq/diagnostic/MessagePoolListener.java b/source/jetbrains/fbq/diagnostic/MessagePoolListener.java new file mode 100644 index 000000000000..d38f8ed471f7 --- /dev/null +++ b/source/jetbrains/fbq/diagnostic/MessagePoolListener.java @@ -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(); + +} diff --git a/source/jetbrains/fbq/ide/reporter/ConnectionException.java b/source/jetbrains/fbq/ide/reporter/ConnectionException.java new file mode 100644 index 000000000000..eced4f547523 --- /dev/null +++ b/source/jetbrains/fbq/ide/reporter/ConnectionException.java @@ -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); + } +}