diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/ExternalSystemNotificationManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/ExternalSystemNotificationManager.java index 11762b633c65..e577f0b75b46 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/ExternalSystemNotificationManager.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/ExternalSystemNotificationManager.java @@ -52,9 +52,10 @@ import com.intellij.ui.content.ContentFactory; import com.intellij.ui.content.MessageView; import com.intellij.util.ObjectUtils; import com.intellij.util.SmartList; -import com.intellij.util.concurrency.SequentialTaskExecutor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -63,7 +64,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ExecutorService; /** * This class is responsible for ide user by external system integration-specific events. @@ -77,11 +77,10 @@ import java.util.concurrent.ExecutorService; * @since 3/21/12 4:04 PM */ public class ExternalSystemNotificationManager implements Disposable { + @NotNull private static final Key> CONTENT_ID_KEY = Key.create("CONTENT_ID"); - - @NotNull private final ExecutorService myUpdater = SequentialTaskExecutor.createSequentialApplicationPoolExecutor("ExternalSystemNotificationManager pool"); - - @NotNull private final Project myProject; + @NotNull private final MergingUpdateQueue myUpdateQueue; + @SuppressWarnings("StatefulEp") @Nullable private volatile Project myProject; @NotNull private final Set myNotifications; @NotNull private final Map myUniqueNotifications; @NotNull private final Set initializedExternalSystem; @@ -93,6 +92,7 @@ public class ExternalSystemNotificationManager implements Disposable { myUniqueNotifications = ContainerUtil.newConcurrentMap(); initializedExternalSystem = ContainerUtil.newConcurrentSet(); myMessageCounter = new MessageCounter(); + myUpdateQueue = new MergingUpdateQueue(getClass() + " updates", 500, true, null, this, null, false); } @NotNull @@ -103,9 +103,12 @@ public class ExternalSystemNotificationManager implements Disposable { public void processExternalProjectRefreshError(@NotNull Throwable error, @NotNull String externalProjectName, @NotNull ProjectSystemId externalSystemId) { - if (myProject.isDisposed() || !myProject.isOpen()) { + if (isDisposedOrNotOpen()) { return; } + assert myProject != null; + Project project = myProject; + ExternalSystemManager manager = ExternalSystemApiUtil.getManager(externalSystemId); if (!(manager instanceof ExternalSystemConfigurableAware)) { return; @@ -138,10 +141,10 @@ public class ExternalSystemNotificationManager implements Disposable { if (!externalSystemId.equals(targetExternalSystemId) && !targetExternalSystemId.equals(ProjectSystemId.IDE)) { continue; } - extension.customize(notificationData, myProject, error); + extension.customize(notificationData, project, error); } - EditorNotifications.getInstance(myProject).updateAllNotifications(); + EditorNotifications.getInstance(project).updateAllNotifications(); showNotification(externalSystemId, notificationData); } @@ -157,6 +160,7 @@ public class ExternalSystemNotificationManager implements Disposable { public void showNotification(@NotNull final ProjectSystemId externalSystemId, @NotNull final NotificationData notificationData, @Nullable Key notificationKey) { + Disposer.register(this, notificationData); if (ApplicationManager.getApplication().isUnitTestMode()) { @SuppressWarnings("UseOfSystemOutOrSystemErr") PrintStream out = notificationData.getNotificationCategory() == NotificationCategory.INFO ? System.out : System.err; @@ -165,54 +169,60 @@ public class ExternalSystemNotificationManager implements Disposable { } if (notificationKey != null && isNotificationActive(notificationKey)) return; - myUpdater.submit(() -> { - if (myProject.isDisposed()) return; + myUpdateQueue.queue(new Update(new Object()) { - final Application app = ApplicationManager.getApplication(); - Runnable action = () -> { - if (!initializedExternalSystem.contains(externalSystemId)) { - app.runWriteAction(() -> { - if (myProject.isDisposed()) return; - ExternalSystemUtil.ensureToolWindowContentInitialized(myProject, externalSystemId); - initializedExternalSystem.add(externalSystemId); - }); - } - if (myProject.isDisposed()) return; - NotificationGroup group; - if (notificationData.getBalloonGroup() == null) { - ExternalProjectsView externalProjectsView = - ExternalProjectsManagerImpl.getInstance(myProject).getExternalProjectsView(externalSystemId); - group = externalProjectsView instanceof ExternalProjectsViewImpl ? - ((ExternalProjectsViewImpl)externalProjectsView).getNotificationGroup() : null; - } - else { - final NotificationGroup registeredGroup = NotificationGroup.findRegisteredGroup(notificationData.getBalloonGroup()); - group = registeredGroup != null ? registeredGroup : NotificationGroup.balloonGroup(notificationData.getBalloonGroup()); - } - if (group == null) return; + @Override + public void run() { + if (isDisposedOrNotOpen()) return; + assert myProject != null; + Project project = myProject; - final Notification notification = group.createNotification( - notificationData.getTitle(), notificationData.getMessage(), - notificationData.getNotificationCategory().getNotificationType(), notificationData.getListener()); - - if (notificationKey == null) { - myNotifications.add(notification); - } - else { - Notification oldNotification = myUniqueNotifications.put(notificationKey, notification); - if (oldNotification != null) { - oldNotification.expire(); + final Application app = ApplicationManager.getApplication(); + Runnable action = () -> { + if (!initializedExternalSystem.contains(externalSystemId)) { + app.runWriteAction(() -> { + if (isDisposedOrNotOpen()) return; + ExternalSystemUtil.ensureToolWindowContentInitialized(project, externalSystemId); + initializedExternalSystem.add(externalSystemId); + }); } - } + if (isDisposedOrNotOpen()) return; + NotificationGroup group; + if (notificationData.getBalloonGroup() == null) { + ExternalProjectsView externalProjectsView = + ExternalProjectsManagerImpl.getInstance(project).getExternalProjectsView(externalSystemId); + group = externalProjectsView instanceof ExternalProjectsViewImpl ? + ((ExternalProjectsViewImpl)externalProjectsView).getNotificationGroup() : null; + } + else { + final NotificationGroup registeredGroup = NotificationGroup.findRegisteredGroup(notificationData.getBalloonGroup()); + group = registeredGroup != null ? registeredGroup : NotificationGroup.balloonGroup(notificationData.getBalloonGroup()); + } + if (group == null) return; - if (notificationData.isBalloonNotification()) { - applyNotification(notification); - } - else { - addMessage(notification, externalSystemId, notificationData); - } - }; - app.invokeLater(action, ModalityState.defaultModalityState(), myProject.getDisposed()); + final Notification notification = group.createNotification( + notificationData.getTitle(), notificationData.getMessage(), + notificationData.getNotificationCategory().getNotificationType(), notificationData.getListener()); + + if (notificationKey == null) { + myNotifications.add(notification); + } + else { + Notification oldNotification = myUniqueNotifications.put(notificationKey, notification); + if (oldNotification != null) { + oldNotification.expire(); + } + } + + if (notificationData.isBalloonNotification()) { + applyNotification(notification); + } + else { + addMessage(notification, externalSystemId, notificationData); + } + }; + app.invokeLater(action, ModalityState.defaultModalityState(), project.getDisposed()); + } }); } @@ -231,46 +241,52 @@ public class ExternalSystemNotificationManager implements Disposable { myMessageCounter.remove(groupName, notificationSource, externalSystemId); if(ApplicationManager.getApplication().isUnitTestMode()) return; - myUpdater.submit(() -> { - if (myProject.isDisposed()) return; - for (Iterator iterator = myNotifications.iterator(); iterator.hasNext(); ) { - Notification notification = iterator.next(); - if (groupName == null || groupName.equals(notification.getGroupId())) { - notification.expire(); - iterator.remove(); - } - } + final Pair contentIdPair = Pair.create(notificationSource, externalSystemId); + myUpdateQueue.queue(new Update(new Object()) { + @Override + public void run() { + if (isDisposedOrNotOpen()) return; + assert myProject != null; + Project project = myProject; - List toRemove = new SmartList<>(); - myUniqueNotifications.forEach((key, notification) -> { - if (groupName == null || groupName.equals(notification.getGroupId())) { - notification.expire(); - toRemove.add(key); - } - }); - toRemove.forEach(myUniqueNotifications::remove); - - final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW); - if (toolWindow == null) return; - - final Pair contentIdPair = Pair.create(notificationSource, externalSystemId); - final MessageView messageView = ServiceManager.getService(myProject, MessageView.class); - UIUtil.invokeLaterIfNeeded(() -> { - if (myProject.isDisposed()) return; - for (Content content : messageView.getContentManager().getContents()) { - if (!content.isPinned() && contentIdPair.equals(content.getUserData(CONTENT_ID_KEY))) { - if (groupName == null) { - messageView.getContentManager().removeContent(content, true); - } - else { - assert content.getComponent() instanceof NewEditableErrorTreeViewPanel; - NewEditableErrorTreeViewPanel errorTreeView = (NewEditableErrorTreeViewPanel)content.getComponent(); - ErrorViewStructure errorViewStructure = errorTreeView.getErrorViewStructure(); - errorViewStructure.removeGroup(groupName); - } + for (Iterator iterator = myNotifications.iterator(); iterator.hasNext(); ) { + Notification notification = iterator.next(); + if (groupName == null || groupName.equals(notification.getGroupId())) { + notification.expire(); + iterator.remove(); } } - }); + + List toRemove = new SmartList<>(); + myUniqueNotifications.forEach((key, notification) -> { + if (groupName == null || groupName.equals(notification.getGroupId())) { + notification.expire(); + toRemove.add(key); + } + }); + toRemove.forEach(myUniqueNotifications::remove); + + final ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(ToolWindowId.MESSAGES_WINDOW); + if (toolWindow == null) return; + + final MessageView messageView = ServiceManager.getService(project, MessageView.class); + UIUtil.invokeLaterIfNeeded(() -> { + if (project.isDisposed()) return; + for (Content content : messageView.getContentManager().getContents()) { + if (!content.isPinned() && contentIdPair.equals(content.getUserData(CONTENT_ID_KEY))) { + if (groupName == null) { + messageView.getContentManager().removeContent(content, true); + } + else { + assert content.getComponent() instanceof NewEditableErrorTreeViewPanel; + NewEditableErrorTreeViewPanel errorTreeView = (NewEditableErrorTreeViewPanel)content.getComponent(); + ErrorViewStructure errorViewStructure = errorTreeView.getErrorViewStructure(); + errorViewStructure.removeGroup(groupName); + } + } + } + }); + } }); } @@ -303,9 +319,14 @@ public class ExternalSystemNotificationManager implements Disposable { final int guiLine = line < 0 ? -1 : line + 1; final int guiColumn = column < 0 ? 0 : column + 1; + if (isDisposedOrNotOpen()) return; + assert myProject != null; + Project project = myProject; final Navigatable navigatable = notificationData.getNavigatable() != null ? notificationData.getNavigatable() - : virtualFile != null ? new OpenFileDescriptor(myProject, virtualFile, line, column) : NonNavigatable.INSTANCE; + : virtualFile != null + ? new OpenFileDescriptor(project, virtualFile, line, column) + : NonNavigatable.INSTANCE; final ErrorTreeElementKind kind = ErrorTreeElementKind.convertMessageFromCompilerErrorType(notificationData.getNotificationCategory().getMessageCategory()); @@ -347,7 +368,7 @@ public class ExternalSystemNotificationManager implements Disposable { } private void applyNotification(@NotNull final Notification notification) { - if (!myProject.isDisposed() && myProject.isOpen()) { + if (!isDisposedOrNotOpen()) { notification.notify(myProject); } } @@ -363,6 +384,7 @@ public class ExternalSystemNotificationManager implements Disposable { final Pair contentIdPair = Pair.create(notificationSource, externalSystemId); Content targetContent = findContent(contentIdPair, contentDisplayName); + assert myProject != null; final MessageView messageView = ServiceManager.getService(myProject, MessageView.class); if (targetContent == null || !contentIdPair.equals(targetContent.getUserData(CONTENT_ID_KEY))) { errorTreeView = new NewEditableErrorTreeViewPanel(myProject, null, true, true, null); @@ -388,6 +410,7 @@ public class ExternalSystemNotificationManager implements Disposable { @Nullable private Content findContent(@NotNull Pair contentIdPair, @NotNull String contentDisplayName) { Content targetContent = null; + assert myProject != null; final MessageView messageView = ServiceManager.getService(myProject, MessageView.class); for (Content content : messageView.getContentManager().getContents()) { if (contentIdPair.equals(content.getUserData(CONTENT_ID_KEY)) @@ -419,8 +442,13 @@ public class ExternalSystemNotificationManager implements Disposable { @Override public void dispose() { + myProject = null; myNotifications.clear(); myUniqueNotifications.clear(); initializedExternalSystem.clear(); } + + private boolean isDisposedOrNotOpen() { + return myProject == null || myProject.isDisposed() || !myProject.isOpen(); + } } \ No newline at end of file diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/NotificationData.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/NotificationData.java index 6d0786a11be6..4a6ae62e2499 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/NotificationData.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/notification/NotificationData.java @@ -17,6 +17,7 @@ package com.intellij.openapi.externalSystem.service.notification; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; +import com.intellij.openapi.Disposable; import com.intellij.pom.Navigatable; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -30,7 +31,7 @@ import java.util.Map; * @author Vladislav.Soroka * @since 3/28/14 */ -public class NotificationData { +public class NotificationData implements Disposable { @NotNull private String myTitle; @NotNull private String myMessage; @@ -183,4 +184,9 @@ public class NotificationData { public void setBalloonGroup(@Nullable String balloonGroup) { myBalloonGroup = balloonGroup; } + + @Override + public void dispose() { + myListenerMap.clear(); + } }