TreeUi: tests refactored
[idea/community.git] / platform / platform-api / src / com / intellij / ide / util / treeView / UpdaterTreeState.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 package com.intellij.ide.util.treeView;
17
18 import com.intellij.openapi.util.ActionCallback;
19 import com.intellij.openapi.util.Condition;
20 import com.intellij.util.ArrayUtil;
21 import com.intellij.util.Function;
22 import org.jetbrains.annotations.Nullable;
23
24 import javax.swing.*;
25 import javax.swing.tree.DefaultMutableTreeNode;
26 import javax.swing.tree.TreePath;
27 import java.util.*;
28
29 public class UpdaterTreeState {
30
31   private final AbstractTreeUi myUi;
32   protected WeakHashMap<Object, Object> myToSelect = new WeakHashMap<Object, Object>();
33   protected WeakHashMap<Object, Condition> myAdjustedSelection = new WeakHashMap<Object, Condition>();
34   protected WeakHashMap<Object, Object> myDisposedElements = new WeakHashMap<Object, Object>();
35   protected WeakHashMap<Object, Object> myToExpand = new WeakHashMap<Object, Object>();
36   private boolean myProcessingNow;
37
38   private boolean myCanRunRestore = true;
39
40   public UpdaterTreeState(AbstractTreeUi ui) {
41     myUi = ui;
42
43     final JTree tree = myUi.getTree();
44     putAll(addPaths(tree.getSelectionPaths()), myToSelect);
45     putAll(addPaths(tree.getExpandedDescendants(new TreePath(tree.getModel().getRoot()))), myToExpand);
46   }
47
48   private static void putAll(final Set<Object> source, final Map<Object, Object> target) {
49     for (Object o : source) {
50       target.put(o, o);
51     }
52   }
53
54   private Set<Object> addPaths(Object[] elements) {
55     Set<Object> set = new HashSet<Object>();
56     if (elements != null) {
57       set.addAll(Arrays.asList(elements));
58     }
59
60     return addPaths(set);
61   }
62
63   private Set<Object> addPaths(Enumeration elements) {
64     ArrayList<Object> elementArray = new ArrayList<Object>();
65     if (elements != null) {
66       while (elements.hasMoreElements()) {
67         Object each = elements.nextElement();
68         elementArray.add(each);
69       }
70     }
71
72     return addPaths(elementArray);
73   }
74
75   private Set<Object> addPaths(Collection elements) {
76     Set<Object> target = new HashSet<Object>();
77
78     if (elements != null) {
79       for (Object each : elements) {
80         final Object node = ((TreePath)each).getLastPathComponent();
81         if (node instanceof DefaultMutableTreeNode) {
82           final Object descriptor = ((DefaultMutableTreeNode)node).getUserObject();
83           if (descriptor instanceof NodeDescriptor) {
84             final Object element = myUi.getElementFromDescriptor((NodeDescriptor)descriptor);
85             if (element != null) {
86               target.add(element);
87             }
88           }
89         }
90       }
91     }
92     return target;
93   }
94
95   public Object[] getToSelect() {
96     return myToSelect.keySet().toArray(new Object[myToSelect.size()]);
97   }
98
99   public Object[] getToExpand() {
100     return myToExpand.keySet().toArray(new Object[myToExpand.size()]);
101   }
102
103   public boolean process(Runnable runnable) {
104     boolean oldValue = isProcessingNow();
105     try {
106       setProcessingNow(true);
107       runnable.run();
108     }
109     finally {
110       if (!oldValue) {
111         setProcessingNow(false);
112       }
113     }
114
115     return isEmpty();
116   }
117
118   public boolean isEmpty() {
119     return myToExpand.isEmpty() && myToSelect.isEmpty() && myAdjustedSelection.isEmpty();
120   }
121
122
123   public boolean isProcessingNow() {
124     return myProcessingNow;
125   }
126
127   public void addAll(final UpdaterTreeState state) {
128     myToExpand.putAll(state.myToExpand);
129
130     Object[] toSelect = state.getToSelect();
131     for (Object each : toSelect) {
132       if (!myAdjustedSelection.containsKey(each)) {
133         myToSelect.put(each, each);
134       }
135     }
136
137     myCanRunRestore = state.myCanRunRestore;
138   }
139
140   public boolean restore(@Nullable DefaultMutableTreeNode actionNode) {
141     if (isProcessingNow() || !myCanRunRestore || myUi.hasNodesToUpdate()) return false;
142
143     invalidateToSelectWithRefsToParent(actionNode);
144
145     setProcessingNow(true);
146
147     final Object[] toSelect = getToSelect();
148     final Object[] toExpand = getToExpand();
149
150
151     final Map<Object, Condition> adjusted = new WeakHashMap<Object, Condition>();
152     adjusted.putAll(myAdjustedSelection);
153
154     clearSelection();
155     clearExpansion();
156
157     final Set<Object> originallySelected = myUi.getSelectedElements();
158
159     myUi._select(toSelect, new Runnable() {
160       public void run() {
161         processUnsuccessfulSelections(toSelect, new Function<Object, Object>() {
162           public Object fun(final Object o) {
163             if (myUi.getTree().isRootVisible() || !myUi.getTreeStructure().getRootElement().equals(o)) {
164               addSelection(o);
165             }
166             return o;
167           }
168         }, originallySelected);
169
170         processAjusted(adjusted, originallySelected).doWhenDone(new Runnable() {
171           public void run() {
172             myUi.expand(toExpand, new Runnable() {
173               public void run() {
174                 myUi.clearUpdaterState();
175                 setProcessingNow(false);
176               }
177             }, true);
178           }
179         });
180       }
181     }, false, true, true, false);
182
183     return true;
184   }
185
186   private void invalidateToSelectWithRefsToParent(DefaultMutableTreeNode actionNode) {
187     if (actionNode != null) {
188       Object readyElement = myUi.getElementFor(actionNode);
189       if (readyElement != null) {
190         Iterator<Object> toSelect = myToSelect.keySet().iterator();
191         while (toSelect.hasNext()) {
192           Object eachToSelect = toSelect.next();
193           if (readyElement.equals(myUi.getTreeStructure().getParentElement(eachToSelect))) {
194             List<Object> children = myUi.getLoadedChildrenFor(readyElement);
195             if (!children.contains(eachToSelect)) {
196               toSelect.remove();
197               if (!myToSelect.containsKey(readyElement) && !myUi.getSelectedElements().contains(eachToSelect)) {
198                 addAdjustedSelection(eachToSelect, Condition.FALSE);
199               }
200             }
201           }
202         }
203       }
204     }
205   }
206
207   void beforeSubtreeUpdate() {
208     myCanRunRestore = true;
209   }
210
211   private void processUnsuccessfulSelections(final Object[] toSelect, Function<Object, Object> restore, Set<Object> originallySelected) {
212     final Set<Object> selected = myUi.getSelectedElements();
213
214     boolean wasFullyRejected = false;
215     if (toSelect.length > 0 && selected.size() > 0 && !originallySelected.containsAll(selected)) {
216       final Set<Object> successfulSelections = new HashSet<Object>();
217       successfulSelections.addAll(Arrays.asList(toSelect));
218
219       successfulSelections.retainAll(selected);
220       wasFullyRejected = successfulSelections.size() == 0;
221     } else if (selected.size() == 0 && originallySelected.size() == 0) {
222       wasFullyRejected = true;
223     }
224
225     if (wasFullyRejected && selected.size() > 0) return;
226
227     for (Object eachToSelect : toSelect) {
228       if (!selected.contains(eachToSelect)) {
229         restore.fun(eachToSelect);
230       }
231     }
232   }
233
234   private ActionCallback processAjusted(final Map<Object, Condition> adjusted, final Set<Object> originallySelected) {
235     final ActionCallback result = new ActionCallback();
236
237     final Set<Object> allSelected = myUi.getSelectedElements();
238
239     Set<Object> toSelect = new HashSet<Object>();
240     for (Object each : adjusted.keySet()) {
241       if (adjusted.get(each).value(each)) continue;
242
243       for (final Object eachSelected : allSelected) {
244         if (isParentOrSame(each, eachSelected)) continue;
245         toSelect.add(each);
246       }
247       if (allSelected.size() == 0) {
248         toSelect.add(each);
249       }
250     }
251
252     final Object[] newSelection = ArrayUtil.toObjectArray(toSelect);
253
254     if (newSelection.length > 0) {
255       myUi._select(newSelection, new Runnable() {
256         public void run() {
257           final Set<Object> hangByParent = new HashSet<Object> ();
258           processUnsuccessfulSelections(newSelection, new Function<Object, Object>() {
259             public Object fun(final Object o) {
260               if (myUi.isInStructure(o) && !adjusted.get(o).value(o)) {
261                 hangByParent.add(o);
262               } else {
263                 addAdjustedSelection(o, adjusted.get(o));
264               }
265               return null;
266             }
267           }, originallySelected);
268
269           processHangByParent(hangByParent).notify(result);
270         }
271       }, true, true, true);
272     } else {
273       result.setDone();
274     }
275
276     return result;
277   }
278
279   private ActionCallback processHangByParent(Set<Object> elements) {
280     if (elements.size() == 0) return new ActionCallback.Done();
281
282     ActionCallback result = new ActionCallback(elements.size());
283     for (Iterator<Object> iterator = elements.iterator(); iterator.hasNext();) {
284       processHangByParent(iterator.next()).notify(result);
285     }
286     return result;
287   }
288
289   private ActionCallback processHangByParent(Object each) {
290     ActionCallback result = new ActionCallback();
291     processNextHang(each, result);
292     return result;
293   }
294
295   private void processNextHang(Object element, final ActionCallback callback) {
296     if (element == null || myUi.getSelectedElements().contains(element)) {
297       callback.setDone();
298     } else {
299       final Object nextElement = myUi.getTreeStructure().getParentElement(element);
300       if (nextElement == null) {
301         callback.setDone();
302       } else {
303        myUi.select(nextElement, new Runnable() {
304           public void run() {
305             processNextHang(nextElement, callback);
306           }
307         }, true);
308       }
309     }
310   }
311
312   private boolean isParentOrSame(Object parent, Object child) {
313     Object eachParent = child;
314     while (eachParent != null) {
315       if (parent.equals(eachParent)) return true;
316       eachParent = myUi.getTreeStructure().getParentElement(eachParent);
317     }
318
319     return false;
320   }
321
322   public void clearExpansion() {
323     myToExpand.clear();
324   }
325
326   public void clearSelection() {
327     myToSelect.clear();
328     myAdjustedSelection = new WeakHashMap<Object, Condition>();
329   }
330
331   public void addSelection(final Object element) {
332     myToSelect.put(element, element);
333   }
334
335   public void addAdjustedSelection(final Object element, Condition isExpired) {
336     myAdjustedSelection.put(element, isExpired);
337   }
338
339   @Override
340   public String toString() {
341     return "UpdaterState toSelect" + Arrays.asList(myToSelect) + " toExpand=" + Arrays.asList(myToExpand) + " processingNow=" + isProcessingNow() + " canRun=" + myCanRunRestore;
342   }
343
344   public void setProcessingNow(boolean processingNow) {
345     myProcessingNow = processingNow;
346     if (!processingNow) {
347       myUi.maybeReady();
348     }
349   }
350 }