3b35ae4dd74bd0ea7b3abe337663068a045127d0
[idea/community.git] / platform / platform-api / src / com / intellij / ide / util / treeView / AbstractTreeUpdater.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.ide.util.treeView;
18
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.application.Application;
21 import com.intellij.openapi.application.ApplicationManager;
22 import com.intellij.openapi.diagnostic.Logger;
23 import com.intellij.openapi.progress.ProcessCanceledException;
24 import com.intellij.openapi.util.ActionCallback;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.ui.treeStructure.treetable.TreeTableTree;
27 import com.intellij.util.Alarm;
28 import com.intellij.util.ui.UIUtil;
29 import com.intellij.util.ui.update.Activatable;
30 import com.intellij.util.ui.update.MergingUpdateQueue;
31 import com.intellij.util.ui.update.UiNotifyConnector;
32 import com.intellij.util.ui.update.Update;
33 import org.jetbrains.annotations.NotNull;
34
35 import javax.swing.*;
36 import javax.swing.tree.DefaultMutableTreeNode;
37 import java.util.*;
38
39 public class AbstractTreeUpdater implements Disposable, Activatable {
40   private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.treeView.AbstractTreeUpdater");
41
42   private final LinkedList<TreeUpdatePass> myNodeQueue = new LinkedList<TreeUpdatePass>();
43   private final AbstractTreeBuilder myTreeBuilder;
44   private final List<Runnable> myRunAfterUpdate = new ArrayList<Runnable>();
45   private Runnable myRunBeforeUpdate;
46   private final MergingUpdateQueue myUpdateQueue;
47
48   private long myUpdateCount;
49
50   public AbstractTreeUpdater(AbstractTreeBuilder treeBuilder) {
51     myTreeBuilder = treeBuilder;
52     final JTree tree = myTreeBuilder.getTree();
53     final JComponent component = tree instanceof TreeTableTree ? ((TreeTableTree)tree).getTreeTable() : tree;
54     myUpdateQueue = new MergingUpdateQueue("UpdateQueue", 300, component.isShowing(), component) {
55       @Override
56       protected Alarm createAlarm(Alarm.ThreadToUse thread, Disposable parent) {
57         return new Alarm(thread, parent) {
58           @Override
59           protected boolean isEdt() {
60             return AbstractTreeUpdater.this.isEdt();
61           }
62         };
63       }
64     };
65     myUpdateQueue.setRestartTimerOnAdd(false);
66
67     final UiNotifyConnector uiNotifyConnector = new UiNotifyConnector(component, myUpdateQueue);
68     Disposer.register(this, myUpdateQueue);
69     Disposer.register(this, uiNotifyConnector);
70   }
71
72   /**
73    * @param delay update delay in milliseconds.
74    */
75   public void setDelay(int delay) {
76     myUpdateQueue.setMergingTimeSpan(delay);
77   }
78
79   public void setPassThroughMode(boolean passThroughMode) {
80     myUpdateQueue.setPassThrough(passThroughMode);
81   }
82
83   public void setModalityStateComponent(JComponent c) {
84     myUpdateQueue.setModalityStateComponent(c);
85   }
86
87   public boolean hasNodesToUpdate() {
88     return myNodeQueue.size() > 0;
89   }
90
91   public void dispose() {
92   }
93
94   public synchronized void addSubtreeToUpdate(@NotNull DefaultMutableTreeNode rootNode) {
95     addSubtreeToUpdate(new TreeUpdatePass(rootNode).setUpdateStamp(-1));
96   }
97
98   public synchronized void addSubtreeToUpdate(@NotNull TreeUpdatePass toAdd) {
99     if (LOG.isDebugEnabled()) {
100       LOG.debug("addSubtreeToUpdate:" + toAdd.getNode());
101     }
102
103
104     assert !toAdd.isExpired();
105
106
107     final AbstractTreeUi ui = myTreeBuilder.getUi();
108
109     if (ui.isUpdatingNow(toAdd.getNode())) {
110       toAdd.expire();
111     }
112     else {
113       for (Iterator<TreeUpdatePass> iterator = myNodeQueue.iterator(); iterator.hasNext();) {
114         final TreeUpdatePass passInQueue = iterator.next();
115
116
117         if (passInQueue == toAdd) {
118           return;
119         }
120         else if (passInQueue.getNode() == toAdd.getNode()) {
121           toAdd.expire();
122           return;
123         }
124         else if (toAdd.getNode().isNodeAncestor(passInQueue.getNode())) {
125           toAdd.expire();
126           return;
127         }
128         else if (passInQueue.getNode().isNodeAncestor(toAdd.getNode())) {
129           iterator.remove();
130           passInQueue.expire();
131         }
132       }
133     }
134
135     long newUpdateCount = toAdd.getUpdateStamp() == -1 ? myUpdateCount : myUpdateCount + 1;
136
137     if (!toAdd.isExpired()) {
138       final Collection<TreeUpdatePass> yielding = ui.getYeildingPasses();
139       for (Iterator<TreeUpdatePass> iterator = yielding.iterator(); iterator.hasNext();) {
140         TreeUpdatePass eachYielding = iterator.next();
141
142         final DefaultMutableTreeNode eachNode = eachYielding.getCurrentNode();
143         if (eachNode != null) {
144           if (eachNode.isNodeAncestor(toAdd.getNode())) {
145             eachYielding.setUpdateStamp(newUpdateCount);
146           }
147           else {
148             toAdd.expire();
149           }
150         }
151       }
152     }
153
154
155     if (toAdd.isExpired()) return;
156
157
158     myNodeQueue.add(toAdd);
159
160     myUpdateCount = newUpdateCount;
161     toAdd.setUpdateStamp(myUpdateCount);
162
163     queue(new Update("ViewUpdate") {
164       public boolean isExpired() {
165         return myTreeBuilder.isDisposed();
166       }
167
168       public void run() {
169         if (myTreeBuilder.getTreeStructure().hasSomethingToCommit()) {
170           myTreeBuilder.getTreeStructure().commit();
171           queue(this);
172           return;
173         }
174         try {
175           performUpdate();
176         }
177         catch (ProcessCanceledException e) {
178           throw e;
179         }
180         catch (RuntimeException e) {
181           LOG.error(myTreeBuilder.getClass().getName(), e);
182         }
183       }
184     });
185   }
186
187   private void queue(Update update) {
188     myUpdateQueue.queue(update);
189   }
190
191   /**
192    * @param node
193    * @deprecated use addSubtreeToUpdate instead
194    */
195   protected void updateSubtree(DefaultMutableTreeNode node) {
196     myTreeBuilder.updateSubtree(node);
197   }
198
199   public synchronized void performUpdate() {
200     if (myRunBeforeUpdate != null) {
201       myRunBeforeUpdate.run();
202       myRunBeforeUpdate = null;
203     }
204
205
206     while (!myNodeQueue.isEmpty()) {
207       if (isInPostponeMode()) break;
208
209
210       final TreeUpdatePass eachPass = myNodeQueue.removeFirst();
211
212       beforeUpdate(eachPass).doWhenDone(new Runnable() {
213         public void run() {
214           myTreeBuilder.getUi().updateSubtreeNow(eachPass, false);
215         }
216       });
217
218     }
219
220     myTreeBuilder.getUi().maybeReady();
221
222     if (myRunAfterUpdate != null) {
223       final Runnable runnable = new Runnable() {
224         public void run() {
225           List<Runnable> runAfterUpdate = null;
226           synchronized (myRunAfterUpdate) {
227             if (!myRunAfterUpdate.isEmpty()) {
228               runAfterUpdate = new ArrayList<Runnable>(myRunAfterUpdate);
229               myRunAfterUpdate.clear();
230             }
231           }
232           if (runAfterUpdate != null) {
233             for (Runnable r : runAfterUpdate) {
234               r.run();
235             }
236           }
237         }
238       };
239
240       myTreeBuilder.getReady(this).doWhenDone(runnable);
241     }
242   }
243
244   protected void invokeLater(Runnable runnable) {
245     if (myTreeBuilder.getUi().isPassthroughMode()) {
246       runnable.run();
247     } else {
248       final Application app = ApplicationManager.getApplication();
249       if (app != null) {
250         app.invokeLater(runnable);
251       }
252       else {
253         UIUtil.invokeAndWaitIfNeeded(runnable);
254       }
255     }
256   }
257
258   protected ActionCallback beforeUpdate(TreeUpdatePass pass) {
259     return new ActionCallback.Done();
260   }
261
262   public boolean addSubtreeToUpdateByElement(Object element) {
263     if (LOG.isDebugEnabled()) {
264       LOG.debug("addSubtreeToUpdateByElement:" + element);
265     }
266
267     DefaultMutableTreeNode node = myTreeBuilder.getNodeForElement(element);
268     if (node != null) {
269       addSubtreeToUpdate(node);
270       return true;
271     }
272     else {
273       return false;
274     }
275   }
276
277   public void cancelAllRequests() {
278     myNodeQueue.clear();
279     myUpdateQueue.cancelAllUpdates();
280   }
281
282   public void runAfterUpdate(final Runnable runnable) {
283     if (runnable == null) return;
284     synchronized (myRunAfterUpdate) {
285       myRunAfterUpdate.add(runnable);
286     }
287   }
288
289   public synchronized void runBeforeUpdate(final Runnable runnable) {
290     myRunBeforeUpdate = runnable;
291   }
292
293   public long getUpdateCount() {
294     return myUpdateCount;
295   }
296
297   public boolean isRerunNeededFor(TreeUpdatePass pass) {
298     return pass.getUpdateStamp() < getUpdateCount();
299   }
300
301   public boolean isInPostponeMode() {
302     return !myUpdateQueue.isActive() && !myUpdateQueue.isPassThrough();
303   }
304
305   public void showNotify() {
306     myUpdateQueue.showNotify();
307   }
308
309   public void hideNotify() {
310     myUpdateQueue.hideNotify();
311   }
312
313   protected boolean isEdt() {
314     return Alarm.isEventDispatchThread();
315   }
316
317   @Override
318   public String toString() {
319     return "AbstractTreeUpdater updateCount=" + myUpdateCount + " queue=[" + myUpdateQueue.toString() + "] " + " nodeQueue=" + myNodeQueue;
320   }
321
322   public void flush() {
323     myUpdateQueue.sendFlush();
324   }
325
326   public boolean isEnqueuedToUpdate(DefaultMutableTreeNode node) {
327     Iterator<TreeUpdatePass> nodes = myNodeQueue.iterator();
328     while (nodes.hasNext()) {
329       TreeUpdatePass each = nodes.next();
330       if (each.willUpdate(node)) return true;
331     }
332     return false;
333   }
334
335   public final void queueSelection(final SelectionRequest request) {
336     queue(new Update("UserSelection", Update.LOW_PRIORITY) {
337       public void run() {
338         request.execute(myTreeBuilder.getUi());
339       }
340
341       @Override
342       public boolean isExpired() {
343         return myTreeBuilder.isDisposed();
344       }
345
346       @Override
347       public void setRejected() {
348         request.reject();
349       }
350     });
351   }
352 }