blob: c66eb5eb1a27060c5d594bda5dc5ae2bc25f5145 [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.
*/
package com.intellij.openapi.project;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.caches.CacheUpdater;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.wm.AppIconScheme;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
import com.intellij.openapi.wm.ex.StatusBarEx;
import com.intellij.ui.AppIcon;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Queue;
import com.intellij.util.io.storage.HeavyProcessLatch;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
public class DumbServiceImpl extends DumbService implements Disposable {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl");
private volatile boolean myDumb = false;
private final DumbModeListener myPublisher;
private final Queue<DumbModeTask> myUpdatesQueue = new Queue<DumbModeTask>(5);
/**
* Per-task progress indicators. Modified from EDT only.
* The task is removed from this map after it's finished or when the project is disposed.
*/
private final Map<DumbModeTask, ProgressIndicatorEx> myProgresses = ContainerUtil.newConcurrentMap();
private final Queue<Runnable> myRunWhenSmartQueue = new Queue<Runnable>(5);
private final Project myProject;
public DumbServiceImpl(Project project) {
myProject = project;
myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE);
}
@SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"})
public static DumbServiceImpl getInstance(@NotNull Project project) {
return (DumbServiceImpl)DumbService.getInstance(project);
}
@Override
public void queueTask(@NotNull final DumbModeTask task) {
scheduleCacheUpdate(task, true);
}
@Override
public void cancelTask(@NotNull DumbModeTask task) {
if (ApplicationManager.getApplication().isInternal()) LOG.info("cancel " + task);
ProgressIndicatorEx indicator = myProgresses.get(task);
if (indicator != null) {
indicator.cancel();
}
}
@Override
public void dispose() {
ApplicationManager.getApplication().assertIsDispatchThread();
myUpdatesQueue.clear();
myRunWhenSmartQueue.clear();
for (DumbModeTask task : new ArrayList<DumbModeTask>(myProgresses.keySet())) {
cancelTask(task);
Disposer.dispose(task);
}
}
@Override
public Project getProject() {
return myProject;
}
@Override
public boolean isDumb() {
return myDumb;
}
@TestOnly
public void setDumb(boolean dumb) {
if (dumb) {
myDumb = true;
myPublisher.enteredDumbMode();
}
else {
updateFinished();
}
}
@Override
public void runWhenSmart(@NotNull Runnable runnable) {
if (!isDumb()) {
runnable.run();
}
else {
synchronized (myRunWhenSmartQueue) {
myRunWhenSmartQueue.addLast(runnable);
}
}
}
@SuppressWarnings("deprecation")
public void queueCacheUpdate(@NotNull Collection<CacheUpdater> updaters) {
scheduleCacheUpdate(new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters)), false);
}
@SuppressWarnings("deprecation")
public void queueCacheUpdateInDumbMode(@NotNull Collection<CacheUpdater> updaters) {
scheduleCacheUpdate(new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters)), true);
}
private void scheduleCacheUpdate(@NotNull final DumbModeTask task, boolean forceDumbMode) {
if (ApplicationManager.getApplication().isInternal()) LOG.info("schedule " + task);
final Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode() ||
application.isHeadlessEnvironment() ||
!forceDumbMode && !myDumb && application.isReadAccessAllowed()) {
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
if (indicator != null) {
indicator.pushState();
}
try {
HeavyProcessLatch.INSTANCE.processStarted();
task.performInDumbMode(indicator != null ? indicator : new EmptyProgressIndicator());
}
finally {
HeavyProcessLatch.INSTANCE.processFinished();
if (indicator != null) {
indicator.popState();
}
Disposer.dispose(task);
}
return;
}
UIUtil.invokeLaterIfNeeded(new DumbAwareRunnable() {
@Override
public void run() {
if (myProject.isDisposed()) {
return;
}
final ProgressIndicatorBase indicator = new ProgressIndicatorBase();
myProgresses.put(task, indicator);
Disposer.register(task, new Disposable() {
@Override
public void dispose() {
application.assertIsDispatchThread();
myProgresses.remove(task);
}
});
// ok to test and set the flag like this, because the change is always done from dispatch thread
if (!myDumb) {
// always change dumb status inside write action.
// This will ensure all active read actions are completed before the app goes dumb
boolean startSuccess =
application.runWriteAction(new Computable<Boolean>() {
@Override
public Boolean compute() {
myDumb = true;
try {
myPublisher.enteredDumbMode();
}
catch (Throwable e) {
LOG.error(e);
}
try {
startBackgroundProcess(task, indicator);
}
catch (Throwable e) {
LOG.error("Failed to start background index update task", e);
return false;
}
return true;
}
});
if (!startSuccess) {
updateFinished();
}
}
else {
myUpdatesQueue.addLast(task);
}
}
});
}
private void updateFinished() {
myDumb = false;
if (myProject.isDisposed()) return;
if (ApplicationManager.getApplication().isInternal()) LOG.info("updateFinished");
try {
myPublisher.exitDumbMode();
FileEditorManagerEx.getInstanceEx(myProject).refreshIcons();
}
finally {
// It may happen that one of the pending runWhenSmart actions triggers new dumb mode;
// in this case we should quit processing pending actions and postpone them until the newly started dumb mode finishes.
while (!myDumb) {
final Runnable runnable;
synchronized (myRunWhenSmartQueue) {
if (myRunWhenSmartQueue.isEmpty()) {
break;
}
runnable = myRunWhenSmartQueue.pullFirst();
}
try {
runnable.run();
}
catch (Throwable e) {
LOG.error(e);
}
}
}
}
@Override
public void showDumbModeNotification(@NotNull final String message) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
final IdeFrame ideFrame = WindowManager.getInstance().getIdeFrame(myProject);
if (ideFrame != null) {
StatusBarEx statusBar = (StatusBarEx)ideFrame.getStatusBar();
statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, null);
}
}
});
}
@Override
public void waitForSmartMode() {
final Application application = ApplicationManager.getApplication();
if (!application.isUnitTestMode()) {
assert !application.isDispatchThread();
assert !application.isReadAccessAllowed();
}
if (!isDumb()) {
return;
}
final Semaphore semaphore = new Semaphore();
semaphore.down();
runWhenSmart(new Runnable() {
@Override
public void run() {
semaphore.up();
}
});
semaphore.waitFor();
}
@Override
public JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) {
final DumbUnawareHider wrapper = new DumbUnawareHider(dumbUnawareContent);
wrapper.setContentVisible(!isDumb());
getProject().getMessageBus().connect(parentDisposable).subscribe(DUMB_MODE, new DumbModeListener() {
@Override
public void enteredDumbMode() {
wrapper.setContentVisible(false);
}
@Override
public void exitDumbMode() {
wrapper.setContentVisible(true);
}
});
return wrapper;
}
public void smartInvokeLater(@NotNull final Runnable runnable) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
runWhenSmart(runnable);
}
}, myProject.getDisposed());
}
public void smartInvokeLater(@NotNull final Runnable runnable, @NotNull ModalityState modalityState) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
runWhenSmart(runnable);
}
}, modalityState, myProject.getDisposed());
}
private void startBackgroundProcess(@NotNull final DumbModeTask task, @NotNull final ProgressIndicatorEx indicator) {
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) {
@Override
public void run(@NotNull final ProgressIndicator visibleIndicator) {
if (ApplicationManager.getApplication().isInternal()) LOG.info("Running dumb mode task: " + task);
final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance();
final Thread self = Thread.currentThread();
try {
HeavyProcessLatch.INSTANCE.processStarted();
shutdownTracker.registerStopperThread(self);
indicator.checkCanceled();
if (visibleIndicator instanceof ProgressIndicatorEx) {
indicator.addStateDelegate((ProgressIndicatorEx)visibleIndicator);
((ProgressIndicatorEx)visibleIndicator).addStateDelegate(new AppIconProgress());
}
indicator.setIndeterminate(true);
indicator.setText(IdeBundle.message("progress.indexing.scanning"));
task.performInDumbMode(indicator);
}
catch (ProcessCanceledException ignored) {
}
catch (Throwable unexpected) {
LOG.error(unexpected);
}
finally {
shutdownTracker.unregisterStopperThread(self);
HeavyProcessLatch.INSTANCE.processFinished();
taskFinished(task);
}
}
});
}
private void taskFinished(@NotNull final DumbModeTask prevTask) {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
if (myProject.isDisposed()) return;
Disposer.dispose(prevTask);
while (true) {
if (myUpdatesQueue.isEmpty()) {
updateFinished();
return;
}
DumbModeTask queuedTask = myUpdatesQueue.pullFirst();
ProgressIndicatorEx indicator = myProgresses.get(queuedTask);
if (indicator.isCanceled()) {
Disposer.dispose(queuedTask);
continue;
}
startBackgroundProcess(queuedTask, indicator);
return;
}
}
});
}
private class AppIconProgress extends ProgressIndicatorBase {
private double lastFraction;
@Override
public void setFraction(final double fraction) {
if (fraction - lastFraction < 0.01d) return;
lastFraction = fraction;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
AppIcon.getInstance().setProgress(myProject, "indexUpdate", AppIconScheme.Progress.INDEXING, fraction, true);
}
});
}
@Override
public void finish(@NotNull TaskInfo task) {
if (lastFraction != 0) { // we should call setProgress at least once before
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
AppIcon appIcon = AppIcon.getInstance();
if (appIcon.hideProgress(myProject, "indexUpdate")) {
appIcon.requestAttention(myProject, false);
appIcon.setOkBadge(myProject, true);
}
}
});
}
}
}
}