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
) {
46 public UpdaterTreeState(AbstractTreeUi ui
, boolean isEmpty
) {
50 final JTree tree
= myUi
.getTree();
51 putAll(addPaths(tree
.getSelectionPaths()), myToSelect
);
52 putAll(addPaths(tree
.getExpandedDescendants(new TreePath(tree
.getModel().getRoot()))), myToExpand
);
56 private static void putAll(final Set
<Object
> source
, final Map
<Object
, Object
> target
) {
57 for (Object o
: source
) {
62 private Set
<Object
> addPaths(Object
[] elements
) {
63 Set
<Object
> set
= new HashSet
<Object
>();
64 if (elements
!= null) {
65 set
.addAll(Arrays
.asList(elements
));
71 private Set
<Object
> addPaths(Enumeration elements
) {
72 ArrayList
<Object
> elementArray
= new ArrayList
<Object
>();
73 if (elements
!= null) {
74 while (elements
.hasMoreElements()) {
75 Object each
= elements
.nextElement();
76 elementArray
.add(each
);
80 return addPaths(elementArray
);
83 private Set
<Object
> addPaths(Collection elements
) {
84 Set
<Object
> target
= new HashSet
<Object
>();
86 if (elements
!= null) {
87 for (Object each
: elements
) {
88 final Object node
= ((TreePath
)each
).getLastPathComponent();
89 if (node
instanceof DefaultMutableTreeNode
) {
90 final Object descriptor
= ((DefaultMutableTreeNode
)node
).getUserObject();
91 if (descriptor
instanceof NodeDescriptor
) {
92 final Object element
= myUi
.getElementFromDescriptor((NodeDescriptor
)descriptor
);
93 if (element
!= null) {
103 public Object
[] getToSelect() {
104 return myToSelect
.keySet().toArray(new Object
[myToSelect
.size()]);
107 public Object
[] getToExpand() {
108 return myToExpand
.keySet().toArray(new Object
[myToExpand
.size()]);
111 public boolean process(Runnable runnable
) {
113 setProcessingNow(true);
117 setProcessingNow(false);
123 public boolean isEmpty() {
124 return myToExpand
.isEmpty() && myToSelect
.isEmpty() && myAdjustedSelection
.isEmpty();
128 public boolean isProcessingNow() {
129 return myProcessingCount
> 0;
132 public void addAll(final UpdaterTreeState state
) {
133 myToExpand
.putAll(state
.myToExpand
);
135 Object
[] toSelect
= state
.getToSelect();
136 for (Object each
: toSelect
) {
137 if (!myAdjustedSelection
.containsKey(each
)) {
138 myToSelect
.put(each
, each
);
142 myCanRunRestore
= state
.myCanRunRestore
;
145 public boolean restore(@Nullable DefaultMutableTreeNode actionNode
) {
146 if (isProcessingNow() || !myCanRunRestore
|| myUi
.hasNodesToUpdate()) {
150 invalidateToSelectWithRefsToParent(actionNode
);
152 setProcessingNow(true);
154 final Object
[] toSelect
= getToSelect();
155 final Object
[] toExpand
= getToExpand();
158 final Map
<Object
, Condition
> adjusted
= new WeakHashMap
<Object
, Condition
>();
159 adjusted
.putAll(myAdjustedSelection
);
164 final Set
<Object
> originallySelected
= myUi
.getSelectedElements();
166 myUi
._select(toSelect
, new Runnable() {
168 processUnsuccessfulSelections(toSelect
, new Function
<Object
, Object
>() {
169 public Object
fun(final Object o
) {
170 if (myUi
.getTree().isRootVisible() || !myUi
.getTreeStructure().getRootElement().equals(o
)) {
175 }, originallySelected
);
177 processAjusted(adjusted
, originallySelected
).doWhenDone(new Runnable() {
179 myUi
.expand(toExpand
, new Runnable() {
181 myUi
.clearUpdaterState();
182 setProcessingNow(false);
188 }, false, true, true, false);
193 private void invalidateToSelectWithRefsToParent(DefaultMutableTreeNode actionNode
) {
194 if (actionNode
!= null) {
195 Object readyElement
= myUi
.getElementFor(actionNode
);
196 if (readyElement
!= null) {
197 Iterator
<Object
> toSelect
= myToSelect
.keySet().iterator();
198 while (toSelect
.hasNext()) {
199 Object eachToSelect
= toSelect
.next();
200 if (readyElement
.equals(myUi
.getTreeStructure().getParentElement(eachToSelect
))) {
201 List
<Object
> children
= myUi
.getLoadedChildrenFor(readyElement
);
202 if (!children
.contains(eachToSelect
)) {
204 if (!myToSelect
.containsKey(readyElement
) && !myUi
.getSelectedElements().contains(eachToSelect
)) {
205 addAdjustedSelection(eachToSelect
, Condition
.FALSE
, null);
214 void beforeSubtreeUpdate() {
215 myCanRunRestore
= true;
218 private void processUnsuccessfulSelections(final Object
[] toSelect
, Function
<Object
, Object
> restore
, Set
<Object
> originallySelected
) {
219 final Set
<Object
> selected
= myUi
.getSelectedElements();
221 boolean wasFullyRejected
= false;
222 if (toSelect
.length
> 0 && selected
.size() > 0 && !originallySelected
.containsAll(selected
)) {
223 final Set
<Object
> successfulSelections
= new HashSet
<Object
>();
224 successfulSelections
.addAll(Arrays
.asList(toSelect
));
226 successfulSelections
.retainAll(selected
);
227 wasFullyRejected
= successfulSelections
.size() == 0;
228 } else if (selected
.size() == 0 && originallySelected
.size() == 0) {
229 wasFullyRejected
= true;
232 if (wasFullyRejected
&& selected
.size() > 0) return;
234 for (Object eachToSelect
: toSelect
) {
235 if (!selected
.contains(eachToSelect
)) {
236 restore
.fun(eachToSelect
);
241 private ActionCallback
processAjusted(final Map
<Object
, Condition
> adjusted
, final Set
<Object
> originallySelected
) {
242 final ActionCallback result
= new ActionCallback();
244 final Set
<Object
> allSelected
= myUi
.getSelectedElements();
246 Set
<Object
> toSelect
= new HashSet
<Object
>();
247 for (Object each
: adjusted
.keySet()) {
248 if (adjusted
.get(each
).value(each
)) continue;
250 for (final Object eachSelected
: allSelected
) {
251 if (isParentOrSame(each
, eachSelected
)) continue;
254 if (allSelected
.size() == 0) {
259 final Object
[] newSelection
= ArrayUtil
.toObjectArray(toSelect
);
261 if (newSelection
.length
> 0) {
262 myUi
._select(newSelection
, new Runnable() {
264 final Set
<Object
> hangByParent
= new HashSet
<Object
> ();
265 processUnsuccessfulSelections(newSelection
, new Function
<Object
, Object
>() {
266 public Object
fun(final Object o
) {
267 if (myUi
.isInStructure(o
) && !adjusted
.get(o
).value(o
)) {
270 addAdjustedSelection(o
, adjusted
.get(o
), null);
274 }, originallySelected
);
276 processHangByParent(hangByParent
).notify(result
);
278 }, true, true, true);
286 private ActionCallback
processHangByParent(Set
<Object
> elements
) {
287 if (elements
.size() == 0) return new ActionCallback
.Done();
289 ActionCallback result
= new ActionCallback(elements
.size());
290 for (Iterator
<Object
> iterator
= elements
.iterator(); iterator
.hasNext();) {
291 Object hangElement
= iterator
.next();
292 if (!myAdjustmentCause2Adjustment
.containsKey(hangElement
)) {
293 processHangByParent(hangElement
).notify(result
);
301 private ActionCallback
processHangByParent(Object each
) {
302 ActionCallback result
= new ActionCallback();
303 processNextHang(each
, result
);
307 private void processNextHang(Object element
, final ActionCallback callback
) {
308 if (element
== null || myUi
.getSelectedElements().contains(element
)) {
311 final Object nextElement
= myUi
.getTreeStructure().getParentElement(element
);
312 if (nextElement
== null) {
315 myUi
.select(nextElement
, new Runnable() {
317 processNextHang(nextElement
, callback
);
324 private boolean isParentOrSame(Object parent
, Object child
) {
325 Object eachParent
= child
;
326 while (eachParent
!= null) {
327 if (parent
.equals(eachParent
)) return true;
328 eachParent
= myUi
.getTreeStructure().getParentElement(eachParent
);
334 public void clearExpansion() {
338 public void clearSelection() {
340 myAdjustedSelection
= new WeakHashMap
<Object
, Condition
>();
343 public void addSelection(final Object element
) {
344 myToSelect
.put(element
, element
);
347 public void addAdjustedSelection(final Object element
, Condition isExpired
, @Nullable Object adjustmentCause
) {
348 myAdjustedSelection
.put(element
, isExpired
);
349 if (adjustmentCause
!= null) {
350 myAdjustmentCause2Adjustment
.put(adjustmentCause
, element
);
355 public String
toString() {
356 return "UpdaterState toSelect" + Arrays
.asList(myToSelect
) + " toExpand=" + Arrays
.asList(myToExpand
) + " processingNow=" + isProcessingNow() + " canRun=" + myCanRunRestore
;
359 public void setProcessingNow(boolean processingNow
) {
365 if (!isProcessingNow()) {
370 public void removeFromSelection(Object element
) {
371 myToSelect
.remove(element
);
372 myAdjustedSelection
.remove(element
);