KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > explorer > ExplorerManager


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19 package org.openide.explorer;
20
21 import org.openide.nodes.*;
22 import org.openide.util.*;
23 import org.openide.util.io.SafeException;
24
25 import java.awt.Component JavaDoc;
26
27 import java.beans.*;
28
29 import java.io.*;
30
31 import java.util.*;
32
33
34 /**
35  * Manages a selection and root context for a (set of) Explorer view(s). The
36  * views should register their {@link java.beans.VetoableChangeListener}s and
37  * {@link java.beans.PropertyChangeListener}s at the
38  * <code>ExplorerManager</code> of the Explorer they belong to
39  * (usually found in AWT hierarchy using {@link ExplorerManager#find}.
40  * The manager is then the mediator that keeps the shared state,
41  * notifies {@link PropertyChangeListener}s and {@link VetoableChangeListener}s
42  * about changes and allows views to call its setter methods to incluence
43  * the root of the visible hierarchy using {@link #setRootContext}, the
44  * set of selected nodes using {@link #setSelectedNodes} and also the
45  * explored context (useful for {@link org.openide.explorer.view.ListView} for
46  * example) using {@link #setExploredContext}.
47  * <p>
48  * This class interacts with Swing components in the
49  * <code>org.openide.explorer.view</code> package and as such it shall be
50  * used according to Swing threading model.
51  * <p>
52  * To provide an {@link ExplorerManager} from your component just let your
53  * component implement {@link Provider} as described at {@link ExplorerUtils}.
54  *
55  * <P>Deserialization may throw {@link SafeException} if the contexts cannot be
56  * restored correctly, but the stream is uncorrupted.
57  *
58  *
59  * @author Ian Formanek, Petr Hamernik, Jaroslav Tulach, Jan Jancura,
60  * Jesse Glick
61  * @see ExplorerUtils
62  * @see org.openide.explorer.view.TreeView
63  * @see org.openide.explorer.view.ListView
64  */

