gradle: add CLI-style task execution action
[idea/community.git] / platform / external-system-impl / src / com / intellij / openapi / externalSystem / service / notification / ExternalSystemNotificationManager.java
1 package com.intellij.openapi.externalSystem.service.notification;
2
3 import com.intellij.execution.rmi.RemoteUtil;
4 import com.intellij.ide.errorTreeView.*;
5 import com.intellij.notification.Notification;
6 import com.intellij.notification.NotificationGroup;
7 import com.intellij.openapi.application.Application;
8 import com.intellij.openapi.application.ApplicationManager;
9 import com.intellij.openapi.application.ModalityState;
10 import com.intellij.openapi.components.ServiceManager;
11 import com.intellij.openapi.externalSystem.ExternalSystemConfigurableAware;
12 import com.intellij.openapi.externalSystem.ExternalSystemManager;
13 import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
14 import com.intellij.openapi.externalSystem.model.LocationAwareExternalSystemException;
15 import com.intellij.openapi.externalSystem.model.ProjectSystemId;
16 import com.intellij.openapi.externalSystem.service.project.manage.ExternalProjectsManager;
17 import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
18 import com.intellij.openapi.externalSystem.util.ExternalSystemBundle;
19 import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
20 import com.intellij.openapi.externalSystem.view.ExternalProjectsView;
21 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.Disposer;
24 import com.intellij.openapi.util.Key;
25 import com.intellij.openapi.util.Pair;
26 import com.intellij.openapi.util.text.StringUtil;
27 import com.intellij.openapi.vfs.VirtualFile;
28 import com.intellij.openapi.wm.ToolWindow;
29 import com.intellij.openapi.wm.ToolWindowId;
30 import com.intellij.openapi.wm.ToolWindowManager;
31 import com.intellij.pom.Navigatable;
32 import com.intellij.ui.EditorNotifications;
33 import com.intellij.ui.content.Content;
34 import com.intellij.ui.content.ContentFactory;
35 import com.intellij.ui.content.MessageView;
36 import com.intellij.util.ObjectUtils;
37 import com.intellij.util.concurrency.SequentialTaskExecutor;
38 import com.intellij.util.containers.ContainerUtil;
39 import com.intellij.util.ui.UIUtil;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.jetbrains.ide.PooledThreadExecutor;
43
44 import java.util.Iterator;
45 import java.util.List;
46 import java.util.Set;
47
48 /**
49  * This class is responsible for ide user by external system integration-specific events.
50  * <p/>
51  * One example use-case is a situation when an error occurs during external project refresh. We need to
52  * show corresponding message to the end-user.
53  * <p/>
54  * Thread-safe.
55  *
56  * @author Denis Zhdanov, Vladislav Soroka
57  * @since 3/21/12 4:04 PM
58  */
59 public class ExternalSystemNotificationManager {
60   @NotNull private static final Key<Pair<NotificationSource, ProjectSystemId>> CONTENT_ID_KEY = Key.create("CONTENT_ID");
61
62   @NotNull private final SequentialTaskExecutor myUpdater = new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE);
63
64   @NotNull private final Project myProject;
65   @NotNull private final List<Notification> myNotifications;
66   @NotNull private final Set<ProjectSystemId> initializedExternalSystem;
67   @NotNull private final MessageCounter myMessageCounter;
68
69   public ExternalSystemNotificationManager(@NotNull final Project project) {
70     myProject = project;
71     myNotifications = ContainerUtil.newArrayList();
72     initializedExternalSystem = ContainerUtil.newHashSet();
73     myMessageCounter = new MessageCounter();
74   }
75
76   @NotNull
77   public static ExternalSystemNotificationManager getInstance(@NotNull Project project) {
78     return ServiceManager.getService(project, ExternalSystemNotificationManager.class);
79   }
80
81   public void processExternalProjectRefreshError(@NotNull Throwable error,
82                                                  @NotNull String externalProjectName,
83                                                  @NotNull ProjectSystemId externalSystemId) {
84     if (myProject.isDisposed() || !myProject.isOpen()) {
85       return;
86     }
87     ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId);
88     if (!(manager instanceof ExternalSystemConfigurableAware)) {
89       return;
90     }
91
92     String title =
93       ExternalSystemBundle.message("notification.project.refresh.fail.title", externalSystemId.getReadableName(), externalProjectName);
94     String message = ExternalSystemApiUtil.buildErrorMessage(error);
95     NotificationCategory notificationCategory = NotificationCategory.ERROR;
96     String filePath = null;
97     Integer line = null;
98     Integer column = null;
99
100     //noinspection ThrowableResultOfMethodCallIgnored
101     Throwable unwrapped = RemoteUtil.unwrap(error);
102     if (unwrapped instanceof LocationAwareExternalSystemException) {
103       LocationAwareExternalSystemException locationAwareExternalSystemException = (LocationAwareExternalSystemException)unwrapped;
104       filePath = locationAwareExternalSystemException.getFilePath();
105       line = locationAwareExternalSystemException.getLine();
106       column = locationAwareExternalSystemException.getColumn();
107     }
108
109     NotificationData notificationData =
110       new NotificationData(
111         title, message, notificationCategory, NotificationSource.PROJECT_SYNC,
112         filePath, ObjectUtils.notNull(line, -1), ObjectUtils.notNull(column, -1), false);
113
114     for (ExternalSystemNotificationExtension extension : ExternalSystemNotificationExtension.EP_NAME.getExtensions()) {
115       if (!externalSystemId.equals(extension.getTargetExternalSystemId())) {
116         continue;
117       }
118       extension.customize(notificationData, myProject, error);
119     }
120
121     EditorNotifications.getInstance(myProject).updateAllNotifications();
122     showNotification(externalSystemId, notificationData);
123   }
124
125   public void showNotification(@NotNull final ProjectSystemId externalSystemId, @NotNull final NotificationData notificationData) {
126     myUpdater.execute(new Runnable() {
127       @Override
128       public void run() {
129         if (myProject.isDisposed()) return;
130
131         if (!initializedExternalSystem.contains(externalSystemId)) {
132           final Application app = ApplicationManager.getApplication();
133           Runnable action = new Runnable() {
134             public void run() {
135               app.runWriteAction(new Runnable() {
136                 public void run() {
137                   if (myProject.isDisposed()) return;
138                   ExternalSystemUtil.ensureToolWindowContentInitialized(myProject, externalSystemId);
139                   initializedExternalSystem.add(externalSystemId);
140                 }
141               });
142             }
143           };
144           if (app.isDispatchThread()) {
145             action.run();
146           }
147           else {
148             app.invokeAndWait(action, ModalityState.defaultModalityState());
149           }
150         }
151
152         NotificationGroup group;
153         if (notificationData.getBalloonGroup() == null) {
154           ExternalProjectsView externalProjectsView = ExternalProjectsManager.getInstance(myProject).getExternalProjectsView(externalSystemId);
155           group = externalProjectsView != null ? externalProjectsView.getNotificationGroup() : null;
156         }
157         else {
158           final NotificationGroup registeredGroup = NotificationGroup.findRegisteredGroup(notificationData.getBalloonGroup());
159           group = registeredGroup != null ? registeredGroup : NotificationGroup.balloonGroup(notificationData.getBalloonGroup());
160         }
161         if (group == null) return;
162
163         final Notification notification = group.createNotification(
164           notificationData.getTitle(), notificationData.getMessage(),
165           notificationData.getNotificationCategory().getNotificationType(), notificationData.getListener());
166
167         myNotifications.add(notification);
168
169         if (notificationData.isBalloonNotification()) {
170           applyNotification(notification);
171         }
172         else {
173           addMessage(notification, externalSystemId, notificationData);
174         }
175       }
176     });
177   }
178
179   public void openMessageView(@NotNull final ProjectSystemId externalSystemId, @NotNull final NotificationSource notificationSource) {
180     UIUtil.invokeLaterIfNeeded(new Runnable() {
181       @Override
182       public void run() {
183         prepareMessagesView(externalSystemId, notificationSource, true);
184       }
185     });
186   }
187
188   public void clearNotifications(@NotNull final NotificationSource notificationSource,
189                                  @NotNull final ProjectSystemId externalSystemId) {
190     clearNotifications(null, notificationSource, externalSystemId);
191   }
192
193   public void clearNotifications(@Nullable final String groupName,
194                                  @NotNull final NotificationSource notificationSource,
195                                  @NotNull final ProjectSystemId externalSystemId) {
196     myMessageCounter.remove(groupName, notificationSource, externalSystemId);
197     myUpdater.execute(new Runnable() {
198       @Override
199       public void run() {
200         for (Iterator<Notification> iterator = myNotifications.iterator(); iterator.hasNext(); ) {
201           Notification notification = iterator.next();
202           if (groupName == null || groupName.equals(notification.getGroupId())) {
203             notification.expire();
204             iterator.remove();
205           }
206         }
207
208         final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
209         if (toolWindow == null) return;
210
211         final Pair<NotificationSource, ProjectSystemId> contentIdPair = Pair.create(notificationSource, externalSystemId);
212         final MessageView messageView = ServiceManager.getService(myProject, MessageView.class);
213         UIUtil.invokeLaterIfNeeded(new Runnable() {
214           @Override
215           public void run() {
216             for (Content content : messageView.getContentManager().getContents()) {
217               if (!content.isPinned() && contentIdPair.equals(content.getUserData(CONTENT_ID_KEY))) {
218                 if (groupName == null) {
219                   messageView.getContentManager().removeContent(content, true);
220                 }
221                 else {
222                   assert content.getComponent() instanceof NewEditableErrorTreeViewPanel;
223                   NewEditableErrorTreeViewPanel errorTreeView = (NewEditableErrorTreeViewPanel)content.getComponent();
224                   ErrorViewStructure errorViewStructure = errorTreeView.getErrorViewStructure();
225                   errorViewStructure.removeGroup(groupName);
226                 }
227               }
228             }
229           }
230         });
231       }
232     });
233   }
234
235   public int getMessageCount(@NotNull final NotificationSource notificationSource,
236                              @Nullable final NotificationCategory notificationCategory,
237                              @NotNull final ProjectSystemId externalSystemId) {
238     return getMessageCount(null, notificationSource, notificationCategory, externalSystemId);
239   }
240
241   public int getMessageCount(@Nullable final String groupName,
242                              @NotNull final NotificationSource notificationSource,
243                              @Nullable final NotificationCategory notificationCategory,
244                              @NotNull final ProjectSystemId externalSystemId) {
245     return myMessageCounter.getCount(groupName, notificationSource, notificationCategory, externalSystemId);
246   }
247
248   private void addMessage(@NotNull final Notification notification,
249                           @NotNull final ProjectSystemId externalSystemId,
250                           @NotNull final NotificationData notificationData) {
251     final VirtualFile virtualFile =
252       notificationData.getFilePath() != null ? ExternalSystemUtil.waitForTheFile(notificationData.getFilePath()) : null;
253     final String groupName = virtualFile != null ? virtualFile.getPresentableUrl() : notificationData.getTitle();
254
255     myMessageCounter
256       .increment(groupName, notificationData.getNotificationSource(), notificationData.getNotificationCategory(), externalSystemId);
257
258     int line = notificationData.getLine() - 1;
259     int column = notificationData.getColumn() - 1;
260     if (virtualFile == null) line = column = -1;
261     final int guiLine = line < 0 ? -1 : line + 1;
262     final int guiColumn = column < 0 ? 0 : column + 1;
263
264     final Navigatable navigatable = notificationData.getNavigatable() != null
265                                     ? notificationData.getNavigatable()
266                                     : virtualFile != null ? new OpenFileDescriptor(myProject, virtualFile, line, column) : null;
267
268     final ErrorTreeElementKind kind =
269       ErrorTreeElementKind.convertMessageFromCompilerErrorType(notificationData.getNotificationCategory().getMessageCategory());
270     final String[] message = notificationData.getMessage().split("\n");
271     final String exportPrefix = NewErrorTreeViewPanel.createExportPrefix(guiLine);
272     final String rendererPrefix = NewErrorTreeViewPanel.createRendererPrefix(guiLine, guiColumn);
273
274     UIUtil.invokeLaterIfNeeded(new Runnable() {
275       @Override
276       public void run() {
277         boolean activate =
278           notificationData.getNotificationCategory() == NotificationCategory.ERROR ||
279           notificationData.getNotificationCategory() == NotificationCategory.WARNING;
280         final NewErrorTreeViewPanel errorTreeView =
281           prepareMessagesView(externalSystemId, notificationData.getNotificationSource(), activate);
282         final GroupingElement groupingElement = errorTreeView.getErrorViewStructure().getGroupingElement(groupName, null, virtualFile);
283         final NavigatableMessageElement navigatableMessageElement;
284         if (notificationData.hasLinks()) {
285           navigatableMessageElement = new EditableNotificationMessageElement(
286             notification,
287             kind,
288             groupingElement,
289             message,
290             navigatable,
291             exportPrefix,
292             rendererPrefix);
293         }
294         else {
295           navigatableMessageElement = new NotificationMessageElement(
296             kind,
297             groupingElement,
298             message,
299             navigatable,
300             exportPrefix,
301             rendererPrefix);
302         }
303
304         errorTreeView.getErrorViewStructure().addNavigatableMessage(groupName, navigatableMessageElement);
305         errorTreeView.updateTree();
306       }
307     });
308   }
309
310   private void applyNotification(@NotNull final Notification notification) {
311     if (!myProject.isDisposed() && myProject.isOpen()) {
312       notification.notify(myProject);
313     }
314   }
315
316   @NotNull
317   public NewErrorTreeViewPanel prepareMessagesView(@NotNull final ProjectSystemId externalSystemId,
318                                                     @NotNull final NotificationSource notificationSource,
319                                                     boolean activateView) {
320     ApplicationManager.getApplication().assertIsDispatchThread();
321
322     final NewErrorTreeViewPanel errorTreeView;
323     final String contentDisplayName = getContentDisplayName(notificationSource, externalSystemId);
324     final Pair<NotificationSource, ProjectSystemId> contentIdPair = Pair.create(notificationSource, externalSystemId);
325     Content targetContent = findContent(contentIdPair, contentDisplayName);
326
327     final MessageView messageView = ServiceManager.getService(myProject, MessageView.class);
328     if (targetContent == null || !contentIdPair.equals(targetContent.getUserData(CONTENT_ID_KEY))) {
329       errorTreeView = new NewEditableErrorTreeViewPanel(myProject, null, true, true, null);
330       targetContent = ContentFactory.SERVICE.getInstance().createContent(errorTreeView, contentDisplayName, true);
331       targetContent.putUserData(CONTENT_ID_KEY, contentIdPair);
332
333       messageView.getContentManager().addContent(targetContent);
334       Disposer.register(targetContent, errorTreeView);
335     }
336     else {
337       assert targetContent.getComponent() instanceof NewEditableErrorTreeViewPanel;
338       errorTreeView = (NewEditableErrorTreeViewPanel)targetContent.getComponent();
339     }
340
341     messageView.getContentManager().setSelectedContent(targetContent);
342     final ToolWindow tw = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
343     if (activateView && tw != null && !tw.isActive()) {
344       tw.activate(null, false);
345     }
346     return errorTreeView;
347   }
348
349   @Nullable
350   private Content findContent(@NotNull Pair<NotificationSource, ProjectSystemId> contentIdPair, @NotNull String contentDisplayName) {
351     Content targetContent = null;
352     final MessageView messageView = ServiceManager.getService(myProject, MessageView.class);
353     for (Content content : messageView.getContentManager().getContents()) {
354       if (contentIdPair.equals(content.getUserData(CONTENT_ID_KEY))
355           && StringUtil.equals(content.getDisplayName(), contentDisplayName) && !content.isPinned()) {
356         targetContent = content;
357       }
358     }
359     return targetContent;
360   }
361
362   @NotNull
363   public static String getContentDisplayName(@NotNull final NotificationSource notificationSource,
364                                              @NotNull final ProjectSystemId externalSystemId) {
365     final String contentDisplayName;
366     switch (notificationSource) {
367       case PROJECT_SYNC:
368         contentDisplayName =
369           ExternalSystemBundle.message("notification.messages.project.sync.tab.name", externalSystemId.getReadableName());
370         break;
371       case TASK_EXECUTION:
372         contentDisplayName =
373           ExternalSystemBundle.message("notification.messages.task.execution.tab.name", externalSystemId.getReadableName());
374         break;
375       default:
376         throw new AssertionError("unsupported notification source found: " + notificationSource);
377     }
378     return contentDisplayName;
379   }
380 }