blob: 2f9f023bc52abd850b91a6d81513b5bdb8042b3b [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* @author: Eugene Zhuravlev
* Date: Jan 22, 2003
* Time: 2:25:31 PM
*/
package com.intellij.compiler.progress;
import com.intellij.compiler.CompilerManagerImpl;
import com.intellij.compiler.impl.CompilerErrorTreeView;
import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.project.DumbModeAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerListener;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.AppIconScheme;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
import com.intellij.pom.Navigatable;
import com.intellij.problems.WolfTheProblemSolver;
import com.intellij.ui.AppIcon;
import com.intellij.ui.content.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.MessageCategory;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class CompilerTask extends Task.Backgroundable {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.progress.CompilerProgressIndicator");
private static final Key<Object> CONTENT_ID_KEY = Key.create("CONTENT_ID");
private static final Key<Object> SESSION_ID_KEY = Key.create("SESSION_ID");
private static final String APP_ICON_ID = "compiler";
@NotNull
private final Object myContentId = new IDObject("content_id");
@NotNull
private Object mySessionId = myContentId; // by default sessionID should be unique, just as content ID
private NewErrorTreeViewPanel myErrorTreeView;
private final Object myMessageViewLock = new Object();
private final String myContentName;
private final boolean myHeadlessMode;
private final boolean myForceAsyncExecution;
private final boolean myWaitForPreviousSession;
private int myErrorCount = 0;
private int myWarningCount = 0;
private boolean myMessagesAutoActivated = false;
private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator();
private Runnable myCompileWork;
private final AtomicBoolean myMessageViewWasPrepared = new AtomicBoolean(false);
private Runnable myRestartWork;
private final boolean myCompilationStartedAutomatically;
@Deprecated
public CompilerTask(@NotNull Project project, String contentName, final boolean headlessMode, boolean forceAsync,
boolean waitForPreviousSession) {
this(project, contentName, headlessMode, forceAsync, waitForPreviousSession, false);
}
public CompilerTask(@NotNull Project project, String contentName, final boolean headlessMode, boolean forceAsync,
boolean waitForPreviousSession, boolean compilationStartedAutomatically) {
super(project, contentName);
myContentName = contentName;
myHeadlessMode = headlessMode;
myForceAsyncExecution = forceAsync;
myWaitForPreviousSession = waitForPreviousSession;
myCompilationStartedAutomatically = compilationStartedAutomatically;
}
@NotNull
public Object getSessionId() {
return mySessionId;
}
public void setSessionId(@NotNull Object sessionId) {
mySessionId = sessionId;
}
@NotNull
public Object getContentId() {
return myContentId;
}
public void registerCloseAction(final Runnable onClose) {
synchronized (myMessageViewLock) {
if (myErrorTreeView != null) {
Disposer.register(myErrorTreeView, new Disposable() {
@Override
public void dispose() {
onClose.run();
}
});
return;
}
}
onClose.run();
}
@Override
public String getProcessId() {
return "compilation";
}
@NotNull
@Override
public DumbModeAction getDumbModeAction() {
return DumbModeAction.WAIT;
}
@Override
public boolean shouldStartInBackground() {
return true;
}
public ProgressIndicator getIndicator() {
return myIndicator;
}
@Override
@Nullable
public NotificationInfo getNotificationInfo() {
return new NotificationInfo(myErrorCount > 0? "Compiler (errors)" : "Compiler (success)", "Compilation Finished", myErrorCount + " Errors, " + myWarningCount + " Warnings", true);
}
private CloseListener myCloseListener;
@Override
public void run(@NotNull final ProgressIndicator indicator) {
myIndicator = indicator;
final ProjectManager projectManager = ProjectManager.getInstance();
projectManager.addProjectManagerListener(myProject, myCloseListener = new CloseListener());
final Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(myProject)).getCompilationSemaphore();
boolean acquired = false;
try {
try {
while (!acquired) {
acquired = semaphore.tryAcquire(300, TimeUnit.MILLISECONDS);
if (!acquired && !myWaitForPreviousSession) {
return;
}
if (indicator.isCanceled()) {
// give up obtaining the semaphore,
// let compile work begin in order to stop gracefuly on cancel event
break;
}
}
}
catch (InterruptedException ignored) {
}
if (!isHeadless()) {
addIndicatorDelegate();
}
myCompileWork.run();
}
finally {
try {
indicator.stop();
projectManager.removeProjectManagerListener(myProject, myCloseListener);
}
finally {
if (acquired) {
semaphore.release();
}
}
}
}
private void prepareMessageView() {
if (!myIndicator.isRunning()) {
return;
}
if (myMessageViewWasPrepared.getAndSet(true)) {
return;
}
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
final Project project = myProject;
if (project == null || project.isDisposed()) {
return;
}
synchronized (myMessageViewLock) {
// clear messages from the previous compilation
if (myErrorTreeView == null) {
// if message view != null, the contents has already been cleared
removeAllContents(project, null);
}
}
}
});
}
private void addIndicatorDelegate() {
ProgressIndicator indicator = myIndicator;
if (!(indicator instanceof ProgressIndicatorEx)) {
return;
}
((ProgressIndicatorEx)indicator).addStateDelegate(new ProgressIndicatorBase() {
@Override
public void cancel() {
super.cancel();
selectFirstMessage();
stopAppIconProgress();
}
@Override
public void stop() {
super.stop();
if (!isCanceled()) {
selectFirstMessage();
}
stopAppIconProgress();
}
private void selectFirstMessage() {
if (!isHeadlessMode()) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
synchronized (myMessageViewLock) {
if (myErrorTreeView != null) {
myErrorTreeView.selectFirstMessage();
}
}
}
});
}
}
private void stopAppIconProgress() {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
AppIcon appIcon = AppIcon.getInstance();
if (appIcon.hideProgress(myProject, APP_ICON_ID)) {
if (myErrorCount > 0) {
appIcon.setErrorBadge(myProject, String.valueOf(myErrorCount));
appIcon.requestAttention(myProject, true);
}
else if (!myCompilationStartedAutomatically) {
appIcon.setOkBadge(myProject, true);
appIcon.requestAttention(myProject, false);
}
}
}
});
}
@Override
public void setText(final String text) {
super.setText(text);
updateProgressText();
}
@Override
public void setText2(final String text) {
super.setText2(text);
updateProgressText();
}
@Override
public void setFraction(final double fraction) {
super.setFraction(fraction);
updateProgressText();
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
AppIcon.getInstance().setProgress(myProject, APP_ICON_ID, AppIconScheme.Progress.BUILD, fraction, true);
}
});
}
@Override
protected void onProgressChange() {
prepareMessageView();
}
});
}
public void cancel() {
if (!myIndicator.isCanceled()) {
myIndicator.cancel();
}
}
public void addMessage(final CompilerMessage message) {
prepareMessageView();
final CompilerMessageCategory messageCategory = message.getCategory();
if (CompilerMessageCategory.WARNING.equals(messageCategory)) {
myWarningCount += 1;
}
else if (CompilerMessageCategory.ERROR.equals(messageCategory)) {
myErrorCount += 1;
informWolf(message);
}
if (ApplicationManager.getApplication().isDispatchThread()) {
openMessageView();
doAddMessage(message);
}
else {
final Window window = getWindow();
final ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (!myProject.isDisposed()) {
openMessageView();
doAddMessage(message);
}
}
}, modalityState);
}
}
private void informWolf(final CompilerMessage message) {
WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(myProject);
VirtualFile file = getVirtualFile(message);
wolf.queue(file);
}
private void doAddMessage(final CompilerMessage message) {
synchronized (myMessageViewLock) {
if (myErrorTreeView != null) {
final Navigatable navigatable = message.getNavigatable();
final VirtualFile file = message.getVirtualFile();
final CompilerMessageCategory category = message.getCategory();
final int type = translateCategory(category);
final String[] text = convertMessage(message);
if (navigatable != null) {
final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText();
myErrorTreeView.addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile());
}
else {
myErrorTreeView.addMessage(type, text, file, -1, -1, message.getVirtualFile());
}
final boolean shouldAutoActivate =
!myMessagesAutoActivated &&
(
CompilerMessageCategory.ERROR.equals(category) ||
(CompilerMessageCategory.WARNING.equals(category) && !ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings())
);
if (shouldAutoActivate) {
myMessagesAutoActivated = true;
activateMessageView();
}
}
}
}
private static String[] convertMessage(final CompilerMessage message) {
String text = message.getMessage();
if (!text.contains("\n")) {
return new String[]{text};
}
ArrayList<String> lines = new ArrayList<String>();
StringTokenizer tokenizer = new StringTokenizer(text, "\n", false);
while (tokenizer.hasMoreTokens()) {
lines.add(tokenizer.nextToken());
}
return ArrayUtil.toStringArray(lines);
}
public static int translateCategory(CompilerMessageCategory category) {
if (CompilerMessageCategory.ERROR.equals(category)) {
return MessageCategory.ERROR;
}
if (CompilerMessageCategory.WARNING.equals(category)) {
return MessageCategory.WARNING;
}
if (CompilerMessageCategory.STATISTICS.equals(category)) {
return MessageCategory.STATISTICS;
}
if (CompilerMessageCategory.INFORMATION.equals(category)) {
return MessageCategory.INFORMATION;
}
LOG.error("Unknown message category: " + category);
return 0;
}
public void start(Runnable compileWork, Runnable restartWork) {
myCompileWork = compileWork;
myRestartWork = restartWork;
queue();
}
private void updateProgressText() {
if (isHeadlessMode()) {
return;
}
}
// error tree view initialization must be invoked from event dispatch thread
private void openMessageView() {
if (isHeadlessMode()) {
return;
}
if (myIndicator.isCanceled()) {
return;
}
final JComponent component;
synchronized (myMessageViewLock) {
if (myErrorTreeView != null) {
return;
}
myErrorTreeView = new CompilerErrorTreeView(
myProject,
myRestartWork
);
myErrorTreeView.setProcessController(new NewErrorTreeViewPanel.ProcessController() {
@Override
public void stopProcess() {
cancel();
}
@Override
public boolean isProcessStopped() {
return !myIndicator.isRunning();
}
});
component = myErrorTreeView.getComponent();
}
final MessageView messageView = MessageView.SERVICE.getInstance(myProject);
final Content content = ContentFactory.SERVICE.getInstance().createContent(component, myContentName, true);
CONTENT_ID_KEY.set(content, myContentId);
SESSION_ID_KEY.set(content, mySessionId);
messageView.getContentManager().addContent(content);
myCloseListener.setContent(content, messageView.getContentManager());
removeAllContents(myProject, content);
messageView.getContentManager().setSelectedContent(content);
}
public void showCompilerContent() {
synchronized (myMessageViewLock) {
if (myErrorTreeView != null) {
showCompilerContent(myProject, myContentId);
}
}
}
public static boolean showCompilerContent(final Project project, final Object contentId) {
final MessageView messageView = MessageView.SERVICE.getInstance(project);
for (Content content : messageView.getContentManager().getContents()) {
if (CONTENT_ID_KEY.get(content) == contentId) {
messageView.getContentManager().setSelectedContent(content);
return true;
}
}
return false;
}
private void removeAllContents(Project project, Content notRemove) {
if (project.isDisposed()) {
return;
}
final MessageView messageView = MessageView.SERVICE.getInstance(project);
Content[] contents = messageView.getContentManager().getContents();
for (Content content : contents) {
if (content.isPinned()) {
continue;
}
if (content == notRemove) {
continue;
}
boolean toRemove = CONTENT_ID_KEY.get(content) == myContentId;
if (!toRemove) {
final Object contentSessionId = SESSION_ID_KEY.get(content);
toRemove = contentSessionId != null && contentSessionId != mySessionId; // the content was added by previous compilation
}
if (toRemove) {
messageView.getContentManager().removeContent(content, true);
}
}
}
private void activateMessageView() {
synchronized (myMessageViewLock) {
if (myErrorTreeView != null && myProject != null) {
final ToolWindow tw = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
if (tw != null) {
tw.activate(null, false);
}
}
}
}
public Window getWindow(){
return null;
}
@Override
public boolean isHeadless() {
return myHeadlessMode && !myForceAsyncExecution;
}
private boolean isHeadlessMode() {
return myHeadlessMode;
}
private static VirtualFile getVirtualFile(final CompilerMessage message) {
VirtualFile virtualFile = message.getVirtualFile();
if (virtualFile == null) {
Navigatable navigatable = message.getNavigatable();
if (navigatable instanceof OpenFileDescriptor) {
virtualFile = ((OpenFileDescriptor)navigatable).getFile();
}
}
return virtualFile;
}
public static TextRange getTextRange(final CompilerMessage message) {
Navigatable navigatable = message.getNavigatable();
if (navigatable instanceof OpenFileDescriptor) {
int offset = ((OpenFileDescriptor)navigatable).getOffset();
return new TextRange(offset, offset);
}
return TextRange.EMPTY_RANGE;
}
private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener {
private Content myContent;
private ContentManager myContentManager;
private boolean myIsApplicationExitingOrProjectClosing = false;
private boolean myUserAcceptedCancel = false;
@Override
public boolean canCloseProject(final Project project) {
assert project != null;
if (!project.equals(myProject)) {
return true;
}
if (shouldAskUser()) {
int result = Messages.showOkCancelDialog(
myProject,
CompilerBundle.message("warning.compiler.running.on.project.close"),
CompilerBundle.message("compiler.running.dialog.title"),
Messages.getQuestionIcon()
);
if (result != Messages.OK) {
return false; // veto closing
}
myUserAcceptedCancel = true;
final MessageBusConnection connection = project.getMessageBus().connect();
connection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusAdapter() {
@Override
public void compilationFinished(boolean aborted, int errors, int warnings, final CompileContext compileContext) {
connection.disconnect();
ProjectUtil.closeAndDispose(project);
}
});
cancel();
return false; // cancel compiler and let it finish, after compilation close the project, but currently - veto closing
}
return !myIndicator.isRunning();
}
public void setContent(Content content, ContentManager contentManager) {
myContent = content;
myContentManager = contentManager;
contentManager.addContentManagerListener(this);
}
@Override
public void contentRemoved(ContentManagerEvent event) {
if (event.getContent() == myContent) {
synchronized (myMessageViewLock) {
if (myErrorTreeView != null) {
Disposer.dispose(myErrorTreeView);
myErrorTreeView = null;
if (myIndicator.isRunning()) {
cancel();
}
if (AppIcon.getInstance().hideProgress(myProject, "compiler")) {
AppIcon.getInstance().setErrorBadge(myProject, null);
}
}
}
myContentManager.removeContentManagerListener(this);
myContent.release();
myContent = null;
}
}
@Override
public void contentRemoveQuery(ContentManagerEvent event) {
if (event.getContent() == myContent) {
if (!myIndicator.isCanceled() && shouldAskUser()) {
int result = Messages.showOkCancelDialog(
myProject,
CompilerBundle.message("warning.compiler.running.on.toolwindow.close"),
CompilerBundle.message("compiler.running.dialog.title"),
Messages.getQuestionIcon()
);
if (result != Messages.OK) {
event.consume(); // veto closing
}
myUserAcceptedCancel = true;
}
}
}
private boolean shouldAskUser() {
// do not ask second time if user already accepted closing
return !myUserAcceptedCancel && !myIsApplicationExitingOrProjectClosing && myIndicator.isRunning();
}
@Override
public void projectOpened(Project project) {
}
@Override
public void projectClosed(Project project) {
if (project.equals(myProject) && myContent != null) {
myContentManager.removeContent(myContent, true);
}
}
@Override
public void projectClosing(Project project) {
if (project.equals(myProject)) {
myIsApplicationExitingOrProjectClosing = true;
}
}
}
public static final class IDObject {
private final String myDisplayName;
public IDObject(@NotNull String displayName) {
myDisplayName = displayName;
}
@Override
public String toString() {
return myDisplayName;
}
}
}