65 public final class ExplorerManager extends Object JavaDoc implements Serializable, Cloneable JavaDoc {
66     /** generated Serialized Version UID */
67     static final long serialVersionUID = -4330330689803575792L;
68
69     /** Name of property for the root context. */
70     public static final String JavaDoc PROP_ROOT_CONTEXT = "rootContext"; // NOI18N
71

72     /** Name of property for the explored context. */
73     public static final String JavaDoc PROP_EXPLORED_CONTEXT = "exploredContext"; // NOI18N
74

75     /** Name of property for the node selection. */
76     public static final String JavaDoc PROP_SELECTED_NODES = "selectedNodes"; // NOI18N
77

78     /** Name of property for change in a node. */
79     public static final String JavaDoc PROP_NODE_CHANGE = "nodeChange"; // NOI18N
80

81     /** Request processor for managing selections.
82     */

83     static RequestProcessor selectionProcessor;
84
85     /** Delay for coalescing events before removing destroyed nodes from
86         the selection.
87     */

88     private static final int SELECTION_SYNC_DELAY = 200;
89
90     /** defines serialized fields for the manager.
91     */

92     private static final ObjectStreamField[] serialPersistentFields = {
93         new ObjectStreamField("root", Node.Handle.class), // NOI18N
94
new ObjectStreamField("rootName", String JavaDoc.class), // NOI18N
95
new ObjectStreamField("explored", String JavaDoc[].class), // NOI18N
96
new ObjectStreamField("selected", Object JavaDoc[].class) // NOI18N
97
};
98
99     /** The support for VetoableChangeEvent */
100     private transient VetoableChangeSupport vetoableSupport;
101
102     /** The support for PropertyChangeEvent */
103     private transient PropertyChangeSupport propertySupport;
104
105     /** The current root context */
106     private Node rootContext;
107
108     /** The current explored context */
109     private Node exploredContext;
110
111     /** The currently selected beans */
112     private Node[] selectedNodes;
113
114     /** listener to destroy of root node */
115     private transient Listener listener;
116
117     /** weak listener */
118     private transient NodeListener weakListener;
119
120     /** Task that removes manages node selection issues.
121     */

122     private RequestProcessor.Task selectionSyncTask;
123
124     /** Actions factory provided for this explorer manager */
125     private ExplorerActionsImpl actions;
126
127     /** Construct a new manager. */
128     public ExplorerManager() {
129         init();
130     }
131
132     /** Initializes the nodes.
133     */

134     private void init() {
135         exploredContext = rootContext = Node.EMPTY;
136         selectedNodes = new Node[0];
137         listener = new Listener();
138         weakListener = NodeOp.weakNodeListener(listener, null);
139     }
140
141     /** Clones the manager.
142     * @return manager with the same settings like this one
143     */

144     public Object JavaDoc clone() {
145         ExplorerManager em = new ExplorerManager();
146         em.rootContext = rootContext;
147         em.exploredContext = exploredContext;
148         em.selectedNodes = selectedNodes;
149
150         return em;
151     }
152
153     /** Get the set of selected nodes.
154     * @return the selected nodes; empty (not <code>null</code>) if none are selected
155     */

156     public Node[] getSelectedNodes() {
157         return selectedNodes;
158     }
159
160     // compare two arrays of nodes in respect to a path to root
161
private boolean equalNodes(Node[] arr1, Node[] arr2) {
162         // generic tests
163
if (!Arrays.equals(arr1, arr2)) {
164             return false;
165         }
166
167         if ((arr1 == null) || (arr1.length == 0)) {
168             return true;
169         }
170
171         // compare paths from each node to root
172
int i = 0;
173
174         while ((i < arr1.length) && Arrays.equals(NodeOp.createPath(arr1[i], null), NodeOp.createPath(arr2[i], null))) {
175             i++;
176         }
177
178         return i == arr1.length;
179     }
180
181     /** Set the set of selected nodes.
182     * @param value the nodes to select; empty (not <code>null</code>) if none are to be selected
183     * @exception PropertyVetoException when the given nodes cannot be selected
184     * @throws IllegalArgumentException if <code>null</code> is given, or if any elements
185     * of the selection are not within the current root context
186     */

187     public final void setSelectedNodes(final Node[] value)
188     throws PropertyVetoException {
189         class AtomicSetSelectedNodes implements Runnable JavaDoc {
190             public PropertyVetoException veto;
191             private boolean doFire;
192             private Node[] oldValue;
193
194             /** @return false if no further processing is needed */
195             private boolean checkArgumentIsValid() {
196                 if (value == null) {
197                     throw new IllegalArgumentException JavaDoc(getString("EXC_NodeCannotBeNull"));
198                 }
199
200                 if (equalNodes(value, selectedNodes)) {
201                     return false;
202                 }
203
204                 for (int i = 0; i < value.length; i++) {
205                     if (value[i] == null) {
206                         throw new IllegalArgumentException JavaDoc(getString("EXC_NoElementOfNodeSelectionMayBeNull"));
207                     }
208
209                     checkUnderRoot(value[i], "EXC_NodeSelectionCannotContainNodes");
210                 }
211
212                 if ((value.length != 0) && (vetoableSupport != null)) {
213                     try {
214                         // we send the vetoable change event only for non-empty selections
215
vetoableSupport.fireVetoableChange(PROP_SELECTED_NODES, selectedNodes, value);
216                     } catch (PropertyVetoException ex) {
217                         veto = ex;
218
219                         return false;
220                     }
221                 }
222
223                 return true;
224             }
225
226             private void updateSelection() {
227                 oldValue = selectedNodes;
228
229                 Collection<Node> currentNodes = Arrays.asList(oldValue);
230
231                 // PENDING: filter out duplicities from the selection
232
Collection<Node> newSelection = Arrays.asList(value);
233
234                 Collection<Node> nodesToAdd = new LinkedList<Node>(newSelection);
235                 nodesToAdd.removeAll(currentNodes);
236
237                 Collection<Node> nodesToRemove = new LinkedList<Node>(currentNodes);
238                 nodesToRemove.removeAll(newSelection);
239
240                 selectedNodes = value;
241
242                 Iterator<Node> it;
243
244                 // remove listeners from nodes that are being deselected
245
for (it = nodesToRemove.iterator(); it.hasNext();) {
246                     Node n = it.next();
247                     n.removeNodeListener(weakListener);
248                 }
249
250                 // and add listeners to nodes that become selected
251
for (it = nodesToAdd.iterator(); it.hasNext();) {
252                     Node n = it.next();
253                     n.removeNodeListener(weakListener);
254                     n.addNodeListener(weakListener);
255                 }
256
257                 doFire = true;
258             }
259             
260             public void fire() {
261                 if (doFire) {
262                     fireInAWT(PROP_SELECTED_NODES, oldValue, selectedNodes);
263                 }
264             }
265
266             public void run() {
267                 if (checkArgumentIsValid()) {
268                     updateSelection();
269                 }
270             }
271         }
272
273         AtomicSetSelectedNodes setNodes = new AtomicSetSelectedNodes();
274         Children.MUTEX.readAccess(setNodes);
275         setNodes.fire();
276         
277         if (setNodes.veto != null) {
278             throw setNodes.veto;
279         }
280     }
281
282     /** Get the explored context.
283      * <p>The "explored context" is not as frequently used as the node selection;
284      * generally it refers to a parent node which contains all of the things
285      * being displayed at this moment. For <code>BeanTreeView</code> this is
286      * irrelevant, but <code>ContextTreeView</code> uses it (in lieu of the node
287      * selection) and for <code>IconView</code> it is important (the node
288      * whose children are visible, i.e. the "background" of the icon view).
289      * @return the node being explored, or <code>null</code>
290      */

291     public final Node getExploredContext() {
292         return exploredContext;
293     }
294
295     /** Set the explored context.
296      * The node selection will be cleared as well.
297      * @param value the new node to explore, or <code>null</code> if none should be explored.
298      * @throws IllegalArgumentException if the node is not within the current root context in the node hierarchy
299      */

300     public final void setExploredContext(Node value) {
301         setExploredContext(value, new Node[0]);
302     }
303
304     /** Set the explored context.
305      * The node selection will be changed as well. Note: node selection cannot be
306      * vetoed if calling this method. It is generally better to call setExploredContextAndSelection.
307      * @param value the new node to explore, or <code>null</code> if none should be explored.
308      * @throws IllegalArgumentException if the node is not within the current root context in the node hierarchy
309      */

310     public final void setExploredContext(final Node value, final Node[] selection) {
311         class SetExploredContext implements Runnable JavaDoc {
312             boolean doFire;
313             Object JavaDoc oldValue;
314             
315             public void run() {
316                 // handles nulls correctly:
317
if (Utilities.compareObjects(value, exploredContext)) {
318                     setSelectedNodes0(selection);
319
320                     return;
321                 }
322
323                 checkUnderRoot(value, "EXC_ContextMustBeWithinRootContext");
324                 setSelectedNodes0(selection);
325
326                 oldValue = exploredContext;
327                 exploredContext = value;
328
329                 doFire = true;
330             }
331             public void fire() {
332                 if (doFire) {
333                     fireInAWT(PROP_EXPLORED_CONTEXT, oldValue, value);
334                 }
335             }
336         }
337
338         SetExploredContext set = new SetExploredContext();
339         Children.MUTEX.readAccess(set);
340         set.fire();
341     }
342
343     /** Set the explored context and selected nodes. If the change in selected nodes is vetoed,
344      * PropertyVetoException is rethrown from here.
345      * @param value the new node to explore, or <code>null</code> if none should be explored.
346      * @param selection the new nodes to be selected
347      * @throws IllegalArgumentException if the node is not within the current root context in the node hierarchy
348      * @throws PropertyVetoExcepion if listeners attached to this explorer manager do so
349      */

350     public final void setExploredContextAndSelection(final Node value, final Node[] selection)
351     throws PropertyVetoException {
352         class SetExploredContextAndSelection implements Runnable JavaDoc {
353             public PropertyVetoException veto;
354             private boolean doFire;
355             private Object JavaDoc oldValue;
356
357             public void run() {
358                 try {
359                     // handles nulls correctly:
360
if (Utilities.compareObjects(value, exploredContext)) {
361                         setSelectedNodes(selection);
362
363                         return;
364                     }
365
366                     checkUnderRoot(value, "EXC_ContextMustBeWithinRootContext");
367                     setSelectedNodes(selection);
368
369                     oldValue = exploredContext;
370                     exploredContext = value;
371
372                     doFire = true;
373                 } catch (PropertyVetoException ex) {
374                     veto = ex;
375                 }
376             }
377             
378             public void fire() {
379                 if (doFire) {
380                     fireInAWT(PROP_EXPLORED_CONTEXT, oldValue, exploredContext);
381                 }
382             }
383         }
384
385         SetExploredContextAndSelection set = new SetExploredContextAndSelection();
386         Children.MUTEX.readAccess(set);
387         set.fire();
388
389         if (set.veto != null) {
390             throw set.veto;
391         }
392     }
393
394     /** Sets selected nodes and handles PropertyVetoException */
395     final void setSelectedNodes0(Node[] nodes) {
396         try {
397             setSelectedNodes(nodes);
398         } catch (PropertyVetoException e) {
399         }
400     }
401
402     /** Get the root context.
403     * <p>The "root context" is simply the topmost node that this explorer can
404     * display or manipulate. For <code>BeanTreeView</code>, this would mean
405     * the root node of the tree. For e.g. <code>IconView</code>, this would
406     * mean the uppermost possible node that that icon view could display;
407     * while the explored context would change at user prompting via the
408     * up button and clicking on subfolders, the root context would be fixed
409     * by the code displaying the explorer.
410     * @return the root context node
411     */

412     public final Node getRootContext() {
413         return rootContext;
414     }
415
416     /** Set the root context.
417     * The explored context will be set to the new root context as well.
418     * If any of the selected nodes are not inside it, the selection will be cleared.
419     * @param value the new node to serve as a root
420     * @throws IllegalArgumentException if it is <code>null</code>
421     */

422     public final void setRootContext(Node value) {
423         if (value == null) {
424             throw new IllegalArgumentException JavaDoc(getString("EXC_CannotHaveNullRootContext"));
425         }
426
427         if (rootContext.equals(value)) {
428             return;
429         }
430
431         Node oldValue = rootContext;
432         rootContext = value;
433
434         oldValue.removeNodeListener(weakListener);
435         rootContext.addNodeListener(weakListener);
436
437         fireInAWT(PROP_ROOT_CONTEXT, oldValue, rootContext);
438
439         Node[] newselection = getSelectedNodes();
440
441         if (!areUnderTarget(newselection, rootContext)) {
442             newselection = new Node[0];
443         }
444
445         setExploredContext(rootContext, newselection);
446     }
447
448     /** @return true iff all nodes are under the target node */
449     private boolean areUnderTarget(Node[] nodes, Node target) {
450 bigloop:
451         for (int i = 0; i < nodes.length; i++) {
452             Node node = nodes[i];
453
454             while (node != null) {
455                 if (node.equals(target)) {
456                     continue bigloop;
457                 }
458
459                 node = node.getParentNode();
460             }
461
462             return false;
463         }
464
465         return true;
466     }
467
468     /** Add a <code>PropertyChangeListener</code> to the listener list.
469     * @param l the listener to add
470     */

471     public synchronized void addPropertyChangeListener(PropertyChangeListener l) {
472         if (propertySupport == null) {
473             propertySupport = new PropertyChangeSupport(this);
474         }
475
476         propertySupport.addPropertyChangeListener(l);
477     }
478
479     /** Remove a <code>PropertyChangeListener</code> from the listener list.
480     * @param l the listener to remove
481     */

482     public synchronized void removePropertyChangeListener(PropertyChangeListener l) {
483         if (propertySupport != null) {
484             propertySupport.removePropertyChangeListener(l);
485         }
486     }
487
488     /** Add a <code>VetoableListener</code> to the listener list.
489     * @param l the listener to add
490     */

491     public synchronized void addVetoableChangeListener(VetoableChangeListener l) {
492         if (vetoableSupport == null) {
493             vetoableSupport = new VetoableChangeSupport(this);
494         }
495
496         vetoableSupport.addVetoableChangeListener(l);
497     }
498
499     /** Remove a <code>VetoableChangeListener</code> from the listener list.
500     * @param l the listener to remove
501     */

502     public synchronized void removeVetoableChangeListener(VetoableChangeListener l) {
503         if (vetoableSupport != null) {
504             vetoableSupport.removeVetoableChangeListener(l);
505         }
506     }
507
508     /** Checks whether given Node is a subnode of rootContext.
509     * @return true if specified Node is under current rootContext
510     */

511     private boolean isUnderRoot(Node node) {
512         while (node != null) {
513             if (node.equals(rootContext)) {
514                 return true;
515             }
516
517             node = node.getParentNode();
518         }
519
520         return false;
521     }
522
523     /** Checks whether given Node is a subnode of rootContext.
524     * and throws IllegalArgumentException if not.
525     */

526     private void checkUnderRoot(Node value, String JavaDoc errorKey) {
527         if ((value != null) && !isUnderRoot(value)) {
528             throw new IllegalArgumentException JavaDoc(
529                 NbBundle.getMessage(
530                     ExplorerManager.class, errorKey, value.getDisplayName(), rootContext.getDisplayName()
531                 )
532             );
533         }
534     }
535
536     /** Waits till all async processing is finished
537      */

538     final void waitFinished() {
539         if (selectionSyncTask != null) {
540             selectionSyncTask.waitFinished();
541         }
542     }
543
544     /** serializes object
545     * @serialData the following objects are written in sequence:
546     * <ol>
547     * <li> a Node.Handle for the root context; may be null if root context
548     * is not persistable
549     * <li> the display name of the root context (to give nicer error messages
550     * later on)
551     * <li> the path from root context to explored context; null if no explored
552     * context or no such path
553     * <li> for every element of node selection, path from root context to that node;
554     * null if no such path
555     * <li> null to terminate
556     * </ol>
557     * Note that if the root context handle is null, the display name is still written
558     * but the paths to explored context and node selection are not written, the stream
559     * ends there.
560     */

561     private void writeObject(ObjectOutputStream os) throws IOException {
562         // indication that we gonna use put fields and not the old method.
563
os.writeObject(this);
564
565         ObjectOutputStream.PutField fields = os.putFields();
566
567         // [PENDING] is this method (and readObject) always called from within
568
// the Nodes mutex? It should be!
569
//System.err.println("rootContext: " + rootContext);
570
Node.Handle rCH = rootContext.getHandle();
571         fields.put("root", rCH); // NOI18N
572

573         //System.err.println("writing: " + rCH);
574
fields.put("rootName", rootContext.getDisplayName()); // NOI18N
575

576         if (rCH != null) {
577             // Note that explored context may be null (this is valid).
578
// Also, it may have happened that the hierarchy changed so that
579
// the explored context is *no longer* under the root (though it was at
580
// the time these things were set up). In this case, we cannot store the
581
// path. Caution: NodeOp.createPath will create a path to a root (parentless)
582
// node even if you specify a non-null root, if the first arg is not a child!
583
String JavaDoc[] explored;
584
585             if (exploredContext == null) {
586                 explored = null;
587             } else if (isUnderRoot(exploredContext)) {
588                 explored = NodeOp.createPath(exploredContext, rootContext);
589             } else {
590                 explored = null;
591             }
592
593             fields.put("explored", explored); // NOI18N
594

595             List<String JavaDoc[]> selected = new LinkedList<String JavaDoc[]>();
596
597             for (int i = 0; i < selectedNodes.length; i++) {
598                 if (isUnderRoot(selectedNodes[i])) {
599                     selected.add(NodeOp.createPath(selectedNodes[i], rootContext));
600                 }
601             }
602
603             fields.put("selected", selected.toArray()); // NOI18N
604
}
605
606         os.writeFields();
607     }
608
609     /** Deserializes the view and initializes it
610      * @serialData see writeObject
611      */

612     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException JavaDoc {
613         // perform initialization
614
init();
615
616         // read the first object in the stream
617
Object JavaDoc firstObject = ois.readObject();
618
619         if (firstObject != this) {
620             // use old version of deserialization
621
readObjectOld((Node.Handle) firstObject, ois);
622
623             return;
624         }
625
626         // work with get fields
627
ObjectInputStream.GetField fields = ois.readFields();
628
629         // read root handle
630
Node.Handle h = (Node.Handle) fields.get("root", null); // NOI18N
631

632         //System.err.println("reading: " + h);
633
final String JavaDoc rootName = (String JavaDoc) fields.get("rootName", null); // NOI18N
634

635         //System.err.println("reading: " + rootName);
636
if (h == null) {
637             // Cancel deserialization (e.g. of the ExplorerPanel window) in case the
638
// root handle was not persistent:
639
throw new SafeException(
640                 new IOException(
641                     "Could not restore Explorer window; the root node \"" + rootName +
642                     "\" is not persistent; override Node.getHandle to fix"
643                 )
644             ); // NOI18N
645
} else {
646             String JavaDoc[] exploredCtx = (String JavaDoc[]) fields.get("explored", null); // NOI18N
647
Object JavaDoc[] selPaths = (Object JavaDoc[]) fields.get("selected", null); // NOI18N
648

649             try {
650                 Node root = h.getNode();
651
652                 if (root == null) {
653                     throw new IOException("Node.Handle.getNode (for " + rootName + ") should not return null"); // NOI18N
654
}
655
656                 restoreSelection(root, exploredCtx, Arrays.asList(selPaths));
657             } catch (IOException ioe) {
658                 SafeException safe = new SafeException(ioe);
659
660                 if (!Utilities.compareObjects(ioe.getMessage(), ioe.getLocalizedMessage())) {
661                     Exceptions.attachLocalizedMessage(safe,
662                                                       NbBundle.getMessage(ExplorerManager.class,
663                                                                           "EXC_handle_failed",
664                                                                           rootName));
665                 }
666
667                 throw safe;
668             }
669         }
670     }
671
672     private void readObjectOld(Node.Handle h, ObjectInputStream ois)
673     throws java.io.IOException JavaDoc, ClassNotFoundException JavaDoc {
674         if (h == null) {
675             // do nothing => should not occur to often and moreover this is also
676
// dead code replaced by new version
677
return;
678         } else {
679             String JavaDoc[] rootCtx = (String JavaDoc[]) ois.readObject();
680             String JavaDoc[] exploredCtx = (String JavaDoc[]) ois.readObject();
681             List<String JavaDoc[]> ll = new LinkedList<String JavaDoc[]>();
682
683             for (;;) {
684                 String JavaDoc[] path = (String JavaDoc[]) ois.readObject();
685
686                 if (path == null) {
687                     break;
688                 }
689
690                 ll.add(path);
691             }
692
693             Node root = findPath(h.getNode(), rootCtx);
694             restoreSelection(root, exploredCtx, ll);
695         }
696     }
697
698     private void restoreSelection(
699         final Node root, final String JavaDoc[] exploredCtx, final List</*String[]*/?> selectedPaths) {
700         setRootContext(root);
701
702         // XXX(-ttran) findPath() can take a long time and employs DataSystems
703
// and others. We cannot call it synchrorously, in the past deadlocks
704
// have happened because of this. OTOH as we call setSelectedNodes
705
// asynchonously someone else can change the root context or the Node
706
// hierarchy in between, which causes setSelectedNodes to throw
707
// IllegalArgumentException. There seems to be no simple good
708
// solution. For now we just catch IllegalArgumentException and be
709
// decently silent about the fact.
710
//RequestProcessor.getDefault().post(new Runnable() {
711
// bugfix #38207, select nodes has to be called in AWT queue here
712
Mutex.EVENT.readAccess(
713             new Runnable JavaDoc() {
714                 public void run() {
715                     // convert paths to Nodes
716
List<Node> selNodes = new ArrayList<Node>(selectedPaths.size());
717
718                     for (Object JavaDoc path : selectedPaths) {
719                         selNodes.add(findPath(root, (String JavaDoc[]) path));
720                     }
721
722                     // set the selection
723
Node[] newSelection = selNodes.toArray(new Node[selNodes.size()]);
724
725                     if (exploredCtx != null) {
726                         setExploredContext(findPath(root, exploredCtx), newSelection);
727                     } else {
728                         setSelectedNodes0(newSelection);
729                     }
730                 }
731             }
732         );
733     }
734
735     /**
736      * Finds the proper Explorer manager for a given component. This is done
737      * by traversing the component hierarchy and finding the first ancestor
738      * that implements {@link Provider}. <P> This method should be used in
739      * {@link Component#addNotify} of each component that works with the
740      * Explorer manager, e.g.:
741      * <p><pre>
742      * private transient ExplorerManager explorer;
743      *
744      * public void addNotify () {
745      * super.addNotify ();
746      * explorer = ExplorerManager.find (this);
747      * }
748      * </pre>
749      *
750      * @param comp component to find the manager for
751      * @return the manager, or a new empty manager if no ancestor implements
752      * <code>Provider</code>
753      *
754      * @see Provider
755      */

756     public static ExplorerManager find(Component JavaDoc comp) {
757         // start looking for manager from parent, not the component itself
758
for (;;) {
759             comp = comp.getParent();
760
761             if (comp == null) {
762                 // create new explorer because nothing has been found
763
return new ExplorerManager();
764             }
765
766             if (comp instanceof Provider) {
767                 // ok, found a provider, return its manager
768
return ((Provider) comp).getExplorerManager();
769             }
770         }
771     }
772
773     /** Finds node by given path */
774     static Node findPath(Node r, String JavaDoc[] path) {
775         try {
776             return NodeOp.findPath(r, path);
777         } catch (NodeNotFoundException ex) {
778             return ex.getClosestNode();
779         }
780     }
781
782     /** Creates or retrieves RequestProcessor for selection updates. */
783     static synchronized RequestProcessor getSelectionProcessor() {
784         if (selectionProcessor == null) {
785             selectionProcessor = new RequestProcessor("ExplorerManager-selection"); //NOI18N
786
}
787
788         return selectionProcessor;
789     }
790
791     /** Finds ExplorerActionsImpl for a explorer manager.
792      * @param em the manager
793      * @return ExplorerActionsImpl
794      */

795     static synchronized ExplorerActionsImpl findExplorerActionsImpl(ExplorerManager em) {
796         if (em.actions == null) {
797             em.actions = new ExplorerActionsImpl();
798             em.actions.attach(em);
799         }
800
801         return em.actions;
802     }
803
804     private void fireInAWT(final String JavaDoc propName, final Object JavaDoc oldVal, final Object JavaDoc newVal) {
805         if (propertySupport != null) {
806             Mutex.EVENT.readAccess(
807                 new Runnable JavaDoc() {
808                     public void run() {
809                         propertySupport.firePropertyChange(propName, oldVal, newVal);
810                     }
811                 }
812             );
813         }
814     }
815
816     private static String JavaDoc getString(String JavaDoc key) {
817         return NbBundle.getMessage(ExplorerManager.class, key);
818     }
819
820     //
821
// inner classes
822
//
823

824     /** Interface for components wishing to provide their own <code>ExplorerManager</code>.
825     * @see ExplorerManager#find
826     * @see ExplorerUtils
827     */

828     public static interface Provider {
829         /** Get the explorer manager.
830         * @return the manager
831         */

832         public ExplorerManager getExplorerManager();
833     }
834
835     /** Listener to be notified when root node has been destroyed.
836     * Then the root node is changed to Node.EMPTY
837     */

838     private class Listener extends NodeAdapter implements Runnable JavaDoc {
839         Collection<Node> removeList = new HashSet<Node>();
840
841         Listener() {
842         }
843
844         /** Fired when the node is deleted.
845          * @param ev event describing the node
846          */

847         public void nodeDestroyed(NodeEvent ev) {
848             if (ev.getNode().equals(getRootContext())) {
849                 // node has been deleted
850
// [PENDING] better to show a node with a label such as "<deleted>"
851
// and a tool tip explaining the situation
852
setRootContext(Node.EMPTY);
853             } else {
854                 // assume that the node is among currently selected nodes
855
scheduleRemove(ev.getNode());
856             }
857         }
858
859         /* Change in a node.
860          * @param ev the event
861          */

862         public void propertyChange(java.beans.PropertyChangeEvent JavaDoc ev) {
863             fireInAWT(PROP_NODE_CHANGE, null, null);
864         }
865
866         /** Schedules removal of a node
867         */

868         private void scheduleRemove(Node n) {
869             synchronized (ExplorerManager.this) {
870                 if (selectionSyncTask == null) {
871                     selectionSyncTask = getSelectionProcessor().create(this);
872                 } else {
873                     selectionSyncTask.cancel();
874                 }
875             }
876
877             synchronized (this) {
878                 removeList.add(n);
879             }
880
881             // invariant: selectionSyncTask != null && is not running yet.
882
selectionSyncTask.schedule(SELECTION_SYNC_DELAY);
883         }
884
885         public void run() {
886             if (!Children.MUTEX.isReadAccess()) {
887                 Children.MUTEX.readAccess(this);
888
889                 return;
890             }
891
892             Collection<Node> remove;
893
894             synchronized (this) {
895                 // atomically clears the list while keeping a copy.
896
// if another node is removed after this point, the selection
897
// will be updated later.
898
remove = removeList;
899                 removeList = new HashSet<Node>();
900             }
901
902             LinkedList<Node> newSel = new LinkedList<Node>(Arrays.asList(getSelectedNodes()));
903             Iterator<Node> it = remove.iterator();
904
905             while (it.hasNext()) {
906                 Node n_remove = it.next();
907
908                 if (newSel.contains(n_remove)) {
909                     // compare paths to root
910
Node n_selection = newSel.get(newSel.indexOf(n_remove));
911
912                     if (!Arrays.equals(NodeOp.createPath(n_remove, null), NodeOp.createPath(n_selection, null))) {
913                         it.remove();
914                     }
915                 }
916             }
917
918             newSel.removeAll(remove);
919             for( Iterator<Node> i=newSel.iterator(); i.hasNext(); ) {
920                 Node n = i.next();
921                 if( !isUnderRoot( n ) )
922                     i.remove();
923             }
924
925             Node[] selNodes = newSel.toArray(new Node[newSel.size()]);
926             setSelectedNodes0(selNodes);
927         }
928     }
929 }
930
Popular Tags