0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.openide.explorer;
0042:
0043: import org.openide.nodes.*;
0044: import org.openide.util.*;
0045: import org.openide.util.io.SafeException;
0046:
0047: import java.awt.Component;
0048:
0049: import java.beans.*;
0050:
0051: import java.io.*;
0052:
0053: import java.util.*;
0054:
0055: /**
0056: * Manages a selection and root context for a (set of) Explorer view(s). The
0057: * views should register their {@link java.beans.VetoableChangeListener}s and
0058: * {@link java.beans.PropertyChangeListener}s at the
0059: * <code>ExplorerManager</code> of the Explorer they belong to
0060: * (usually found in AWT hierarchy using {@link ExplorerManager#find}.
0061: * The manager is then the mediator that keeps the shared state,
0062: * notifies {@link PropertyChangeListener}s and {@link VetoableChangeListener}s
0063: * about changes and allows views to call its setter methods to incluence
0064: * the root of the visible hierarchy using {@link #setRootContext}, the
0065: * set of selected nodes using {@link #setSelectedNodes} and also the
0066: * explored context (useful for {@link org.openide.explorer.view.ListView} for
0067: * example) using {@link #setExploredContext}.
0068: * <p>
0069: * This class interacts with Swing components in the
0070: * <code>org.openide.explorer.view</code> package and as such it shall be
0071: * used according to Swing threading model.
0072: * <p>
0073: * To provide an {@link ExplorerManager} from your component just let your
0074: * component implement {@link Provider} as described at {@link ExplorerUtils}.
0075: *
0076: * <P>Deserialization may throw {@link SafeException} if the contexts cannot be
0077: * restored correctly, but the stream is uncorrupted.
0078: *
0079: *
0080: * @author Ian Formanek, Petr Hamernik, Jaroslav Tulach, Jan Jancura,
0081: * Jesse Glick
0082: * @see ExplorerUtils
0083: * @see org.openide.explorer.view.TreeView
0084: * @see org.openide.explorer.view.ListView
0085: */
0086: public final class ExplorerManager extends Object implements
0087: Serializable, Cloneable {
0088: /** generated Serialized Version UID */
0089: static final long serialVersionUID = -4330330689803575792L;
0090:
0091: /** Name of property for the root context. */
0092: public static final String PROP_ROOT_CONTEXT = "rootContext"; // NOI18N
0093:
0094: /** Name of property for the explored context. */
0095: public static final String PROP_EXPLORED_CONTEXT = "exploredContext"; // NOI18N
0096:
0097: /** Name of property for the node selection. */
0098: public static final String PROP_SELECTED_NODES = "selectedNodes"; // NOI18N
0099:
0100: /** Name of property for change in a node. */
0101: public static final String PROP_NODE_CHANGE = "nodeChange"; // NOI18N
0102:
0103: /** Request processor for managing selections.
0104: */
0105: static RequestProcessor selectionProcessor;
0106:
0107: /** Delay for coalescing events before removing destroyed nodes from
0108: the selection.
0109: */
0110: private static final int SELECTION_SYNC_DELAY = 200;
0111:
0112: /** defines serialized fields for the manager.
0113: */
0114: private static final ObjectStreamField[] serialPersistentFields = {
0115: new ObjectStreamField("root", Node.Handle.class), // NOI18N
0116: new ObjectStreamField("rootName", String.class), // NOI18N
0117: new ObjectStreamField("explored", String[].class), // NOI18N
0118: new ObjectStreamField("selected", Object[].class) // NOI18N
0119: };
0120:
0121: /** The support for VetoableChangeEvent */
0122: private transient VetoableChangeSupport vetoableSupport;
0123:
0124: /** The support for PropertyChangeEvent */
0125: private transient PropertyChangeSupport propertySupport;
0126:
0127: /** The current root context */
0128: private Node rootContext;
0129:
0130: /** The current explored context */
0131: private Node exploredContext;
0132:
0133: /** The currently selected beans */
0134: private Node[] selectedNodes;
0135:
0136: /** listener to destroy of root node */
0137: private transient Listener listener;
0138:
0139: /** weak listener */
0140: private transient NodeListener weakListener;
0141:
0142: /** Task that removes manages node selection issues.
0143: */
0144: private RequestProcessor.Task selectionSyncTask;
0145:
0146: /** Actions factory provided for this explorer manager */
0147: private ExplorerActionsImpl actions;
0148:
0149: /** Construct a new manager. */
0150: public ExplorerManager() {
0151: init();
0152: }
0153:
0154: /** Initializes the nodes.
0155: */
0156: private void init() {
0157: exploredContext = rootContext = Node.EMPTY;
0158: selectedNodes = new Node[0];
0159: listener = new Listener();
0160: weakListener = NodeOp.weakNodeListener(listener, null);
0161: }
0162:
0163: /** Clones the manager.
0164: * @return manager with the same settings like this one
0165: */
0166: @Override
0167: public ExplorerManager clone() {
0168: ExplorerManager em = new ExplorerManager();
0169: em.rootContext = rootContext;
0170: em.exploredContext = exploredContext;
0171: em.selectedNodes = selectedNodes;
0172:
0173: return em;
0174: }
0175:
0176: /** Get the set of selected nodes.
0177: * @return the selected nodes; empty (not <code>null</code>) if none are selected
0178: */
0179: public Node[] getSelectedNodes() {
0180: return selectedNodes;
0181: }
0182:
0183: // compare two arrays of nodes in respect to a path to root
0184: private boolean equalNodes(Node[] arr1, Node[] arr2) {
0185: // generic tests
0186: if (!Arrays.equals(arr1, arr2)) {
0187: return false;
0188: }
0189:
0190: if ((arr1 == null) || (arr1.length == 0)) {
0191: return true;
0192: }
0193:
0194: // compare paths from each node to root
0195: int i = 0;
0196:
0197: while ((i < arr1.length)
0198: && Arrays.equals(NodeOp.createPath(arr1[i], null),
0199: NodeOp.createPath(arr2[i], null))) {
0200: i++;
0201: }
0202:
0203: return i == arr1.length;
0204: }
0205:
0206: /** Set the set of selected nodes.
0207: * @param value the nodes to select; empty (not <code>null</code>) if none are to be selected
0208: * @exception PropertyVetoException when the given nodes cannot be selected
0209: * @throws IllegalArgumentException if <code>null</code> is given, or if any elements
0210: * of the selection are not within the current root context
0211: */
0212: public final void setSelectedNodes(final Node[] value)
0213: throws PropertyVetoException {
0214: class AtomicSetSelectedNodes implements Runnable {
0215: public PropertyVetoException veto;
0216: private boolean doFire;
0217: private Node[] oldValue;
0218:
0219: /** @return false if no further processing is needed */
0220: private boolean checkArgumentIsValid() {
0221: if (value == null) {
0222: throw new IllegalArgumentException(
0223: getString("EXC_NodeCannotBeNull"));
0224: }
0225:
0226: if (equalNodes(value, selectedNodes)) {
0227: return false;
0228: }
0229:
0230: for (int i = 0; i < value.length; i++) {
0231: if (value[i] == null) {
0232: throw new IllegalArgumentException(
0233: getString("EXC_NoElementOfNodeSelectionMayBeNull"));
0234: }
0235:
0236: checkUnderRoot(value[i],
0237: "EXC_NodeSelectionCannotContainNodes");
0238: }
0239:
0240: if ((value.length != 0) && (vetoableSupport != null)) {
0241: try {
0242: // we send the vetoable change event only for non-empty selections
0243: vetoableSupport.fireVetoableChange(
0244: PROP_SELECTED_NODES, selectedNodes,
0245: value);
0246: } catch (PropertyVetoException ex) {
0247: veto = ex;
0248:
0249: return false;
0250: }
0251: }
0252:
0253: return true;
0254: }
0255:
0256: private void updateSelection() {
0257: oldValue = selectedNodes;
0258: addRemoveListeners(false);
0259: selectedNodes = value;
0260: addRemoveListeners(true);
0261:
0262: doFire = true;
0263: }
0264:
0265: public void fire() {
0266: if (doFire) {
0267: fireInAWT(PROP_SELECTED_NODES, oldValue,
0268: selectedNodes);
0269: }
0270: }
0271:
0272: public void run() {
0273: if (checkArgumentIsValid()) {
0274: updateSelection();
0275: }
0276: }
0277: }
0278:
0279: AtomicSetSelectedNodes setNodes = new AtomicSetSelectedNodes();
0280: Children.MUTEX.readAccess(setNodes);
0281: setNodes.fire();
0282:
0283: if (setNodes.veto != null) {
0284: throw setNodes.veto;
0285: }
0286: }
0287:
0288: /** Get the explored context.
0289: * <p>The "explored context" is not as frequently used as the node selection;
0290: * generally it refers to a parent node which contains all of the things
0291: * being displayed at this moment. For <code>BeanTreeView</code> this is
0292: * irrelevant, but <code>ContextTreeView</code> uses it (in lieu of the node
0293: * selection) and for <code>IconView</code> it is important (the node
0294: * whose children are visible, i.e. the "background" of the icon view).
0295: * @return the node being explored, or <code>null</code>
0296: */
0297: public final Node getExploredContext() {
0298: return exploredContext;
0299: }
0300:
0301: /** Set the explored context.
0302: * The node selection will be cleared as well.
0303: * @param value the new node to explore, or <code>null</code> if none should be explored.
0304: * @throws IllegalArgumentException if the node is not within the current root context in the node hierarchy
0305: */
0306: public final void setExploredContext(Node value) {
0307: setExploredContext(value, new Node[0]);
0308: }
0309:
0310: /** Set the explored context.
0311: * The node selection will be changed as well. Note: node selection cannot be
0312: * vetoed if calling this method. It is generally better to call setExploredContextAndSelection.
0313: * @param value the new node to explore, or <code>null</code> if none should be explored.
0314: * @throws IllegalArgumentException if the node is not within the current root context in the node hierarchy
0315: */
0316: public final void setExploredContext(final Node value,
0317: final Node[] selection) {
0318: class SetExploredContext implements Runnable {
0319: boolean doFire;
0320: Node oldValue;
0321:
0322: public void run() {
0323: // handles nulls correctly:
0324: if (Utilities.compareObjects(value, exploredContext)) {
0325: setSelectedNodes0(selection);
0326:
0327: return;
0328: }
0329:
0330: checkUnderRoot(value,
0331: "EXC_ContextMustBeWithinRootContext");
0332: setSelectedNodes0(selection);
0333:
0334: oldValue = exploredContext;
0335: addRemoveListeners(false);
0336: exploredContext = value;
0337: addRemoveListeners(true);
0338:
0339: doFire = true;
0340: }
0341:
0342: public void fire() {
0343: if (doFire) {
0344: fireInAWT(PROP_EXPLORED_CONTEXT, oldValue, value);
0345: }
0346: }
0347: }
0348:
0349: SetExploredContext set = new SetExploredContext();
0350: Children.MUTEX.readAccess(set);
0351: set.fire();
0352: }
0353:
0354: /** Set the explored context and selected nodes. If the change in selected nodes is vetoed,
0355: * PropertyVetoException is rethrown from here.
0356: * @param value the new node to explore, or <code>null</code> if none should be explored.
0357: * @param selection the new nodes to be selected
0358: * @throws IllegalArgumentException if the node is not within the current root context in the node hierarchy
0359: * @throws PropertyVetoExcepion if listeners attached to this explorer manager do so
0360: */
0361: public final void setExploredContextAndSelection(final Node value,
0362: final Node[] selection) throws PropertyVetoException {
0363: class SetExploredContextAndSelection implements Runnable {
0364: public PropertyVetoException veto;
0365: private boolean doFire;
0366: private Object oldValue;
0367:
0368: public void run() {
0369: try {
0370: // handles nulls correctly:
0371: if (Utilities
0372: .compareObjects(value, exploredContext)) {
0373: setSelectedNodes(selection);
0374:
0375: return;
0376: }
0377:
0378: checkUnderRoot(value,
0379: "EXC_ContextMustBeWithinRootContext");
0380: setSelectedNodes(selection);
0381:
0382: oldValue = exploredContext;
0383: addRemoveListeners(false);
0384: exploredContext = value;
0385: addRemoveListeners(true);
0386:
0387: doFire = true;
0388: } catch (PropertyVetoException ex) {
0389: veto = ex;
0390: }
0391: }
0392:
0393: public void fire() {
0394: if (doFire) {
0395: fireInAWT(PROP_EXPLORED_CONTEXT, oldValue,
0396: exploredContext);
0397: }
0398: }
0399: }
0400:
0401: SetExploredContextAndSelection set = new SetExploredContextAndSelection();
0402: Children.MUTEX.readAccess(set);
0403: set.fire();
0404:
0405: if (set.veto != null) {
0406: throw set.veto;
0407: }
0408: }
0409:
0410: final void addRemoveListeners(boolean add) {
0411: Map<Node, Void> collect = new IdentityHashMap<Node, Void>(333);
0412:
0413: collectNodes(exploredContext, collect);
0414: for (Node n : selectedNodes) {
0415: collectNodes(n, collect);
0416: }
0417:
0418: for (Node n : collect.keySet()) {
0419: if (add) {
0420: n.addNodeListener(weakListener);
0421: } else {
0422: n.removeNodeListener(weakListener);
0423: }
0424: }
0425: }
0426:
0427: private final void collectNodes(Node n, Map<Node, ?> collect) {
0428: assert Children.MUTEX.isReadAccess();
0429: while (n != null && n != rootContext) {
0430: collect.put(n, null);
0431: n = n.getParentNode();
0432: }
0433: }
0434:
0435: /** Sets selected nodes and handles PropertyVetoException */
0436: final void setSelectedNodes0(Node[] nodes) {
0437: try {
0438: setSelectedNodes(nodes);
0439: } catch (PropertyVetoException e) {
0440: }
0441: }
0442:
0443: /** Get the root context.
0444: * <p>The "root context" is simply the topmost node that this explorer can
0445: * display or manipulate. For <code>BeanTreeView</code>, this would mean
0446: * the root node of the tree. For e.g. <code>IconView</code>, this would
0447: * mean the uppermost possible node that that icon view could display;
0448: * while the explored context would change at user prompting via the
0449: * up button and clicking on subfolders, the root context would be fixed
0450: * by the code displaying the explorer.
0451: * @return the root context node
0452: */
0453: public final Node getRootContext() {
0454: return rootContext;
0455: }
0456:
0457: /** Set the root context.
0458: * The explored context will be set to the new root context as well.
0459: * If any of the selected nodes are not inside it, the selection will be cleared.
0460: * @param value the new node to serve as a root
0461: * @throws IllegalArgumentException if it is <code>null</code>
0462: */
0463: public final void setRootContext(final Node value) {
0464: if (value == null) {
0465: throw new IllegalArgumentException(
0466: getString("EXC_CannotHaveNullRootContext"));
0467: }
0468:
0469: if (rootContext.equals(value)) {
0470: return;
0471: }
0472:
0473: class SetRootContext implements Runnable {
0474: public void run() {
0475: addRemoveListeners(false);
0476: Node oldValue = rootContext;
0477: rootContext = value;
0478:
0479: oldValue.removeNodeListener(weakListener);
0480: rootContext.addNodeListener(weakListener);
0481:
0482: fireInAWT(PROP_ROOT_CONTEXT, oldValue, rootContext);
0483:
0484: Node[] newselection = getSelectedNodes();
0485:
0486: if (!areUnderTarget(newselection, rootContext)) {
0487: newselection = new Node[0];
0488: }
0489: setExploredContext(rootContext, newselection);
0490: }
0491: }
0492:
0493: SetRootContext run = new SetRootContext();
0494: Children.MUTEX.readAccess(run);
0495: }
0496:
0497: /** @return true iff all nodes are under the target node */
0498: private boolean areUnderTarget(Node[] nodes, Node target) {
0499: bigloop: for (int i = 0; i < nodes.length; i++) {
0500: Node node = nodes[i];
0501:
0502: while (node != null) {
0503: if (node.equals(target)) {
0504: continue bigloop;
0505: }
0506:
0507: node = node.getParentNode();
0508: }
0509:
0510: return false;
0511: }
0512:
0513: return true;
0514: }
0515:
0516: /** Add a <code>PropertyChangeListener</code> to the listener list.
0517: * @param l the listener to add
0518: */
0519: public synchronized void addPropertyChangeListener(
0520: PropertyChangeListener l) {
0521: if (propertySupport == null) {
0522: propertySupport = new PropertyChangeSupport(this );
0523: }
0524:
0525: propertySupport.addPropertyChangeListener(l);
0526: }
0527:
0528: /** Remove a <code>PropertyChangeListener</code> from the listener list.
0529: * @param l the listener to remove
0530: */
0531: public synchronized void removePropertyChangeListener(
0532: PropertyChangeListener l) {
0533: if (propertySupport != null) {
0534: propertySupport.removePropertyChangeListener(l);
0535: }
0536: }
0537:
0538: /** Add a <code>VetoableListener</code> to the listener list.
0539: * @param l the listener to add
0540: */
0541: public synchronized void addVetoableChangeListener(
0542: VetoableChangeListener l) {
0543: if (vetoableSupport == null) {
0544: vetoableSupport = new VetoableChangeSupport(this );
0545: }
0546:
0547: vetoableSupport.addVetoableChangeListener(l);
0548: }
0549:
0550: /** Remove a <code>VetoableChangeListener</code> from the listener list.
0551: * @param l the listener to remove
0552: */
0553: public synchronized void removeVetoableChangeListener(
0554: VetoableChangeListener l) {
0555: if (vetoableSupport != null) {
0556: vetoableSupport.removeVetoableChangeListener(l);
0557: }
0558: }
0559:
0560: /** Checks whether given Node is a subnode of rootContext.
0561: * @return true if specified Node is under current rootContext
0562: */
0563: private boolean isUnderRoot(Node node) {
0564: while (node != null) {
0565: if (node.equals(rootContext)) {
0566: return true;
0567: }
0568:
0569: node = node.getParentNode();
0570: }
0571:
0572: return false;
0573: }
0574:
0575: /** Checks whether given Node is a subnode of rootContext.
0576: * and throws IllegalArgumentException if not.
0577: */
0578: private void checkUnderRoot(Node value, String errorKey) {
0579: if ((value != null) && !isUnderRoot(value)) {
0580: throw new IllegalArgumentException(NbBundle.getMessage(
0581: ExplorerManager.class, errorKey, value
0582: .getDisplayName(), rootContext
0583: .getDisplayName()));
0584: }
0585: }
0586:
0587: /** Waits till all async processing is finished
0588: */
0589: final void waitFinished() {
0590: if (selectionSyncTask != null) {
0591: selectionSyncTask.waitFinished();
0592: }
0593: }
0594:
0595: /** serializes object
0596: * @serialData the following objects are written in sequence:
0597: * <ol>
0598: * <li> a Node.Handle for the root context; may be null if root context
0599: * is not persistable
0600: * <li> the display name of the root context (to give nicer error messages
0601: * later on)
0602: * <li> the path from root context to explored context; null if no explored
0603: * context or no such path
0604: * <li> for every element of node selection, path from root context to that node;
0605: * null if no such path
0606: * <li> null to terminate
0607: * </ol>
0608: * Note that if the root context handle is null, the display name is still written
0609: * but the paths to explored context and node selection are not written, the stream
0610: * ends there.
0611: */
0612: private void writeObject(ObjectOutputStream os) throws IOException {
0613: // indication that we gonna use put fields and not the old method.
0614: os.writeObject(this );
0615:
0616: ObjectOutputStream.PutField fields = os.putFields();
0617:
0618: // [PENDING] is this method (and readObject) always called from within
0619: // the Nodes mutex? It should be!
0620: //System.err.println("rootContext: " + rootContext);
0621: Node.Handle rCH = rootContext.getHandle();
0622: fields.put("root", rCH); // NOI18N
0623:
0624: //System.err.println("writing: " + rCH);
0625: fields.put("rootName", rootContext.getDisplayName()); // NOI18N
0626:
0627: if (rCH != null) {
0628: // Note that explored context may be null (this is valid).
0629: // Also, it may have happened that the hierarchy changed so that
0630: // the explored context is *no longer* under the root (though it was at
0631: // the time these things were set up). In this case, we cannot store the
0632: // path. Caution: NodeOp.createPath will create a path to a root (parentless)
0633: // node even if you specify a non-null root, if the first arg is not a child!
0634: String[] explored;
0635:
0636: if (exploredContext == null) {
0637: explored = null;
0638: } else if (isUnderRoot(exploredContext)) {
0639: explored = NodeOp.createPath(exploredContext,
0640: rootContext);
0641: } else {
0642: explored = null;
0643: }
0644:
0645: fields.put("explored", explored); // NOI18N
0646:
0647: List<String[]> selected = new LinkedList<String[]>();
0648:
0649: for (int i = 0; i < selectedNodes.length; i++) {
0650: if (isUnderRoot(selectedNodes[i])) {
0651: selected.add(NodeOp.createPath(selectedNodes[i],
0652: rootContext));
0653: }
0654: }
0655:
0656: fields.put("selected", selected.toArray()); // NOI18N
0657: }
0658:
0659: os.writeFields();
0660: }
0661:
0662: /** Deserializes the view and initializes it
0663: * @serialData see writeObject
0664: */
0665: private void readObject(ObjectInputStream ois) throws IOException,
0666: ClassNotFoundException {
0667: // perform initialization
0668: init();
0669:
0670: // read the first object in the stream
0671: Object firstObject = ois.readObject();
0672:
0673: if (firstObject != this ) {
0674: // use old version of deserialization
0675: readObjectOld((Node.Handle) firstObject, ois);
0676:
0677: return;
0678: }
0679:
0680: // work with get fields
0681: ObjectInputStream.GetField fields = ois.readFields();
0682:
0683: // read root handle
0684: Node.Handle h = (Node.Handle) fields.get("root", null); // NOI18N
0685:
0686: //System.err.println("reading: " + h);
0687: final String rootName = (String) fields.get("rootName", null); // NOI18N
0688:
0689: //System.err.println("reading: " + rootName);
0690: if (h == null) {
0691: // Cancel deserialization (e.g. of the ExplorerPanel window) in case the
0692: // root handle was not persistent:
0693: throw new SafeException(
0694: new IOException(
0695: "Could not restore Explorer window; the root node \""
0696: + rootName
0697: + "\" is not persistent; override Node.getHandle to fix")); // NOI18N
0698: } else {
0699: String[] exploredCtx = (String[]) fields.get("explored",
0700: null); // NOI18N
0701: Object[] selPaths = (Object[]) fields.get("selected", null); // NOI18N
0702:
0703: try {
0704: Node root = h.getNode();
0705:
0706: if (root == null) {
0707: throw new IOException("Node.Handle.getNode (for "
0708: + rootName + ") should not return null"); // NOI18N
0709: }
0710:
0711: restoreSelection(root, exploredCtx, Arrays
0712: .asList(selPaths));
0713: } catch (IOException ioe) {
0714: SafeException safe = new SafeException(ioe);
0715:
0716: if (!Utilities.compareObjects(ioe.getMessage(), ioe
0717: .getLocalizedMessage())) {
0718: Exceptions.attachLocalizedMessage(safe, NbBundle
0719: .getMessage(ExplorerManager.class,
0720: "EXC_handle_failed", rootName));
0721: }
0722:
0723: throw safe;
0724: }
0725: }
0726: }
0727:
0728: private void readObjectOld(Node.Handle h, ObjectInputStream ois)
0729: throws java.io.IOException, ClassNotFoundException {
0730: if (h == null) {
0731: // do nothing => should not occur to often and moreover this is also
0732: // dead code replaced by new version
0733: return;
0734: } else {
0735: String[] rootCtx = (String[]) ois.readObject();
0736: String[] exploredCtx = (String[]) ois.readObject();
0737: List<String[]> ll = new LinkedList<String[]>();
0738:
0739: for (;;) {
0740: String[] path = (String[]) ois.readObject();
0741:
0742: if (path == null) {
0743: break;
0744: }
0745:
0746: ll.add(path);
0747: }
0748:
0749: Node root = findPath(h.getNode(), rootCtx);
0750: restoreSelection(root, exploredCtx, ll);
0751: }
0752: }
0753:
0754: private void restoreSelection(final Node root,
0755: final String[] exploredCtx,
0756: final List</*String[]*/?> selectedPaths) {
0757: setRootContext(root);
0758:
0759: // XXX(-ttran) findPath() can take a long time and employs DataSystems
0760: // and others. We cannot call it synchrorously, in the past deadlocks
0761: // have happened because of this. OTOH as we call setSelectedNodes
0762: // asynchonously someone else can change the root context or the Node
0763: // hierarchy in between, which causes setSelectedNodes to throw
0764: // IllegalArgumentException. There seems to be no simple good
0765: // solution. For now we just catch IllegalArgumentException and be
0766: // decently silent about the fact.
0767: //RequestProcessor.getDefault().post(new Runnable() {
0768: // bugfix #38207, select nodes has to be called in AWT queue here
0769: Mutex.EVENT.readAccess(new Runnable() {
0770: public void run() {
0771: // convert paths to Nodes
0772: List<Node> selNodes = new ArrayList<Node>(selectedPaths
0773: .size());
0774:
0775: for (Object path : selectedPaths) {
0776: selNodes.add(findPath(root, (String[]) path));
0777: }
0778:
0779: // set the selection
0780: Node[] newSelection = selNodes
0781: .toArray(new Node[selNodes.size()]);
0782:
0783: if (exploredCtx != null) {
0784: setExploredContext(findPath(root, exploredCtx),
0785: newSelection);
0786: } else {
0787: setSelectedNodes0(newSelection);
0788: }
0789: }
0790: });
0791: }
0792:
0793: /**
0794: * Finds the proper Explorer manager for a given component. This is done
0795: * by traversing the component hierarchy and finding the first ancestor
0796: * that implements {@link Provider}. <P> This method should be used in
0797: * {@link Component#addNotify} of each component that works with the
0798: * Explorer manager, e.g.:
0799: * <p><pre>
0800: * private transient ExplorerManager explorer;
0801: *
0802: * public void addNotify () {
0803: * super.addNotify ();
0804: * explorer = ExplorerManager.find (this);
0805: * }
0806: * </pre>
0807: *
0808: * @param comp component to find the manager for
0809: * @return the manager, or a new empty manager if no ancestor implements
0810: * <code>Provider</code>
0811: *
0812: * @see Provider
0813: */
0814: public static ExplorerManager find(Component comp) {
0815: // start looking for manager from parent, not the component itself
0816: for (;;) {
0817: comp = comp.getParent();
0818:
0819: if (comp == null) {
0820: // create new explorer because nothing has been found
0821: return new ExplorerManager();
0822: }
0823:
0824: if (comp instanceof Provider) {
0825: // ok, found a provider, return its manager
0826: return ((Provider) comp).getExplorerManager();
0827: }
0828: }
0829: }
0830:
0831: /** Finds node by given path */
0832: static Node findPath(Node r, String[] path) {
0833: try {
0834: return NodeOp.findPath(r, path);
0835: } catch (NodeNotFoundException ex) {
0836: return ex.getClosestNode();
0837: }
0838: }
0839:
0840: /** Creates or retrieves RequestProcessor for selection updates. */
0841: static synchronized RequestProcessor getSelectionProcessor() {
0842: if (selectionProcessor == null) {
0843: selectionProcessor = new RequestProcessor(
0844: "ExplorerManager-selection"); //NOI18N
0845: }
0846:
0847: return selectionProcessor;
0848: }
0849:
0850: /** Finds ExplorerActionsImpl for a explorer manager.
0851: * @param em the manager
0852: * @return ExplorerActionsImpl
0853: */
0854: static synchronized ExplorerActionsImpl findExplorerActionsImpl(
0855: ExplorerManager em) {
0856: if (em.actions == null) {
0857: em.actions = new ExplorerActionsImpl();
0858: em.actions.attach(em);
0859: }
0860:
0861: return em.actions;
0862: }
0863:
0864: final void fireInAWT(final String propName, final Object oldVal,
0865: final Object newVal) {
0866: if (propertySupport != null) {
0867: Mutex.EVENT.readAccess(new Runnable() {
0868: public void run() {
0869: propertySupport.firePropertyChange(propName,
0870: oldVal, newVal);
0871: }
0872: });
0873: }
0874: }
0875:
0876: private static String getString(String key) {
0877: return NbBundle.getMessage(ExplorerManager.class, key);
0878: }
0879:
0880: //
0881: // inner classes
0882: //
0883:
0884: /** Interface for components wishing to provide their own <code>ExplorerManager</code>.
0885: * @see ExplorerManager#find
0886: * @see ExplorerUtils
0887: */
0888: public static interface Provider {
0889: /** Get the explorer manager.
0890: * @return the manager
0891: */
0892: public ExplorerManager getExplorerManager();
0893: }
0894:
0895: /** Listener to be notified when root node has been destroyed.
0896: * Then the root node is changed to Node.EMPTY
0897: */
0898: private class Listener extends NodeAdapter implements Runnable {
0899: Collection<Node> removeList = new HashSet<Node>();
0900:
0901: Listener() {
0902: }
0903:
0904: /** Fired when the node is deleted.
0905: * @param ev event describing the node
0906: */
0907: @Override
0908: public void nodeDestroyed(NodeEvent ev) {
0909: if (ev.getNode().equals(getRootContext())) {
0910: // node has been deleted
0911: // [PENDING] better to show a node with a label such as "<deleted>"
0912: // and a tool tip explaining the situation
0913: setRootContext(Node.EMPTY);
0914: } else {
0915: // assume that the node is among currently selected nodes
0916: scheduleRemove(ev.getNode());
0917: }
0918: }
0919:
0920: /* Change in a node.
0921: * @param ev the event
0922: */
0923: @Override
0924: public void propertyChange(java.beans.PropertyChangeEvent ev) {
0925: fireInAWT(PROP_NODE_CHANGE, null, null);
0926: }
0927:
0928: /** Schedules removal of a node
0929: */
0930: private void scheduleRemove(Node n) {
0931: synchronized (ExplorerManager.this ) {
0932: if (selectionSyncTask == null) {
0933: selectionSyncTask = getSelectionProcessor().create(
0934: this );
0935: } else {
0936: selectionSyncTask.cancel();
0937: }
0938: }
0939:
0940: synchronized (this ) {
0941: removeList.add(n);
0942: }
0943:
0944: // invariant: selectionSyncTask != null && is not running yet.
0945: selectionSyncTask.schedule(SELECTION_SYNC_DELAY);
0946: }
0947:
0948: public void run() {
0949: if (!Children.MUTEX.isReadAccess()) {
0950: Children.MUTEX.readAccess(this );
0951:
0952: return;
0953: }
0954:
0955: Collection<Node> remove;
0956:
0957: synchronized (this ) {
0958: // atomically clears the list while keeping a copy.
0959: // if another node is removed after this point, the selection
0960: // will be updated later.
0961: remove = removeList;
0962: removeList = new HashSet<Node>();
0963: }
0964:
0965: if (!isUnderRoot(exploredContext)) {
0966: setExploredContext(rootContext);
0967: return;
0968: }
0969:
0970: LinkedList<Node> newSel = new LinkedList<Node>(Arrays
0971: .asList(getSelectedNodes()));
0972: Iterator<Node> it = remove.iterator();
0973:
0974: while (it.hasNext()) {
0975: Node n_remove = it.next();
0976:
0977: if (newSel.contains(n_remove)) {
0978: // compare paths to root
0979: Node n_selection = newSel.get(newSel
0980: .indexOf(n_remove));
0981:
0982: if (!Arrays.equals(NodeOp
0983: .createPath(n_remove, null), NodeOp
0984: .createPath(n_selection, null))) {
0985: it.remove();
0986: }
0987: }
0988: }
0989:
0990: newSel.removeAll(remove);
0991: for (Iterator<Node> i = newSel.iterator(); i.hasNext();) {
0992: Node n = i.next();
0993: if (!isUnderRoot(n))
0994: i.remove();
0995: }
0996:
0997: Node[] selNodes = newSel.toArray(new Node[newSel.size()]);
0998: setSelectedNodes0(selNodes);
0999:
1000: }
1001: }
1002: }
|