do not call updatingDone() if cancelled
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / project / DumbServiceImpl.java
blob545433b3978c91fae5b4e38983e1895086095065
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.project;
18 import com.intellij.ide.IdeBundle;
19 import com.intellij.ide.caches.CacheUpdater;
20 import com.intellij.openapi.application.Application;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.application.ApplicationNamesInfo;
23 import com.intellij.openapi.diagnostic.Logger;
24 import com.intellij.openapi.progress.*;
25 import com.intellij.openapi.ui.MessageType;
26 import com.intellij.openapi.ui.Messages;
27 import com.intellij.openapi.ui.popup.BalloonHandler;
28 import com.intellij.openapi.util.Ref;
29 import com.intellij.openapi.wm.WindowManager;
30 import com.intellij.openapi.wm.ex.StatusBarEx;
31 import com.intellij.util.containers.Queue;
32 import com.intellij.util.messages.MessageBus;
33 import org.jetbrains.annotations.NotNull;
35 import javax.swing.*;
36 import javax.swing.event.HyperlinkEvent;
37 import javax.swing.event.HyperlinkListener;
38 import java.lang.reflect.InvocationHandler;
39 import java.lang.reflect.InvocationTargetException;
40 import java.lang.reflect.Method;
41 import java.lang.reflect.Proxy;
42 import java.util.ArrayList;
43 import java.util.Collection;
44 import java.util.concurrent.ArrayBlockingQueue;
45 import java.util.concurrent.TimeUnit;
47 public class DumbServiceImpl extends DumbService {
48 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl");
49 private volatile boolean myDumb = false;
50 private final DumbModeListener myPublisher;
51 private final Queue<IndexUpdateRunnable> myUpdatesQueue = new Queue<IndexUpdateRunnable>(5);
52 private final Queue<Runnable> myRunWhenSmartQueue = new Queue<Runnable>(5);
53 private final Project myProject;
55 @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"})
56 public static DumbServiceImpl getInstance(@NotNull Project project) {
57 return (DumbServiceImpl)DumbService.getInstance(project);
60 public DumbServiceImpl(Project project, MessageBus bus) {
61 myProject = project;
62 myPublisher = bus.syncPublisher(DUMB_MODE);
65 @Override
66 public Project getProject() {
67 return myProject;
70 public boolean isDumb() {
71 return myDumb;
74 @Override
75 public void runWhenSmart(Runnable runnable) {
76 if (!isDumb()) {
77 runnable.run();
79 else {
80 synchronized (myRunWhenSmartQueue) {
81 myRunWhenSmartQueue.addLast(runnable);
86 public void queueCacheUpdate(Collection<CacheUpdater> updaters) {
87 // prevent concurrent modifications
88 final CacheUpdateRunner runner = new CacheUpdateRunner(myProject, new ArrayList<CacheUpdater>(updaters));
90 final Application application = ApplicationManager.getApplication();
91 if (application.isDispatchThread() && !myDumb && application.isWriteAccessAllowed()) {
92 ProgressIndicator indicator = new EmptyProgressIndicator();
93 final int size = runner.queryNeededFiles(indicator);
94 if (size < 50) {
95 // if not that many files found, process them on the spot, avoiding entering dumb mode
96 if (size > 0) {
97 runner.processFiles(indicator, false);
99 runner.updatingDone();
100 return;
105 final IndexUpdateRunnable updateRunnable = new IndexUpdateRunnable(runner);
107 invokeOnEDT(new DumbAwareRunnable() {
108 public void run() {
109 if (myProject.isDisposed()) {
110 return;
112 // ok to test and set the flag like this, because the change is always done from dispatch thread
113 final boolean wasDumb = myDumb;
114 if (!wasDumb) {
115 // always change dumb status inside write action.
116 // This will ensure all active read actions are completed before the app goes dumb
117 application.runWriteAction(new Runnable() {
118 public void run() {
119 myDumb = true;
120 myPublisher.enteredDumbMode();
122 updateRunnable.run();
126 else {
127 myUpdatesQueue.addLast(updateRunnable);
133 private static void invokeOnEDT(DumbAwareRunnable runnable) {
134 if (ApplicationManager.getApplication().isDispatchThread()) {
135 runnable.run();
137 else {
138 SwingUtilities.invokeLater(runnable);
142 private void updateFinished() {
143 myDumb = false;
144 myPublisher.exitDumbMode();
145 while (true) {
146 final Runnable runnable;
147 synchronized (myRunWhenSmartQueue) {
148 if (myRunWhenSmartQueue.isEmpty()) {
149 break;
151 runnable = myRunWhenSmartQueue.pullFirst();
153 if (myProject.isDisposed()) {
154 return;
156 runnable.run();
160 @Override
161 public BalloonHandler showDumbModeNotification(final String message) {
162 if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
163 return new BalloonHandler() {
164 public void hide() {
169 StatusBarEx statusBar = (StatusBarEx)WindowManager.getInstance().getIdeFrame(myProject).getStatusBar();
170 HyperlinkListener listener = new HyperlinkListener() {
171 public void hyperlinkUpdate(HyperlinkEvent e) {
172 if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return;
174 Messages.showMessageDialog("<html>" +
175 ApplicationNamesInfo.getInstance().getFullProductName() +
176 " is now indexing project sources and libraries to enable advanced features <br>" +
177 "(refactorings, navigation, usage search, code analysis, formatting, etc.)<br>" +
178 "During this process you can use code editor and VCS integrations,<br>" +
179 "and adjust IDE and Run Configurations settings." +
180 "</html>", "Don't panic!", null);
183 return statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, listener);
186 private static final Ref<CacheUpdateRunner> NULL_ACTION = new Ref<CacheUpdateRunner>(null);
188 private class IndexUpdateRunnable implements Runnable {
189 private final CacheUpdateRunner myAction;
190 private double myProcessedItems;
191 private volatile int myTotalItems;
192 private double myCurrentBaseTotal;
194 public IndexUpdateRunnable(CacheUpdateRunner action) {
195 myAction = action;
196 myTotalItems = 0;
197 myCurrentBaseTotal = 0;
200 public void run() {
201 if (myProject.isDisposed()) {
202 return;
205 ProgressManager.getInstance().run(new Task.Backgroundable(myProject, IdeBundle.message("progress.indexing"), false) {
207 private final ArrayBlockingQueue<Ref<CacheUpdateRunner>> myActionQueue = new ArrayBlockingQueue<Ref<CacheUpdateRunner>>(1);
209 @Override
210 public void run(@NotNull final ProgressIndicator indicator) {
211 final ProgressIndicator proxy =
212 (ProgressIndicator)Proxy.newProxyInstance(indicator.getClass().getClassLoader(), new Class[]{ProgressIndicator.class}, new InvocationHandler() {
213 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
214 if ("setFraction".equals(method.getName())) {
215 final double fraction = (Double)args[0];
216 args[0] = new Double((myProcessedItems + fraction * myCurrentBaseTotal) / myTotalItems);
218 try {
219 return method.invoke(indicator, args);
221 catch (InvocationTargetException e) {
222 final Throwable cause = e.getCause();
223 if (cause instanceof ProcessCanceledException) {
224 throw cause;
226 throw e;
230 runAction(proxy, myAction);
233 private void runAction(ProgressIndicator indicator, CacheUpdateRunner updateRunner) {
234 do {
235 int count = 0;
236 try {
237 indicator.setIndeterminate(true);
238 indicator.setText(IdeBundle.message("progress.indexing.scanning"));
239 count = updateRunner.queryNeededFiles(indicator);
241 myCurrentBaseTotal = count;
242 myTotalItems += count;
244 indicator.setIndeterminate(false);
245 indicator.setText(IdeBundle.message("progress.indexing.updaing"));
246 updateRunner.processFiles(indicator, true);
247 updateRunner.updatingDone();
249 finally {
250 myProcessedItems += count;
251 invokeOnEDT(new DumbAwareRunnable() {
252 public void run() {
253 if (myUpdatesQueue.isEmpty()) {
254 // really terminate the tesk
255 myActionQueue.offer(NULL_ACTION);
256 updateFinished();
258 else {
259 //run next dumb action
260 final IndexUpdateRunnable nextUpdateRunnable = myUpdatesQueue.pullFirst();
261 // run next action under already existing progress indicator
262 if (!myActionQueue.offer(new Ref<CacheUpdateRunner>(nextUpdateRunnable.myAction))) {
263 LOG.assertTrue(false, "Action queue rejected next updateRunnable!");
264 nextUpdateRunnable.run();
270 // try to obtain the next action or terminate if no actions left
271 try {
272 Ref<CacheUpdateRunner> ref;
273 do {
274 ref = myActionQueue.poll(500, TimeUnit.MILLISECONDS);
275 updateRunner = ref != null? ref.get() : null;
276 if (myProject.isDisposed()) {
277 // just terminate the progress task
278 break;
281 while (ref == null);
283 catch (InterruptedException ignored) {
284 LOG.info(ignored);
285 break;
289 while (updateRunner != null);
290 // make it impossible to add actions to the queue anymore
291 myActionQueue.offer(NULL_ACTION);