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
.ide
.util
.treeView
;
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
;
25 import javax
.swing
.tree
.DefaultMutableTreeNode
;
26 import javax
.swing
.tree
.TreePath
;
29 public class UpdaterTreeState
{
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 int myProcessingCount
;
38 private boolean myCanRunRestore
= true;
40 private WeakHashMap
<Object
, Object
> myAdjustmentCause2Adjustment
= new WeakHashMap
<Object
, Object
>();
42 public UpdaterTreeState(AbstractTreeUi ui
) {
45 final JTree tree
= myUi
.getTree();
46 putAll(addPaths(tree
.getSelectionPaths()), myToSelect
);
47 putAll(addPaths(tree
.getExpandedDescendants(new TreePath(tree
.getModel().getRoot()))), myToExpand
);
50 private static void putAll(final Set
<Object
> source
, final Map
<Object
, Object
> target
) {
51 for (Object o
: source
) {
56 private Set
<Object
> addPaths(Object
[] elements
) {
57 Set
<Object
> set
= new HashSet
<Object
>();
58 if (elements
!= null) {
59 set
.addAll(Arrays
.asList(elements
));
65 private Set
<Object
> addPaths(Enumeration elements
) {
66 ArrayList
<Object
> elementArray
= new ArrayList
<Object
>();
67 if (elements
!= null) {
68 while (elements
.hasMoreElements()) {
69 Object each
= elements
.nextElement();
70 elementArray
.add(each
);
74 return addPaths(elementArray
);
77 private Set
<Object
> addPaths(Collection elements
) {
78 Set
<Object
> target
= new HashSet
<Object
>();
80 if (elements
!= null) {
81 for (Object each
: elements
) {
82 final Object node
= ((TreePath
)each
).getLastPathComponent();
83 if (node
instanceof DefaultMutableTreeNode
) {
84 final Object descriptor
= ((DefaultMutableTreeNode
)node
).getUserObject();
85 if (descriptor
instanceof NodeDescriptor
) {
86 final Object element
= myUi
.getElementFromDescriptor((NodeDescriptor
)descriptor
);
87 if (element
!= null) {
97 public Object
[] getToSelect() {
98 return myToSelect
.keySet().toArray(new Object
[myToSelect
.size()]);
101 public Object
[] getToExpand() {
102 return myToExpand
.keySet().toArray(new Object
[myToExpand
.size()]);
105 public boolean process(Runnable runnable
) {
107 setProcessingNow(true);
111 setProcessingNow(false);
117 public boolean isEmpty() {
118 return myToExpand
.isEmpty() && myToSelect
.isEmpty() && myAdjustedSelection
.isEmpty();
122 public boolean isProcessingNow() {
123 return myProcessingCount
> 0;
126 public void addAll(final UpdaterTreeState state
) {
127 myToExpand
.putAll(state
.myToExpand
);
129 Object
[] toSelect
= state
.getToSelect();
130 for (Object each
: toSelect
) {
131 if (!myAdjustedSelection
.containsKey(each
)) {
132 myToSelect
.put(each
, each
);
136 myCanRunRestore
= state
.myCanRunRestore
;
139 public boolean restore(@Nullable DefaultMutableTreeNode actionNode
) {
140 if (isProcessingNow() || !myCanRunRestore
|| myUi
.hasNodesToUpdate()) return false;
142 invalidateToSelectWithRefsToParent(actionNode
);
144 setProcessingNow(true);
146 final Object
[] toSelect
= getToSelect();
147 final Object
[] toExpand
= getToExpand();
150 final Map
<Object
, Condition
> adjusted
= new WeakHashMap
<Object
, Condition
>();
151 adjusted
.putAll(myAdjustedSelection
);
156 final Set
<Object
> originallySelected
= myUi
.getSelectedElements();
158 myUi
._select(toSelect
, new Runnable() {
160 processUnsuccessfulSelections(toSelect
, new Function
<Object
, Object
>() {
161 public Object
fun(final Object o
) {
162 if (myUi
.getTree().isRootVisible() || !myUi
.getTreeStructure().getRootElement().equals(o
)) {
167 }, originallySelected
);
169 processAjusted(adjusted
, originallySelected
).doWhenDone(new Runnable() {
171 myUi
.expand(toExpand
, new Runnable() {
173 myUi
.clearUpdaterState();
174 setProcessingNow(false);
180 }, false, true, true, false);
185 private void invalidateToSelectWithRefsToParent(DefaultMutableTreeNode actionNode
) {
186 if (actionNode
!= null) {
187 Object readyElement
= myUi
.getElementFor(actionNode
);
188 if (readyElement
!= null) {
189 Iterator
<Object
> toSelect
= myToSelect
.keySet().iterator();
190 while (toSelect
.hasNext()) {
191 Object eachToSelect
= toSelect
.next();
192 if (readyElement
.equals(myUi
.getTreeStructure().getParentElement(eachToSelect
))) {
193 List
<Object
> children
= myUi
.getLoadedChildrenFor(readyElement
);
194 if (!children
.contains(eachToSelect
)) {
196 if (!myToSelect
.containsKey(readyElement
) && !myUi
.getSelectedElements().contains(eachToSelect
)) {
197 addAdjustedSelection(eachToSelect
, Condition
.FALSE
, null);
206 void beforeSubtreeUpdate() {
207 myCanRunRestore
= true;
210 private void processUnsuccessfulSelections(final Object
[] toSelect
, Function
<Object
, Object
> restore
, Set
<Object
> originallySelected
) {
211 final Set
<Object
> selected
= myUi
.getSelectedElements();
213 boolean wasFullyRejected
= false;
214 if (toSelect
.length
> 0 && selected
.size() > 0 && !originallySelected
.containsAll(selected
)) {
215 final Set
<Object
> successfulSelections
= new HashSet
<Object
>();
216 successfulSelections
.addAll(Arrays
.asList(toSelect
));
218 successfulSelections
.retainAll(selected
);
219 wasFullyRejected
= successfulSelections
.size() == 0;
220 } else if (selected
.size() == 0 && originallySelected
.size() == 0) {
221 wasFullyRejected
= true;
224 if (wasFullyRejected
&& selected
.size() > 0) return;
226 for (Object eachToSelect
: toSelect
) {
227 if (!selected
.contains(eachToSelect
)) {
228 restore
.fun(eachToSelect
);
233 private ActionCallback
processAjusted(final Map
<Object
, Condition
> adjusted
, final Set
<Object
> originallySelected
) {
234 final ActionCallback result
= new ActionCallback();
236 final Set
<Object
> allSelected
= myUi
.getSelectedElements();
238 Set
<Object
> toSelect
= new HashSet
<Object
>();
239 for (Object each
: adjusted
.keySet()) {
240 if (adjusted
.get(each
).value(each
)) continue;
242 for (final Object eachSelected
: allSelected
) {
243 if (isParentOrSame(each
, eachSelected
)) continue;
246 if (allSelected
.size() == 0) {
251 final Object
[] newSelection
= ArrayUtil
.toObjectArray(toSelect
);
253 if (newSelection
.length
> 0) {
254 myUi
._select(newSelection
, new Runnable() {
256 final Set
<Object
> hangByParent
= new HashSet
<Object
> ();
257 processUnsuccessfulSelections(newSelection
, new Function
<Object
, Object
>() {
258 public Object
fun(final Object o
) {
259 if (myUi
.isInStructure(o
) && !adjusted
.get(o
).value(o
)) {
262 addAdjustedSelection(o
, adjusted
.get(o
), null);
266 }, originallySelected
);
268 processHangByParent(hangByParent
).notify(result
);
270 }, true, true, true);
278 private ActionCallback
processHangByParent(Set
<Object
> elements
) {
279 if (elements
.size() == 0) return new ActionCallback
.Done();
281 ActionCallback result
= new ActionCallback(elements
.size());
282 for (Iterator
<Object
> iterator
= elements
.iterator(); iterator
.hasNext();) {
283 Object hangElement
= iterator
.next();
284 if (!myAdjustmentCause2Adjustment
.containsKey(hangElement
)) {
285 processHangByParent(hangElement
).notify(result
);
293 private ActionCallback
processHangByParent(Object each
) {
294 ActionCallback result
= new ActionCallback();
295 processNextHang(each
, result
);
299 private void processNextHang(Object element
, final ActionCallback callback
) {
300 if (element
== null || myUi
.getSelectedElements().contains(element
)) {
303 final Object nextElement
= myUi
.getTreeStructure().getParentElement(element
);
304 if (nextElement
== null) {
307 myUi
.select(nextElement
, new Runnable() {
309 processNextHang(nextElement
, callback
);
316 private boolean isParentOrSame(Object parent
, Object child
) {
317 Object eachParent
= child
;
318 while (eachParent
!= null) {
319 if (parent
.equals(eachParent
)) return true;
320 eachParent
= myUi
.getTreeStructure().getParentElement(eachParent
);
326 public void clearExpansion() {
330 public void clearSelection() {
332 myAdjustedSelection
= new WeakHashMap
<Object
, Condition
>();
335 public void addSelection(final Object element
) {
336 myToSelect
.put(element
, element
);
339 public void addAdjustedSelection(final Object element
, Condition isExpired
, @Nullable Object adjustmentCause
) {
340 myAdjustedSelection
.put(element
, isExpired
);
341 if (adjustmentCause
!= null) {
342 myAdjustmentCause2Adjustment
.put(adjustmentCause
, element
);
347 public String
toString() {
348 return "UpdaterState toSelect" + Arrays
.asList(myToSelect
) + " toExpand=" + Arrays
.asList(myToExpand
) + " processingNow=" + isProcessingNow() + " canRun=" + myCanRunRestore
;
351 public void setProcessingNow(boolean processingNow
) {
357 if (!isProcessingNow()) {
362 public void removeFromSelection(Object element
) {
363 myToSelect
.remove(element
);
364 myAdjustedSelection
.remove(element
);