External system notification manager Project leak fix

This commit is contained in:
Vladislav.Soroka
2017-06-19 15:41:48 +03:00
parent 66441bf6d5
commit 51ea414d7e
2 changed files with 127 additions and 93 deletions
@@ -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<Pair<NotificationSource, ProjectSystemId>> 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<Notification> myNotifications;
@NotNull private final Map<Key, Notification> myUniqueNotifications;
@NotNull private final Set<ProjectSystemId> 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<String> 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<Notification> iterator = myNotifications.iterator(); iterator.hasNext(); ) {
Notification notification = iterator.next();
if (groupName == null || groupName.equals(notification.getGroupId())) {
notification.expire();
iterator.remove();
}
}
final Pair<NotificationSource, ProjectSystemId> 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<Key> 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<NotificationSource, ProjectSystemId> 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<Notification> iterator = myNotifications.iterator(); iterator.hasNext(); ) {
Notification notification = iterator.next();
if (groupName == null || groupName.equals(notification.getGroupId())) {
notification.expire();
iterator.remove();
}
}
});
List<Key> 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<NotificationSource, ProjectSystemId> 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<NotificationSource, ProjectSystemId> 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();
}
}
@@ -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();
}
}