84be6a3ee706ae1ea38f5b32d734e97f43248332
[idea/community.git] / platform / platform-impl / src / com / intellij / diagnostic / IdeErrorsDialog.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.diagnostic;
17
18 import com.intellij.CommonBundle;
19 import com.intellij.ExtensionPoints;
20 import com.intellij.diagnostic.errordialog.*;
21 import com.intellij.icons.AllIcons;
22 import com.intellij.ide.DataManager;
23 import com.intellij.ide.impl.DataManagerImpl;
24 import com.intellij.ide.plugins.IdeaPluginDescriptor;
25 import com.intellij.ide.plugins.PluginManager;
26 import com.intellij.ide.plugins.PluginManagerCore;
27 import com.intellij.ide.plugins.PluginManagerMain;
28 import com.intellij.ide.plugins.cl.PluginClassLoader;
29 import com.intellij.ide.util.PropertiesComponent;
30 import com.intellij.openapi.actionSystem.*;
31 import com.intellij.openapi.application.Application;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.application.ApplicationNamesInfo;
34 import com.intellij.openapi.application.ex.ApplicationEx;
35 import com.intellij.openapi.application.ex.ApplicationInfoEx;
36 import com.intellij.openapi.application.ex.ApplicationManagerEx;
37 import com.intellij.openapi.diagnostic.*;
38 import com.intellij.openapi.extensions.ExtensionException;
39 import com.intellij.openapi.extensions.Extensions;
40 import com.intellij.openapi.extensions.PluginDescriptor;
41 import com.intellij.openapi.extensions.PluginId;
42 import com.intellij.openapi.progress.ProgressIndicator;
43 import com.intellij.openapi.progress.ProgressManager;
44 import com.intellij.openapi.progress.Task;
45 import com.intellij.openapi.project.DumbAware;
46 import com.intellij.openapi.project.Project;
47 import com.intellij.openapi.ui.DialogWrapper;
48 import com.intellij.openapi.util.Comparing;
49 import com.intellij.openapi.util.Condition;
50 import com.intellij.openapi.util.Ref;
51 import com.intellij.openapi.util.SystemInfo;
52 import com.intellij.openapi.util.text.StringUtil;
53 import com.intellij.openapi.wm.IdeFocusManager;
54 import com.intellij.openapi.wm.IdeFrame;
55 import com.intellij.ui.HeaderlessTabbedPane;
56 import com.intellij.ui.HyperlinkLabel;
57 import com.intellij.ui.IdeBorderFactory;
58 import com.intellij.util.Consumer;
59 import com.intellij.util.Function;
60 import com.intellij.util.containers.ContainerUtil;
61 import com.intellij.util.text.DateFormatUtil;
62 import com.intellij.util.ui.UIUtil;
63 import com.intellij.xml.util.XmlStringUtil;
64 import org.jetbrains.annotations.NonNls;
65 import org.jetbrains.annotations.NotNull;
66 import org.jetbrains.annotations.Nullable;
67
68 import javax.swing.*;
69 import javax.swing.event.ChangeEvent;
70 import javax.swing.event.ChangeListener;
71 import javax.swing.event.HyperlinkEvent;
72 import javax.swing.event.HyperlinkListener;
73 import java.awt.*;
74 import java.awt.event.ActionEvent;
75 import java.awt.event.ActionListener;
76 import java.io.IOException;
77 import java.math.BigInteger;
78 import java.security.MessageDigest;
79 import java.security.NoSuchAlgorithmException;
80 import java.util.*;
81 import java.util.List;
82
83 public class IdeErrorsDialog extends DialogWrapper implements MessagePoolListener, TypeSafeDataProvider {
84   private static final Logger LOG = Logger.getInstance(IdeErrorsDialog.class.getName());
85   private final boolean myInternalMode;
86   @NonNls private static final String ACTIVE_TAB_OPTION = IdeErrorsDialog.class.getName() + "activeTab";
87   public static DataKey<String> CURRENT_TRACE_KEY = DataKey.create("current_stack_trace_key");
88   public static final int COMPONENTS_WIDTH = 670;
89   public static Collection<Developer> ourDevelopersList = Collections.emptyList();
90
91   private JPanel myContentPane;
92   private JPanel myBackButtonPanel;
93   private HyperlinkLabel.Croppable myInfoLabel;
94   private JPanel myNextButtonPanel;
95   private JPanel myTabsPanel;
96   private JLabel myCountLabel;
97   private HyperlinkLabel.Croppable myForeignPluginWarningLabel;
98   private HyperlinkLabel.Croppable myDisableLink;
99   private JPanel myCredentialsPanel;
100   private HyperlinkLabel myCredentialsLabel;
101   private JPanel myForeignPluginWarningPanel;
102   private JPanel myAttachmentWarningPanel;
103   private HyperlinkLabel myAttachmentWarningLabel;
104
105   private int myIndex = 0;
106   private final List<ArrayList<AbstractMessage>> myMergedMessages = new ArrayList<ArrayList<AbstractMessage>>();
107   private List<AbstractMessage> myRawMessages;
108   private final MessagePool myMessagePool;
109   private HeaderlessTabbedPane myTabs;
110   @Nullable
111   private CommentsTabForm myCommentsTabForm;
112   private DetailsTabForm myDetailsTabForm;
113   private AttachmentsTabForm myAttachmentsTabForm;
114
115   private ClearFatalsAction myClearAction = new ClearFatalsAction();
116   private BlameAction myBlameAction;
117   @Nullable
118   private AnalyzeAction myAnalyzeAction;
119   private boolean myMute;
120
121   public IdeErrorsDialog(MessagePool messagePool, @Nullable LogMessage defaultMessage) {
122     super(JOptionPane.getRootFrame(), false);
123     myMessagePool = messagePool;
124     ApplicationEx app = ApplicationManagerEx.getApplicationEx();
125     myInternalMode = app != null && app.isInternal();
126     setTitle(DiagnosticBundle.message("error.list.title"));
127     init();
128     rebuildHeaders();
129     if (defaultMessage == null || !moveSelectionToMessage(defaultMessage)) {
130       moveSelectionToEarliestMessage();
131     }
132     setCancelButtonText(CommonBundle.message("close.action.name"));
133     setModal(false);
134     if (myInternalMode) {
135       if (ourDevelopersList.isEmpty()) {
136         loadDevelopersAsynchronously();
137       } else {
138         myDetailsTabForm.setDevelopers(ourDevelopersList);
139       }
140     }
141   }
142
143   private void loadDevelopersAsynchronously() {
144     Task.Backgroundable task = new Task.Backgroundable(null, "Loading Developers List", true) {
145       private final Collection[] myDevelopers = new Collection[]{Collections.emptyList()};
146
147       @Override
148       public void run(@NotNull ProgressIndicator indicator) {
149         try {
150           myDevelopers[0] = DevelopersLoader.fetchDevelopers(indicator);
151         } catch (IOException e) {
152           //Notifications.Bus.register("Error reporter", NotificationDisplayType.BALLOON);
153           //Notifications.Bus.notify(new Notification("Error reporter", "Communication error",
154           //                                          "Unable to load developers list from server.", NotificationType.WARNING));
155         }
156       }
157
158       @Override
159       public void onSuccess() {
160         Collection<Developer> developers = myDevelopers[0];
161         myDetailsTabForm.setDevelopers(developers);
162         //noinspection AssignmentToStaticFieldFromInstanceMethod
163         ourDevelopersList = developers;
164       }
165     };
166     ProgressManager.getInstance().run(task);
167   }
168
169   private boolean moveSelectionToMessage(LogMessage defaultMessage) {
170     int index = -1;
171     for (int i = 0; i < myMergedMessages.size(); i++) {
172       final AbstractMessage each = getMessageAt(i);
173       if (each == defaultMessage) {
174         index = i;
175         break;
176       }
177     }
178
179     if (index >= 0) {
180       myIndex = index;
181       updateControls();
182       return true;
183     }
184     else {
185       return false;
186     }
187   }
188
189   public void newEntryAdded() {
190     SwingUtilities.invokeLater(new Runnable() {
191       public void run() {
192         rebuildHeaders();
193         updateControls();
194       }
195     });
196   }
197
198   public void poolCleared() {
199     SwingUtilities.invokeLater(new Runnable() {
200       public void run() {
201         doOKAction();
202       }
203     });
204   }
205
206   @Override
207   public void entryWasRead() {
208   }
209
210   @NotNull
211   @Override
212   protected Action[] createActions() {
213     if (SystemInfo.isMac) {
214       return new Action[]{getCancelAction(), myClearAction, myBlameAction};
215     }
216     else {
217       return new Action[]{myClearAction, myBlameAction, getCancelAction()};
218     }
219   }
220
221   @Override
222   protected void createDefaultActions() {
223     super.createDefaultActions();
224     myBlameAction = new BlameAction();
225     myBlameAction.putValue(DialogWrapper.DEFAULT_ACTION, true);
226   }
227
228   private class ForwardAction extends AnAction implements DumbAware {
229     public ForwardAction() {
230       super("Next", null, AllIcons.Actions.Forward);
231       AnAction forward = ActionManager.getInstance().getAction(IdeActions.ACTION_NEXT_TAB);
232       if (forward != null) {
233         registerCustomShortcutSet(forward.getShortcutSet(), getRootPane(), getDisposable());
234       }
235
236     }
237
238     public void actionPerformed(AnActionEvent e) {
239       goForward();
240     }
241
242     public void update(AnActionEvent e) {
243       Presentation presentation = e.getPresentation();
244       presentation.setEnabled(myIndex < myMergedMessages.size() - 1);
245     }
246   }
247
248   private class BackAction extends AnAction implements DumbAware {
249     public BackAction() {
250       super("Previous", null, AllIcons.Actions.Back);
251       AnAction back = ActionManager.getInstance().getAction(IdeActions.ACTION_PREVIOUS_TAB);
252       if (back != null) {
253         registerCustomShortcutSet(back.getShortcutSet(), getRootPane(), getDisposable());
254       }
255     }
256
257     public void actionPerformed(AnActionEvent e) {
258       goBack();
259     }
260
261     public void update(AnActionEvent e) {
262       Presentation presentation = e.getPresentation();
263       presentation.setEnabled(myIndex > 0);
264     }
265   }
266
267   @Override
268   protected JComponent createCenterPanel() {
269     DefaultActionGroup goBack = new DefaultActionGroup();
270     BackAction back = new BackAction();
271     goBack.add(back);
272     ActionToolbar backToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, goBack, true);
273     backToolbar.getComponent().setBorder(IdeBorderFactory.createEmptyBorder());
274     backToolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY);
275     myBackButtonPanel.add(backToolbar.getComponent(), BorderLayout.CENTER);
276
277     DefaultActionGroup goForward = new DefaultActionGroup();
278     ForwardAction forward = new ForwardAction();
279     goForward.add(forward);
280     ActionToolbar forwardToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, goForward, true);
281     forwardToolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY);
282     forwardToolbar.getComponent().setBorder(IdeBorderFactory.createEmptyBorder());
283     myNextButtonPanel.add(forwardToolbar.getComponent(), BorderLayout.CENTER);
284
285     myTabs = new HeaderlessTabbedPane(getDisposable());
286     final LabeledTextComponent.TextListener commentsListener = new LabeledTextComponent.TextListener() {
287       @Override
288       public void textChanged(String newText) {
289         if (myMute) {
290           return;
291         }
292
293         AbstractMessage message = getSelectedMessage();
294         if (message != null) {
295           message.setAdditionalInfo(newText);
296         }
297       }
298     };
299     if (!myInternalMode) {
300       myDetailsTabForm = new DetailsTabForm(null, myInternalMode);
301       myCommentsTabForm = new CommentsTabForm();
302       myCommentsTabForm.addCommentsListener(commentsListener);
303       myTabs.addTab(DiagnosticBundle.message("error.comments.tab.title"), myCommentsTabForm.getContentPane());
304       myDetailsTabForm.setCommentsAreaVisible(false);
305     }
306     else {
307       final AnAction analyzePlatformAction = ActionManager.getInstance().getAction("AnalyzeStacktraceOnError");
308       if (analyzePlatformAction != null) {
309         myAnalyzeAction = new AnalyzeAction(analyzePlatformAction);
310       }
311       myDetailsTabForm = new DetailsTabForm(myAnalyzeAction, myInternalMode);
312       myDetailsTabForm.setCommentsAreaVisible(true);
313       myDetailsTabForm.addCommentsListener(commentsListener);
314     }
315
316     myTabs.addTab(DiagnosticBundle.message("error.details.tab.title"), myDetailsTabForm.getContentPane());
317
318     myAttachmentsTabForm = new AttachmentsTabForm();
319     myAttachmentsTabForm.addInclusionListener(new ChangeListener() {
320       public void stateChanged(final ChangeEvent e) {
321         updateAttachmentWarning(getSelectedMessage());
322       }
323     });
324
325     int activeTabIndex = Integer.parseInt(PropertiesComponent.getInstance().getValue(ACTIVE_TAB_OPTION, "0"));
326     if (activeTabIndex >= myTabs.getTabCount() || activeTabIndex < 0) {
327       activeTabIndex = 0; // may happen if myInternalMode changed since last open
328     }
329
330     myTabs.setSelectedIndex(activeTabIndex);
331
332     myTabs.addChangeListener(new ChangeListener() {
333       @Override
334       public void stateChanged(ChangeEvent e) {
335         final JComponent c = getPreferredFocusedComponent();
336         if (c != null) {
337           IdeFocusManager.findInstanceByComponent(myContentPane).requestFocus(c, true);
338         }
339       }
340     });
341
342     myTabsPanel.add(myTabs, BorderLayout.CENTER);
343
344     myDisableLink.setHyperlinkText(UIUtil.removeMnemonic(DiagnosticBundle.message("error.list.disable.plugin")));
345     myDisableLink.addHyperlinkListener(new HyperlinkListener() {
346       @Override
347       public void hyperlinkUpdate(HyperlinkEvent e) {
348         if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
349           disablePlugin();
350         }
351       }
352     });
353
354     myCredentialsLabel.addHyperlinkListener(new HyperlinkListener() {
355       @Override
356       public void hyperlinkUpdate(HyperlinkEvent e) {
357         if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
358           new JetBrainsAccountDialog(getRootPane()).show();
359           updateCredentialsPane(getSelectedMessage());
360         }
361       }
362     });
363
364     myAttachmentWarningLabel.setIcon(UIUtil.getBalloonWarningIcon());
365     myAttachmentWarningLabel.addHyperlinkListener(new HyperlinkListener() {
366       public void hyperlinkUpdate(final HyperlinkEvent e) {
367         if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
368           myTabs.setSelectedIndex(myTabs.indexOfComponent(myAttachmentsTabForm.getContentPane()));
369           myAttachmentsTabForm.selectFirstIncludedAttachment();
370         }
371       }
372     });
373
374     myDetailsTabForm.addAssigneeListener(new ActionListener() {
375       @Override
376       public void actionPerformed(ActionEvent e) {
377         if (myMute) return;
378
379         AbstractMessage message = getSelectedMessage();
380         if (message != null) {
381           message.setAssigneeId(myDetailsTabForm.getAssigneeId());
382         }
383       }
384     });
385
386     return myContentPane;
387   }
388
389   private void moveSelectionToEarliestMessage() {
390     myIndex = 0;
391     for (int i = 0; i < myMergedMessages.size(); i++) {
392       final AbstractMessage each = getMessageAt(i);
393       if (!each.isRead()) {
394         myIndex = i;
395         break;
396       }
397     }
398
399     updateControls();
400   }
401
402   private void disablePlugin() {
403     final PluginId pluginId = findPluginId(getSelectedMessage().getThrowable());
404     if (pluginId == null) {
405       return;
406     }
407
408     IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
409     final Ref<Boolean> hasDependants = new Ref<Boolean>(false);
410     PluginManager.checkDependants(plugin, new Function<PluginId, IdeaPluginDescriptor>() {
411                                     @Override
412                                     public IdeaPluginDescriptor fun(PluginId pluginId) {
413                                       return PluginManager.getPlugin(pluginId);
414                                     }
415                                   }, new Condition<PluginId>() {
416       @Override
417       public boolean value(PluginId pluginId) {
418         if (PluginManagerCore.CORE_PLUGIN_ID.equals(pluginId.getIdString())) {
419           return true;
420         }
421         hasDependants.set(true);
422         return false;
423       }
424     }
425     );
426
427     Application app = ApplicationManager.getApplication();
428     DisablePluginWarningDialog d =
429       new DisablePluginWarningDialog(getRootPane(), plugin.getName(), hasDependants.get(), app.isRestartCapable());
430     d.show();
431     switch (d.getExitCode()) {
432       case CANCEL_EXIT_CODE:
433         return;
434       case DisablePluginWarningDialog.DISABLE_EXIT_CODE:
435         PluginManager.disablePlugin(pluginId.getIdString());
436         break;
437       case DisablePluginWarningDialog.DISABLE_AND_RESTART_EXIT_CODE:
438         PluginManager.disablePlugin(pluginId.getIdString());
439         app.restart();
440         break;
441     }
442   }
443
444   private void goBack() {
445     myIndex--;
446     updateControls();
447   }
448
449   private void goForward() {
450     myIndex++;
451     updateControls();
452   }
453
454   private void updateControls() {
455     updateCountLabel();
456     final AbstractMessage message = getSelectedMessage();
457     updateInfoLabel(message);
458     updateCredentialsPane(message);
459     updateAssigneePane(message);
460     updateAttachmentWarning(message);
461     myDisableLink.setVisible(canDisablePlugin(message));
462     updateForeignPluginLabel(message != null ? message : null);
463     updateTabs();
464
465     myClearAction.update();
466     myBlameAction.update();
467     if (myAnalyzeAction != null) {
468       myAnalyzeAction.update();
469     }
470   }
471
472   private void updateAttachmentWarning(final AbstractMessage message) {
473     if (message == null) return;
474     final List<Attachment> includedAttachments = ContainerUtil.filter(message.getAttachments(), new Condition<Attachment>() {
475       public boolean value(final Attachment attachment) {
476         return attachment.isIncluded();
477       }
478     });
479     if (!includedAttachments.isEmpty()) {
480       myAttachmentWarningPanel.setVisible(true);
481       if (includedAttachments.size() == 1) {
482         myAttachmentWarningLabel.setHtmlText(
483           DiagnosticBundle.message("diagnostic.error.report.include.attachment.warning", includedAttachments.get(0).getName()));
484       }
485       else {
486         myAttachmentWarningLabel.setHtmlText(
487           DiagnosticBundle.message("diagnostic.error.report.include.attachments.warning", includedAttachments.size()));
488       }
489     }
490     else {
491       myAttachmentWarningPanel.setVisible(false);
492     }
493   }
494
495   private static boolean canDisablePlugin(AbstractMessage message) {
496     if (message == null) {
497       return false;
498     }
499
500     PluginId pluginId = findPluginId(message.getThrowable());
501     return pluginId != null && !ApplicationInfoEx.getInstanceEx().isEssentialPlugin(pluginId.getIdString());
502   }
503
504   private void updateCountLabel() {
505     if (myMergedMessages.isEmpty()) {
506       myCountLabel.setText(DiagnosticBundle.message("error.list.empty"));
507     }
508     else {
509       myCountLabel
510         .setText(DiagnosticBundle.message("error.list.message.index.count", Integer.toString(myIndex + 1), myMergedMessages.size()));
511     }
512   }
513
514
515   private void updateCredentialsPane(AbstractMessage message) {
516     if (message != null) {
517       final ErrorReportSubmitter submitter = getSubmitter(message.getThrowable());
518       if (submitter instanceof ITNReporter) {
519         myCredentialsPanel.setVisible(true);
520         String userName = ErrorReportConfigurable.getInstance().ITN_LOGIN;
521         if (StringUtil.isEmpty(userName)) {
522           myCredentialsLabel.setHtmlText(DiagnosticBundle.message("diagnostic.error.report.submit.error.anonymously"));
523         }
524         else {
525           myCredentialsLabel.setHtmlText(DiagnosticBundle.message("diagnostic.error.report.submit.report.as", userName));
526         }
527         return;
528       }
529     }
530     myCredentialsPanel.setVisible(false);
531   }
532
533   private void updateAssigneePane(AbstractMessage message) {
534     final ErrorReportSubmitter submitter = message != null ? getSubmitter(message.getThrowable()) : null;
535     myDetailsTabForm.setAssigneeVisible(submitter instanceof ITNReporter && myInternalMode);
536   }
537
538   private void updateInfoLabel(AbstractMessage message) {
539     if (message == null) {
540       myInfoLabel.setText("");
541       return;
542     }
543     final Throwable throwable = message.getThrowable();
544     if (throwable instanceof MessagePool.TooManyErrorsException) {
545       myInfoLabel.setText("");
546       return;
547     }
548
549     StringBuilder text = new StringBuilder();
550     PluginId pluginId = findPluginId(throwable);
551     if (pluginId == null) {
552       if (throwable instanceof AbstractMethodError) {
553         text.append(DiagnosticBundle.message("error.list.message.blame.unknown.plugin"));
554       }
555       else {
556         text.append(DiagnosticBundle.message("error.list.message.blame.core", ApplicationNamesInfo.getInstance().getProductName()));
557       }
558     }
559     else {
560       text.append(DiagnosticBundle.message("error.list.message.blame.plugin", PluginManager.getPlugin(pluginId).getName()));
561     }
562     text.append(" ").append(DiagnosticBundle.message("error.list.message.info",
563                                                      DateFormatUtil.formatPrettyDateTime(message.getDate()),
564                                                      myMergedMessages.get(myIndex).size()));
565
566     String url = null;
567     if (message.isSubmitted()) {
568       SubmittedReportInfo info = message.getSubmissionInfo();
569       url = info.getURL();
570       appendSubmissionInformation(info, text);
571       text.append(". ");
572     }
573     else if (message.isSubmitting()) {
574       text.append(" Submitting...");
575     }
576     else if (!message.isRead()) {
577       text.append(" ").append(DiagnosticBundle.message("error.list.message.unread"));
578     }
579     myInfoLabel.setHtmlText(XmlStringUtil.wrapInHtml(text));
580     myInfoLabel.setHyperlinkTarget(url);
581   }
582
583   public static void appendSubmissionInformation(SubmittedReportInfo info, StringBuilder out) {
584     if (info.getStatus() == SubmittedReportInfo.SubmissionStatus.FAILED) {
585       out.append(" ").append(DiagnosticBundle.message("error.list.message.submission.failed"));
586     }
587     else if (info.getURL() != null && info.getLinkText() != null) {
588       out.append(" ").append(DiagnosticBundle.message("error.list.message.submitted.as.link", info.getURL(), info.getLinkText()));
589       if (info.getStatus() == SubmittedReportInfo.SubmissionStatus.DUPLICATE) {
590         out.append(" ").append(DiagnosticBundle.message("error.list.message.duplicate"));
591       }
592     }
593     else {
594       out.append(DiagnosticBundle.message("error.list.message.submitted"));
595     }
596   }
597
598   private void updateForeignPluginLabel(AbstractMessage message) {
599     if (message != null) {
600       final Throwable throwable = message.getThrowable();
601       ErrorReportSubmitter submitter = getSubmitter(throwable);
602       if (submitter == null) {
603         PluginId pluginId = findPluginId(throwable);
604         IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
605         if (plugin == null || PluginManagerMain.isDevelopedByJetBrains(plugin)) {
606           myForeignPluginWarningPanel.setVisible(false);
607           return;
608         }
609
610         myForeignPluginWarningPanel.setVisible(true);
611         String vendor = plugin.getVendor();
612         String contactInfo = plugin.getVendorUrl();
613         if (StringUtil.isEmpty(contactInfo)) {
614           contactInfo = plugin.getVendorEmail();
615         }
616         if (StringUtil.isEmpty(vendor)) {
617           if (StringUtil.isEmpty(contactInfo)) {
618             myForeignPluginWarningLabel.setText(DiagnosticBundle.message("error.dialog.foreign.plugin.warning.text"));
619           }
620           else {
621             myForeignPluginWarningLabel
622               .setHyperlinkText(DiagnosticBundle.message("error.dialog.foreign.plugin.warning.text.vendor") + " ",
623                                 contactInfo, ".");
624             myForeignPluginWarningLabel.setHyperlinkTarget(contactInfo);
625           }
626         }
627         else {
628           if (StringUtil.isEmpty(contactInfo)) {
629             myForeignPluginWarningLabel.setText(DiagnosticBundle.message("error.dialog.foreign.plugin.warning.text.vendor") +
630                                                 " " + vendor + ".");
631           }
632           else {
633             myForeignPluginWarningLabel
634               .setHyperlinkText(
635                 DiagnosticBundle.message("error.dialog.foreign.plugin.warning.text.vendor") + " " + vendor + " (",
636                 contactInfo, ").");
637             myForeignPluginWarningLabel.setHyperlinkTarget(contactInfo);
638           }
639         }
640         myForeignPluginWarningPanel.setVisible(true);
641         return;
642       }
643     }
644     myForeignPluginWarningPanel.setVisible(false);
645   }
646
647   private void updateTabs() {
648     myMute = true;
649     try {
650       if (myInternalMode) {
651         boolean hasAttachment = false;
652         for (ArrayList<AbstractMessage> merged : myMergedMessages) {
653           final AbstractMessage message = merged.get(0);
654           if (!message.getAttachments().isEmpty()) {
655             hasAttachment = true;
656             break;
657           }
658         }
659         myTabs.setHeaderVisible(hasAttachment);
660       }
661
662       final AbstractMessage message = getSelectedMessage();
663       if (myCommentsTabForm != null) {
664         if (message != null) {
665           String msg = message.getMessage();
666           int i = msg.indexOf("\n");
667           if (i != -1) {
668             // take first line
669             msg = msg.substring(0, i);
670           }
671           myCommentsTabForm.setErrorText(msg);
672         }
673         else {
674           myCommentsTabForm.setErrorText(null);
675         }
676         if (message != null) {
677           myCommentsTabForm.setCommentText(message.getAdditionalInfo());
678           myCommentsTabForm.setCommentsTextEnabled(true);
679         }
680         else {
681           myCommentsTabForm.setCommentText(null);
682           myCommentsTabForm.setCommentsTextEnabled(false);
683         }
684       }
685
686       myDetailsTabForm.setDetailsText(message != null ? getDetailsText(message) : null);
687       if (message != null) {
688         myDetailsTabForm.setCommentsText(message.getAdditionalInfo());
689         myDetailsTabForm.setCommentsTextEnabled(true);
690       }
691       else {
692         myDetailsTabForm.setCommentsText(null);
693         myDetailsTabForm.setCommentsTextEnabled(false);
694       }
695
696       myDetailsTabForm.setAssigneeId(message == null ? null : message.getAssigneeId());
697
698       List<Attachment> attachments = message != null ? message.getAttachments() : Collections.<Attachment>emptyList();
699       if (!attachments.isEmpty()) {
700         if (myTabs.indexOfComponent(myAttachmentsTabForm.getContentPane()) == -1) {
701           myTabs.addTab(DiagnosticBundle.message("error.attachments.tab.title"), myAttachmentsTabForm.getContentPane());
702         }
703         myAttachmentsTabForm.setAttachments(attachments);
704       }
705       else {
706         int index = myTabs.indexOfComponent(myAttachmentsTabForm.getContentPane());
707         if (index != -1) {
708           myTabs.removeTabAt(index);
709         }
710       }
711     }
712     finally {
713       myMute = false;
714     }
715   }
716
717   private static String getDetailsText(AbstractMessage message) {
718     final Throwable throwable = message.getThrowable();
719     if (throwable instanceof MessagePool.TooManyErrorsException) {
720       return throwable.getMessage();
721     }
722     else {
723       return new StringBuffer().append(message.getMessage()).append("\n").append(message.getThrowableText()).toString();
724     }
725   }
726
727   private void rebuildHeaders() {
728     myMergedMessages.clear();
729     myRawMessages = myMessagePool.getFatalErrors(true, true);
730
731     Map<String, ArrayList<AbstractMessage>> hash2Messages = mergeMessages(myRawMessages);
732
733     for (final ArrayList<AbstractMessage> abstractMessages : hash2Messages.values()) {
734       myMergedMessages.add(abstractMessages);
735     }
736   }
737
738   private void markAllAsRead() {
739     for (AbstractMessage each : myRawMessages) {
740       each.setRead(true);
741     }
742   }
743
744   @Override
745   public JComponent getPreferredFocusedComponent() {
746     final int selectedIndex = myTabs.getSelectedIndex();
747     JComponent result;
748     if (selectedIndex == 0) {
749       result = myInternalMode ? myDetailsTabForm.getPreferredFocusedComponent() : myCommentsTabForm.getPreferredFocusedComponent();
750     }
751     else if (selectedIndex == 1) {
752       result = myInternalMode ? myAttachmentsTabForm.getPreferredFocusedComponent() : myDetailsTabForm.getPreferredFocusedComponent();
753     }
754     else {
755       result = myAttachmentsTabForm.getPreferredFocusedComponent();
756     }
757     return result != null ? result : super.getPreferredFocusedComponent();
758   }
759
760   private static Map<String, ArrayList<AbstractMessage>> mergeMessages(List<AbstractMessage> aErrors) {
761     Map<String, ArrayList<AbstractMessage>> hash2Messages = new LinkedHashMap<String, ArrayList<AbstractMessage>>();
762     for (final AbstractMessage each : aErrors) {
763       final String hashCode = getThrowableHashCode(each.getThrowable());
764       ArrayList<AbstractMessage> list;
765       if (hash2Messages.containsKey(hashCode)) {
766         list = hash2Messages.get(hashCode);
767       }
768       else {
769         list = new ArrayList<AbstractMessage>();
770         hash2Messages.put(hashCode, list);
771       }
772       list.add(0, each);
773     }
774     return hash2Messages;
775   }
776
777   private AbstractMessage getSelectedMessage() {
778     return getMessageAt(myIndex);
779   }
780
781   private AbstractMessage getMessageAt(int idx) {
782     if (idx < 0 || idx >= myMergedMessages.size()) return null;
783     return myMergedMessages.get(idx).get(0);
784   }
785
786   @Nullable
787   public static PluginId findPluginId(Throwable t) {
788     if (t instanceof PluginException) {
789       return ((PluginException)t).getPluginId();
790     }
791
792     Set<String> visitedClassNames = ContainerUtil.newHashSet();
793     for (StackTraceElement element : t.getStackTrace()) {
794       if (element != null) {
795         String className = element.getClassName();
796         if (visitedClassNames.add(className) && PluginManagerCore.isPluginClass(className)) {
797           PluginId id = PluginManagerCore.getPluginByClassName(className);
798           if (LOG.isDebugEnabled()) {
799             LOG.debug(diagnosePluginDetection(className, id));
800           }
801           return id;
802         }
803       }
804     }
805
806     if (t instanceof NoSuchMethodException) {
807       // check is method called from plugin classes
808       if (t.getMessage() != null) {
809         String className = "";
810         StringTokenizer tok = new StringTokenizer(t.getMessage(), ".");
811         while (tok.hasMoreTokens()) {
812           String token = tok.nextToken();
813           if (token.length() > 0 && Character.isJavaIdentifierStart(token.charAt(0))) {
814             className += token;
815           }
816         }
817
818         if (PluginManager.isPluginClass(className)) {
819           return PluginManager.getPluginByClassName(className);
820         }
821       }
822     }
823     else if (t instanceof ClassNotFoundException) {
824       // check is class from plugin classes
825       if (t.getMessage() != null) {
826         String className = t.getMessage();
827
828         if (PluginManager.isPluginClass(className)) {
829           return PluginManager.getPluginByClassName(className);
830         }
831       }
832     }
833     else if (t instanceof AbstractMethodError && t.getMessage() != null) {
834       String s = t.getMessage();
835       // org.antlr.works.plugin.intellij.PIFileType.getHighlighter(Lcom/intellij/openapi/project/Project;Lcom/intellij/openapi/vfs/VirtualFile;)Lcom/intellij/openapi/fileTypes/SyntaxHighlighter;
836       int pos = s.indexOf('(');
837       if (pos >= 0) {
838         s = s.substring(0, pos);
839         pos = s.lastIndexOf('.');
840         if (pos >= 0) {
841           s = s.substring(0, pos);
842           if (PluginManager.isPluginClass(s)) {
843             return PluginManager.getPluginByClassName(s);
844           }
845         }
846       }
847     }
848
849     else if (t instanceof ExtensionException) {
850       String className = ((ExtensionException)t).getExtensionClass().getName();
851       if (PluginManager.isPluginClass(className)) {
852         return PluginManager.getPluginByClassName(className);
853       }
854     }
855
856     return null;
857   }
858
859   @NotNull
860   private static String diagnosePluginDetection(String className, PluginId id) {
861     String msg = "Detected plugin " + id + " by class " + className;
862     IdeaPluginDescriptor descriptor = PluginManager.getPlugin(id);
863     if (descriptor != null) {
864       msg += "; ideaLoader=" + descriptor.getUseIdeaClassLoader();
865       
866       ClassLoader loader = descriptor.getPluginClassLoader();
867       msg += "; loader=" + loader;
868       if (loader instanceof PluginClassLoader) {
869         msg += "; loaded class: " + ((PluginClassLoader)loader).hasLoadedClass(className);
870       }
871     }
872     return msg;
873   }
874
875   private class ClearFatalsAction extends AbstractAction {
876     protected ClearFatalsAction() {
877       super(DiagnosticBundle.message("error.dialog.clear.action"));
878     }
879
880     @Override
881     public void actionPerformed(ActionEvent e) {
882       myMessagePool.clearFatals();
883       doOKAction();
884     }
885
886     public void update() {
887       putValue(NAME, DiagnosticBundle.message(myMergedMessages.size() > 1 ? "error.dialog.clear.all.action" : "error.dialog.clear.action"));
888       setEnabled(!myMergedMessages.isEmpty());
889     }
890   }
891
892   private class BlameAction extends AbstractAction {
893     protected BlameAction() {
894       super(DiagnosticBundle.message("error.report.to.jetbrains.action"));
895     }
896
897     public void update() {
898       AbstractMessage logMessage = getSelectedMessage();
899       if (logMessage != null) {
900         ErrorReportSubmitter submitter = getSubmitter(logMessage.getThrowable());
901         if (submitter != null) {
902           putValue(NAME, submitter.getReportActionText());
903           setEnabled(!(logMessage.isSubmitting() || logMessage.isSubmitted()));
904           return;
905         }
906       }
907       putValue(NAME, DiagnosticBundle.message("error.report.to.jetbrains.action"));
908       setEnabled(false);
909     }
910
911     public void actionPerformed(ActionEvent e) {
912       boolean closeDialog = myMergedMessages.size() == 1;
913       final AbstractMessage logMessage = getSelectedMessage();
914       boolean reportingStarted = reportMessage(logMessage, closeDialog);
915       if (closeDialog) {
916         if (reportingStarted) {
917           doOKAction();
918         }
919       }
920       else {
921         rebuildHeaders();
922         updateControls();
923       }
924     }
925
926     private boolean reportMessage(final AbstractMessage logMessage, final boolean dialogClosed) {
927       final ErrorReportSubmitter submitter = getSubmitter(logMessage.getThrowable());
928       if (submitter == null) return false;
929
930       logMessage.setSubmitting(true);
931       if (!dialogClosed) {
932         updateControls();
933       }
934       Container parentComponent;
935       if (dialogClosed) {
936         IdeFrame ideFrame = UIUtil.getParentOfType(IdeFrame.class, getContentPane());
937         parentComponent = ideFrame.getComponent();
938       }
939       else {
940         parentComponent = getContentPane();
941       }
942
943       return submitter.submit(
944         getEvents(logMessage), logMessage.getAdditionalInfo(), parentComponent, new Consumer<SubmittedReportInfo>() {
945           @Override
946           public void consume(final SubmittedReportInfo submittedReportInfo) {
947             logMessage.setSubmitting(false);
948             logMessage.setSubmitted(submittedReportInfo);
949             ApplicationManager.getApplication().invokeLater(new Runnable() {
950               @Override
951               public void run() {
952                 if (!dialogClosed) {
953                   updateOnSubmit();
954                 }
955               }
956             });
957           }
958         });
959     }
960
961     private IdeaLoggingEvent[] getEvents(final AbstractMessage logMessage) {
962       if (logMessage instanceof GroupedLogMessage) {
963         final List<AbstractMessage> messages = ((GroupedLogMessage)logMessage).getMessages();
964         IdeaLoggingEvent[] res = new IdeaLoggingEvent[messages.size()];
965         for (int i = 0; i < res.length; i++) {
966           res[i] = getEvent(messages.get(i));
967         }
968         return res;
969       }
970       return new IdeaLoggingEvent[]{getEvent(logMessage)};
971     }
972
973     private IdeaLoggingEvent getEvent(final AbstractMessage logMessage) {
974       if (logMessage instanceof LogMessageEx) {
975         return ((LogMessageEx)logMessage).toEvent();
976       }
977       return new IdeaLoggingEvent(logMessage.getMessage(), logMessage.getThrowable()) {
978         @Override
979         public AbstractMessage getData() {
980           return logMessage;
981         }
982       };
983     }
984   }
985
986   protected void updateOnSubmit() {
987     updateControls();
988   }
989
990   public void calcData(DataKey key, DataSink sink) {
991     if (CURRENT_TRACE_KEY == key) {
992       final AbstractMessage message = getSelectedMessage();
993       if (message != null) {
994         sink.put(CURRENT_TRACE_KEY, getDetailsText(message));
995       }
996     }
997   }
998
999   @Nullable
1000   public static ErrorReportSubmitter getSubmitter(final Throwable throwable) {
1001     if (throwable instanceof MessagePool.TooManyErrorsException || throwable instanceof AbstractMethodError) {
1002       return null;
1003     }
1004     final PluginId pluginId = findPluginId(throwable);
1005     final ErrorReportSubmitter[] reporters;
1006     try {
1007       reporters = Extensions.getExtensions(ExtensionPoints.ERROR_HANDLER_EP);
1008     }
1009     catch (Throwable t) {
1010       return null;
1011     }
1012     IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
1013     if (plugin == null) {
1014       return getCorePluginSubmitter(reporters);
1015     }
1016     for (ErrorReportSubmitter reporter : reporters) {
1017       final PluginDescriptor descriptor = reporter.getPluginDescriptor();
1018       if (descriptor != null && Comparing.equal(pluginId, descriptor.getPluginId())) {
1019         return reporter;
1020       }
1021     }
1022     if (PluginManagerMain.isDevelopedByJetBrains(plugin)) {
1023       return getCorePluginSubmitter(reporters);
1024     }
1025     return null;
1026   }
1027
1028   @Nullable
1029   private static ErrorReportSubmitter getCorePluginSubmitter(ErrorReportSubmitter[] reporters) {
1030     for (ErrorReportSubmitter reporter : reporters) {
1031       final PluginDescriptor descriptor = reporter.getPluginDescriptor();
1032       if (descriptor == null || PluginId.getId(PluginManagerCore.CORE_PLUGIN_ID) == descriptor.getPluginId()) {
1033         return reporter;
1034       }
1035     }
1036     return null;
1037   }
1038
1039   @Override
1040   public void doOKAction() {
1041     onClose();
1042     super.doOKAction();
1043   }
1044
1045   private void onClose() {
1046     markAllAsRead();
1047     PropertiesComponent.getInstance().setValue(ACTIVE_TAB_OPTION, String.valueOf(myTabs.getSelectedIndex()));
1048   }
1049
1050   @Override
1051   public void doCancelAction() {
1052     onClose();
1053     super.doCancelAction();
1054   }
1055
1056   @Override
1057   protected String getDimensionServiceKey() {
1058     return "IdeErrosDialog";
1059   }
1060
1061   private static String getThrowableHashCode(Throwable exception) {
1062     try {
1063       return md5(StringUtil.getThrowableText(exception), "stack-trace");
1064     }
1065     catch (NoSuchAlgorithmException e) {
1066       LOG.error(e);
1067       return "";
1068     }
1069   }
1070
1071   private static String md5(String buffer, @NonNls String key) throws NoSuchAlgorithmException {
1072     MessageDigest md5 = MessageDigest.getInstance("MD5");
1073     md5.update(buffer.getBytes());
1074     byte[] code = md5.digest(key.getBytes());
1075     BigInteger bi = new BigInteger(code).abs();
1076     return bi.abs().toString(16);
1077   }
1078
1079   private class AnalyzeAction extends AbstractAction {
1080     private final AnAction myAnalyze;
1081
1082     public AnalyzeAction(AnAction analyze) {
1083       super(analyze.getTemplatePresentation().getText());
1084       putValue(Action.MNEMONIC_KEY, analyze.getTemplatePresentation().getMnemonic());
1085       myAnalyze = analyze;
1086     }
1087
1088     public void update() {
1089       setEnabled(getSelectedMessage() != null);
1090     }
1091
1092     public void actionPerformed(ActionEvent e) {
1093       DataContext dataContext = ((DataManagerImpl)DataManager.getInstance()).getDataContextTest((Component)e.getSource());
1094       AnActionEvent event = AnActionEvent.createFromAnAction(myAnalyze, null, ActionPlaces.UNKNOWN, dataContext);
1095       Project project = CommonDataKeys.PROJECT.getData(dataContext);
1096       if (project != null) {
1097         myAnalyze.actionPerformed(event);
1098         doOKAction();
1099       }
1100     }
1101   }
1102 }