blob: 75a227518b4690cd4abd836dfe3b7959e025ed37 [file] [log] [blame]
package com.intellij.openapi.externalSystem.service.notification;
import com.intellij.execution.rmi.RemoteUtil;
import com.intellij.ide.errorTreeView.*;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationGroup;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.externalSystem.ExternalSystemConfigurableAware;
import com.intellij.openapi.externalSystem.ExternalSystemManager;
import com.intellij.openapi.externalSystem.model.ExternalSystemDataKeys;
import com.intellij.openapi.externalSystem.model.LocationAwareExternalSystemException;
import com.intellij.openapi.externalSystem.model.ProjectSystemId;
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil;
import com.intellij.openapi.externalSystem.util.ExternalSystemBundle;
import com.intellij.openapi.externalSystem.util.ExternalSystemUtil;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.pom.Navigatable;
import com.intellij.ui.EditorNotifications;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.content.MessageView;
import com.intellij.util.ObjectUtils;
import com.intellij.util.concurrency.SequentialTaskExecutor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.ide.PooledThreadExecutor;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* This class is responsible for ide user by external system integration-specific events.
* <p/>
* One example use-case is a situation when an error occurs during external project refresh. We need to
* show corresponding message to the end-user.
* <p/>
* Thread-safe.
*
* @author Denis Zhdanov, Vladislav Soroka
* @since 3/21/12 4:04 PM
*/
public class ExternalSystemNotificationManager {
@NotNull private static final Key<Pair<NotificationSource, ProjectSystemId>> CONTENT_ID_KEY = Key.create("CONTENT_ID");
@NotNull private final SequentialTaskExecutor myUpdater = new SequentialTaskExecutor(PooledThreadExecutor.INSTANCE);
@NotNull private final Project myProject;
@NotNull private final List<Notification> myNotifications;
@NotNull private final Set<ProjectSystemId> initializedExternalSystem;
@NotNull private final MessageCounter myMessageCounter;
public ExternalSystemNotificationManager(@NotNull final Project project) {
myProject = project;
myNotifications = ContainerUtil.newArrayList();
initializedExternalSystem = ContainerUtil.newHashSet();
myMessageCounter = new MessageCounter();
}
@NotNull
public static ExternalSystemNotificationManager getInstance(@NotNull Project project) {
return ServiceManager.getService(project, ExternalSystemNotificationManager.class);
}
public void processExternalProjectRefreshError(@NotNull Throwable error,
@NotNull String externalProjectName,
@NotNull ProjectSystemId externalSystemId) {
if (myProject.isDisposed() || !myProject.isOpen()) {
return;
}
ExternalSystemManager<?, ?, ?, ?, ?> manager = ExternalSystemApiUtil.getManager(externalSystemId);
if (!(manager instanceof ExternalSystemConfigurableAware)) {
return;
}
String title =
ExternalSystemBundle.message("notification.project.refresh.fail.title", externalSystemId.getReadableName(), externalProjectName);
String message = ExternalSystemApiUtil.buildErrorMessage(error);
NotificationCategory notificationCategory = NotificationCategory.ERROR;
String filePath = null;
Integer line = null;
Integer column = null;
//noinspection ThrowableResultOfMethodCallIgnored
Throwable unwrapped = RemoteUtil.unwrap(error);
if (unwrapped instanceof LocationAwareExternalSystemException) {
LocationAwareExternalSystemException locationAwareExternalSystemException = (LocationAwareExternalSystemException)unwrapped;
filePath = locationAwareExternalSystemException.getFilePath();
line = locationAwareExternalSystemException.getLine();
column = locationAwareExternalSystemException.getColumn();
}
NotificationData notificationData =
new NotificationData(
title, message, notificationCategory, NotificationSource.PROJECT_SYNC,
filePath, ObjectUtils.notNull(line, -1), ObjectUtils.notNull(column, -1), false);
for (ExternalSystemNotificationExtension extension : ExternalSystemNotificationExtension.EP_NAME.getExtensions()) {
if (!externalSystemId.equals(extension.getTargetExternalSystemId())) {
continue;
}
extension.customize(notificationData, myProject, error);
}
EditorNotifications.getInstance(myProject).updateAllNotifications();
showNotification(externalSystemId, notificationData);
}
public void showNotification(@NotNull final ProjectSystemId externalSystemId, @NotNull final NotificationData notificationData) {
myUpdater.execute(new Runnable() {
@Override
public void run() {
if (myProject.isDisposed()) return;
if (!initializedExternalSystem.contains(externalSystemId)) {
final Application app = ApplicationManager.getApplication();
Runnable action = new Runnable() {
public void run() {
app.runWriteAction(new Runnable() {
public void run() {
if (myProject.isDisposed()) return;
ExternalSystemUtil.ensureToolWindowContentInitialized(myProject, externalSystemId);
initializedExternalSystem.add(externalSystemId);
}
});
}
};
if (app.isDispatchThread()) {
action.run();
}
else {
app.invokeAndWait(action, ModalityState.defaultModalityState());
}
}
final NotificationGroup group = ExternalSystemUtil.getToolWindowElement(
NotificationGroup.class, myProject, ExternalSystemDataKeys.NOTIFICATION_GROUP, externalSystemId);
if (group == null) return;
final Notification notification = group.createNotification(
notificationData.getTitle(), notificationData.getMessage(),
notificationData.getNotificationCategory().getNotificationType(), notificationData.getListener());
myNotifications.add(notification);
if (notificationData.isBalloonNotification()) {
applyNotification(notification);
}
else {
addMessage(notification, externalSystemId, notificationData);
}
}
});
}
public void openMessageView(@NotNull final ProjectSystemId externalSystemId, @NotNull final NotificationSource notificationSource) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
prepareMessagesView(externalSystemId, notificationSource, true);
}
});
}
public void clearNotifications(@NotNull final NotificationSource notificationSource,
@NotNull final ProjectSystemId externalSystemId) {
clearNotifications(null, notificationSource, externalSystemId);
}
public void clearNotifications(@Nullable final String groupName,
@NotNull final NotificationSource notificationSource,
@NotNull final ProjectSystemId externalSystemId) {
myMessageCounter.remove(groupName, notificationSource, externalSystemId);
myUpdater.execute(new Runnable() {
@Override
public void run() {
for (Iterator<Notification> iterator = myNotifications.iterator(); iterator.hasNext(); ) {
Notification notification = iterator.next();
if (groupName == null || groupName.equals(notification.getGroupId())) {
notification.expire();
iterator.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(new Runnable() {
@Override
public void run() {
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);
}
}
}
}
});
}
});
}
public int getMessageCount(@NotNull final NotificationSource notificationSource,
@Nullable final NotificationCategory notificationCategory,
@NotNull final ProjectSystemId externalSystemId) {
return getMessageCount(null, notificationSource, notificationCategory, externalSystemId);
}
public int getMessageCount(@Nullable final String groupName,
@NotNull final NotificationSource notificationSource,
@Nullable final NotificationCategory notificationCategory,
@NotNull final ProjectSystemId externalSystemId) {
return myMessageCounter.getCount(groupName, notificationSource, notificationCategory, externalSystemId);
}
private void addMessage(@NotNull final Notification notification,
@NotNull final ProjectSystemId externalSystemId,
@NotNull final NotificationData notificationData) {
final VirtualFile virtualFile =
notificationData.getFilePath() != null ? ExternalSystemUtil.waitForTheFile(notificationData.getFilePath()) : null;
final String groupName = virtualFile != null ? virtualFile.getPresentableUrl() : notificationData.getTitle();
myMessageCounter
.increment(groupName, notificationData.getNotificationSource(), notificationData.getNotificationCategory(), externalSystemId);
int line = notificationData.getLine() - 1;
int column = notificationData.getColumn() - 1;
if (virtualFile == null) line = column = -1;
final int guiLine = line < 0 ? -1 : line + 1;
final int guiColumn = column < 0 ? 0 : column + 1;
final Navigatable navigatable = notificationData.getNavigatable() != null
? notificationData.getNavigatable()
: virtualFile != null ? new OpenFileDescriptor(myProject, virtualFile, line, column) : null;
final ErrorTreeElementKind kind =
ErrorTreeElementKind.convertMessageFromCompilerErrorType(notificationData.getNotificationCategory().getMessageCategory());
final String[] message = notificationData.getMessage().split("\n");
final String exportPrefix = NewErrorTreeViewPanel.createExportPrefix(guiLine);
final String rendererPrefix = NewErrorTreeViewPanel.createRendererPrefix(guiLine, guiColumn);
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
boolean activate =
notificationData.getNotificationCategory() == NotificationCategory.ERROR ||
notificationData.getNotificationCategory() == NotificationCategory.WARNING;
final NewErrorTreeViewPanel errorTreeView =
prepareMessagesView(externalSystemId, notificationData.getNotificationSource(), activate);
final GroupingElement groupingElement = errorTreeView.getErrorViewStructure().getGroupingElement(groupName, null, virtualFile);
final NavigatableMessageElement navigatableMessageElement;
if (notificationData.hasLinks()) {
navigatableMessageElement = new EditableNotificationMessageElement(
notification,
kind,
groupingElement,
message,
navigatable,
exportPrefix,
rendererPrefix);
}
else {
navigatableMessageElement = new NotificationMessageElement(
kind,
groupingElement,
message,
navigatable,
exportPrefix,
rendererPrefix);
}
errorTreeView.getErrorViewStructure().addNavigatableMessage(groupName, navigatableMessageElement);
errorTreeView.updateTree();
}
});
}
private void applyNotification(@NotNull final Notification notification) {
if (!myProject.isDisposed() && myProject.isOpen()) {
notification.notify(myProject);
}
}
@NotNull
public NewErrorTreeViewPanel prepareMessagesView(@NotNull final ProjectSystemId externalSystemId,
@NotNull final NotificationSource notificationSource,
boolean activateView) {
ApplicationManager.getApplication().assertIsDispatchThread();
final NewErrorTreeViewPanel errorTreeView;
final String contentDisplayName = getContentDisplayName(notificationSource, externalSystemId);
final Pair<NotificationSource, ProjectSystemId> contentIdPair = Pair.create(notificationSource, externalSystemId);
Content targetContent = findContent(contentIdPair, contentDisplayName);
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);
targetContent = ContentFactory.SERVICE.getInstance().createContent(errorTreeView, contentDisplayName, true);
targetContent.putUserData(CONTENT_ID_KEY, contentIdPair);
messageView.getContentManager().addContent(targetContent);
Disposer.register(targetContent, errorTreeView);
}
else {
assert targetContent.getComponent() instanceof NewEditableErrorTreeViewPanel;
errorTreeView = (NewEditableErrorTreeViewPanel)targetContent.getComponent();
}
messageView.getContentManager().setSelectedContent(targetContent);
final ToolWindow tw = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
if (activateView && tw != null && !tw.isActive()) {
tw.activate(null, false);
}
return errorTreeView;
}
@Nullable
private Content findContent(@NotNull Pair<NotificationSource, ProjectSystemId> contentIdPair, @NotNull String contentDisplayName) {
Content targetContent = null;
final MessageView messageView = ServiceManager.getService(myProject, MessageView.class);
for (Content content : messageView.getContentManager().getContents()) {
if (contentIdPair.equals(content.getUserData(CONTENT_ID_KEY))
&& StringUtil.equals(content.getDisplayName(), contentDisplayName) && !content.isPinned()) {
targetContent = content;
}
}
return targetContent;
}
@NotNull
public static String getContentDisplayName(@NotNull final NotificationSource notificationSource,
@NotNull final ProjectSystemId externalSystemId) {
final String contentDisplayName;
switch (notificationSource) {
case PROJECT_SYNC:
contentDisplayName =
ExternalSystemBundle.message("notification.messages.project.sync.tab.name", externalSystemId.getReadableName());
break;
default:
throw new AssertionError("unsupported notification source found: " + notificationSource);
}
return contentDisplayName;
}
}