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
;
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
) {
62 myPublisher
= bus
.syncPublisher(DUMB_MODE
);
66 public Project
getProject() {
70 public boolean isDumb() {
75 public void runWhenSmart(Runnable runnable
) {
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
);
95 // if not that many files found, process them on the spot, avoiding entering dumb mode
97 runner
.processFiles(indicator
, false);
99 runner
.updatingDone();
105 final IndexUpdateRunnable updateRunnable
= new IndexUpdateRunnable(runner
);
107 invokeOnEDT(new DumbAwareRunnable() {
109 if (myProject
.isDisposed()) {
112 // ok to test and set the flag like this, because the change is always done from dispatch thread
113 final boolean wasDumb
= myDumb
;
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() {
120 myPublisher
.enteredDumbMode();
122 updateRunnable
.run();
127 myUpdatesQueue
.addLast(updateRunnable
);
133 private static void invokeOnEDT(DumbAwareRunnable runnable
) {
134 if (ApplicationManager
.getApplication().isDispatchThread()) {
138 SwingUtilities
.invokeLater(runnable
);
142 private void updateFinished() {
144 myPublisher
.exitDumbMode();
146 final Runnable runnable
;
147 synchronized (myRunWhenSmartQueue
) {
148 if (myRunWhenSmartQueue
.isEmpty()) {
151 runnable
= myRunWhenSmartQueue
.pullFirst();
153 if (myProject
.isDisposed()) {
161 public BalloonHandler
showDumbModeNotification(final String message
) {
162 if (ApplicationManager
.getApplication().isUnitTestMode() || ApplicationManager
.getApplication().isHeadlessEnvironment()) {
163 return new BalloonHandler() {
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
) {
197 myCurrentBaseTotal
= 0;
201 if (myProject
.isDisposed()) {
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);
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
);
219 return method
.invoke(indicator
, args
);
221 catch (InvocationTargetException e
) {
222 final Throwable cause
= e
.getCause();
223 if (cause
instanceof ProcessCanceledException
) {
230 runAction(proxy
, myAction
);
233 private void runAction(ProgressIndicator indicator
, CacheUpdateRunner updateRunner
) {
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();
250 myProcessedItems
+= count
;
251 invokeOnEDT(new DumbAwareRunnable() {
253 if (myUpdatesQueue
.isEmpty()) {
254 // really terminate the tesk
255 myActionQueue
.offer(NULL_ACTION
);
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
272 Ref
<CacheUpdateRunner
> ref
;
274 ref
= myActionQueue
.poll(500, TimeUnit
.MILLISECONDS
);
275 updateRunner
= ref
!= null? ref
.get() : null;
276 if (myProject
.isDisposed()) {
277 // just terminate the progress task
283 catch (InterruptedException ignored
) {
289 while (updateRunner
!= null);
290 // make it impossible to add actions to the queue anymore
291 myActionQueue
.offer(NULL_ACTION
);