TreeUi: tests refactored
[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     assert !toAdd.isExpired();
104
105
106     final AbstractTreeUi ui = myTreeBuilder.getUi();
107
108     if (ui.isUpdatingNow(toAdd.getNode())) {
109       toAdd.expire();
110     }
111     else {
112       for (Iterator<TreeUpdatePass> iterator = myNodeQueue.iterator(); iterator.hasNext();) {
113         final TreeUpdatePass passInQueue = iterator.next();
114
115
116         if (passInQueue == toAdd) {
117           return;
118         }
119         else if (passInQueue.getNode() == toAdd.getNode()) {
120           toAdd.expire();
121           return;
122         }
123         else if (toAdd.getNode().isNodeAncestor(passInQueue.getNode())) {
124           toAdd.expire();
125           return;
126         }
127         else if (passInQueue.getNode().isNodeAncestor(toAdd.getNode())) {
128           iterator.remove();
129           passInQueue.expire();
130         }
131       }
132     }
133
134     long newUpdateCount = toAdd.getUpdateStamp() == -1 ? myUpdateCount : myUpdateCount + 1;
135
136     if (!toAdd.isExpired()) {
137       final Collection<TreeUpdatePass> yielding = ui.getYeildingPasses();
138       for (Iterator<TreeUpdatePass> iterator = yielding.iterator(); iterator.hasNext();) {
139         TreeUpdatePass eachYielding = iterator.next();
140
141         final DefaultMutableTreeNode eachNode = eachYielding.getCurrentNode();
142         if (eachNode != null) {
143           if (eachNode.isNodeAncestor(toAdd.getNode())) {
144             eachYielding.setUpdateStamp(newUpdateCount);
145           }
146           else {
147             toAdd.expire();
148           }
149         }
150       }
151     }
152
153
154     if (toAdd.isExpired()) return;
155
156
157     myNodeQueue.add(toAdd);
158
159     myUpdateCount = newUpdateCount;
160     toAdd.setUpdateStamp(myUpdateCount);
161
162     queue(new Update("ViewUpdate") {
163       public boolean isExpired() {
164         return myTreeBuilder.isDisposed();
165       }
166
167       public void run() {
168         if (myTreeBuilder.getTreeStructure().hasSomethingToCommit()) {
169           myTreeBuilder.getTreeStructure().commit();
170           queue(this);
171           return;
172         }
173         try {
174           performUpdate();
175         }
176         catch (ProcessCanceledException e) {
177           throw e;
178         }
179         catch (RuntimeException e) {
180           LOG.error(myTreeBuilder.getClass().getName(), e);
181         }
182       }
183     });
184   }
185
186   private void queue(Update update) {
187     myUpdateQueue.queue(update);
188   }
189
190   /**
191    * @param node
192    * @deprecated use addSubtreeToUpdate instead
193    */
194   protected void updateSubtree(DefaultMutableTreeNode node) {
195     myTreeBuilder.updateSubtree(node);
196   }
197
198   public synchronized void performUpdate() {
199     if (myRunBeforeUpdate != null) {
200       myRunBeforeUpdate.run();
201       myRunBeforeUpdate = null;
202     }
203
204
205     while (!myNodeQueue.isEmpty()) {
206       if (isInPostponeMode()) break;
207
208
209       final TreeUpdatePass eachPass = myNodeQueue.removeFirst();
210
211       beforeUpdate(eachPass).doWhenDone(new Runnable() {
212         public void run() {
213           myTreeBuilder.getUi().updateSubtreeNow(eachPass, false);
214         }
215       });
216
217     }
218
219     myTreeBuilder.getUi().maybeReady();
220
221     if (myRunAfterUpdate != null) {
222       final Runnable runnable = new Runnable() {
223         public void run() {
224           List<Runnable> runAfterUpdate = null;
225           synchronized (myRunAfterUpdate) {
226             if (!myRunAfterUpdate.isEmpty()) {
227               runAfterUpdate = new ArrayList<Runnable>(myRunAfterUpdate);
228               myRunAfterUpdate.clear();
229             }
230           }
231           if (runAfterUpdate != null) {
232             for (Runnable r : runAfterUpdate) {
233               r.run();
234             }
235           }
236         }
237       };
238
239       myTreeBuilder.getReady(this).doWhenDone(runnable);
240     }
241   }
242
243   protected void invokeLater(Runnable runnable) {
244     if (myTreeBuilder.getUi().isPassthroughMode()) {
245       runnable.run();
246     } else {
247       final Application app = ApplicationManager.getApplication();
248       if (app != null) {
249         app.invokeLater(runnable);
250       }
251       else {
252         UIUtil.invokeAndWaitIfNeeded(runnable);
253       }
254     }
255   }
256
257   protected ActionCallback beforeUpdate(TreeUpdatePass pass) {
258     return new ActionCallback.Done();
259   }
260
261   public boolean addSubtreeToUpdateByElement(Object element) {
262     if (LOG.isDebugEnabled()) {
263       LOG.debug("addSubtreeToUpdateByElement:" + element);
264     }
265
266     DefaultMutableTreeNode node = myTreeBuilder.getNodeForElement(element);
267     if (node != null) {
268       addSubtreeToUpdate(node);
269       return true;
270     }
271     else {
272       return false;
273     }
274   }
275
276   public void cancelAllRequests() {
277     myNodeQueue.clear();
278     myUpdateQueue.cancelAllUpdates();
279   }
280
281   public void runAfterUpdate(final Runnable runnable) {
282     if (runnable == null) return;
283     synchronized (myRunAfterUpdate) {
284       myRunAfterUpdate.add(runnable);
285     }
286   }
287
288   public synchronized void runBeforeUpdate(final Runnable runnable) {
289     myRunBeforeUpdate = runnable;
290   }
291
292   public long getUpdateCount() {
293     return myUpdateCount;
294   }
295
296   public boolean isRerunNeededFor(TreeUpdatePass pass) {
297     return pass.getUpdateStamp() < getUpdateCount();
298   }
299
300   public boolean isInPostponeMode() {
301     return !myUpdateQueue.isActive() && !myUpdateQueue.isPassThrough();
302   }
303
304   public void showNotify() {
305     myUpdateQueue.showNotify();
306   }
307
308   public void hideNotify() {
309     myUpdateQueue.hideNotify();
310   }
311
312   protected boolean isEdt() {
313     return Alarm.isEventDispatchThread();
314   }
315
316   @Override
317   public String toString() {
318     return "AbstractTreeUpdater updateCount=" + myUpdateCount + " queue=[" + myUpdateQueue.toString() + "] " + " nodeQueue=" + myNodeQueue;
319   }
320
321   public void flush() {
322     myUpdateQueue.sendFlush();
323   }
324
325   public boolean isEnqueuedToUpdate(DefaultMutableTreeNode node) {
326     Iterator<TreeUpdatePass> nodes = myNodeQueue.iterator();
327     while (nodes.hasNext()) {
328       TreeUpdatePass each = nodes.next();
329       if (each.willUpdate(node)) return true;
330     }
331     return false;
332   }
333
334   public final void queueSelection(final SelectionRequest request) {
335     queue(new Update("UserSelection", Update.LOW_PRIORITY) {
336       public void run() {
337         request.execute(myTreeBuilder.getUi());
338       }
339
340       @Override
341       public boolean isExpired() {
342         return myTreeBuilder.isDisposed();
343       }
344
345       @Override
346       public void setRejected() {
347         request.reject();
348       }
349     });
350   }
351 }