/*
* Copyright 2000-2009 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.ProblemsView;
import com.intellij.ide.impl.ProjectUtil;
import com.intellij.openapi.application.Application;
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.AbstractProgressIndicatorExBase;
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.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.AppIconScheme;
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.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentManagerAdapter;
import com.intellij.ui.content.ContentManagerEvent;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.ui.MessageCategory;
import com.intellij.util.ui.UIUtil;
import consulo.compiler.impl.CompilerManagerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class CompilerTask extends Task.Backgroundable {
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.progress.CompilerProgressIndicator");
private static final String APP_ICON_ID = "compiler";
private final Object myMessageViewLock = new Object();
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 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);
myHeadlessMode = headlessMode;
myForceAsyncExecution = forceAsync;
myWaitForPreviousSession = waitForPreviousSession;
myCompilationStartedAutomatically = compilationStartedAutomatically;
}
@Override
public String getProcessId() {
return "compilation";
}
@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);
}
@Override
public void run(@NotNull final ProgressIndicator indicator) {
myIndicator = indicator;
final ProjectManager projectManager = ProjectManager.getInstance();
CloseListener closeListener;
projectManager.addProjectManagerListener(myProject, closeListener = 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, closeListener);
}
finally {
if (acquired) {
semaphore.release();
}
}
}
}
private void addIndicatorDelegate() {
ProgressIndicator indicator = myIndicator;
if (!(indicator instanceof ProgressIndicatorEx)) return;
((ProgressIndicatorEx)indicator).addStateDelegate(new AbstractProgressIndicatorExBase() {
@Override
public void cancel() {
super.cancel();
closeUI();
stopAppIconProgress();
}
@Override
public void stop() {
super.stop();
if (!isCanceled()) {
closeUI();
}
stopAppIconProgress();
}
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 setFraction(final double fraction) {
super.setFraction(fraction);
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
AppIcon.getInstance().setProgress(myProject, APP_ICON_ID, AppIconScheme.Progress.BUILD, fraction, true);
}
});
}
});
}
public void cancel() {
if (!myIndicator.isCanceled()) {
myIndicator.cancel();
}
}
public void addMessage(final CompilerMessage message) {
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()) {
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()) {
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) {
final CompilerMessageCategory category = message.getCategory();
final boolean shouldAutoActivate = !myMessagesAutoActivated &&
(CompilerMessageCategory.ERROR.equals(category) ||
(CompilerMessageCategory.WARNING.equals(category) &&
!ProblemsView.getInstance(myProject).isHideWarnings()));
if (shouldAutoActivate) {
myMessagesAutoActivated = true;
activateMessageView();
}
}
}
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 activateMessageView() {
synchronized (myMessageViewLock) {
ProblemsView.getInstance(myProject).showOrHide(false);
}
}
private void closeUI() {
if (isHeadlessMode()) {
return;
}
Window window = getWindow();
ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
final Application application = ApplicationManager.getApplication();
application.invokeLater(new Runnable() {
@Override
public void run() {
synchronized (myMessageViewLock) {
final boolean shouldRetainView = myErrorCount > 0 || myWarningCount > 0 && !ProblemsView.getInstance(myProject).isHideWarnings();
if (shouldRetainView) {
ProblemsView.getInstance(myProject).selectFirstMessage();
}
else {
ProblemsView.getInstance(myProject).showOrHide(true);
}
}
}
}, modalityState);
}
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 != 0) {
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) {
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 != 0) {
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;
}
}
}
}