001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide.nodes;
042:
043: import java.io.IOException;
044: import java.util.ArrayList;
045: import java.util.Enumeration;
046: import java.util.HashMap;
047: import java.util.HashSet;
048: import java.util.LinkedList;
049: import java.util.List;
050: import java.util.Map;
051: import java.util.Set;
052: import java.util.logging.Level;
053: import java.util.logging.Logger;
054: import javax.swing.Action;
055: import javax.swing.ActionMap;
056: import javax.swing.JPopupMenu;
057: import org.openide.util.Enumerations;
058: import org.openide.util.Lookup;
059: import org.openide.util.Utilities;
060: import org.openide.util.WeakListeners;
061: import org.openide.util.actions.SystemAction;
062: import org.openide.util.lookup.Lookups;
063: import org.openide.util.lookup.ProxyLookup;
064:
065: /** Utility class for operations on nodes.
066: *
067: * @author Jaroslav Tulach, Petr Hamernik, Dafe Simonek
068: */
069: public final class NodeOp extends Object {
070: /** default node actions */
071: private static SystemAction[] defaultActions;
072:
073: private NodeOp() {
074: }
075:
076: /** Get the default actions for all nodes.
077: * @return array of default actions
078: * @deprecated Do not use this method. It is useless now.
079: */
080: @Deprecated
081: public static SystemAction[] getDefaultActions() {
082: if (defaultActions == null) {
083: defaultActions = createFromNames(new String[] { "Tools",
084: "Properties" }); // NOI18N
085: }
086:
087: return defaultActions;
088: }
089:
090: /** @deprecated Useless. */
091: @Deprecated
092: public static void setDefaultActions(SystemAction[] def) {
093: throw new SecurityException();
094: }
095:
096: /** Compute common menu for specified nodes.
097: * Provides only those actions supplied by all nodes in the list.
098: * @param nodes the nodes
099: * @return the menu for all nodes
100: */
101: public static JPopupMenu findContextMenu(Node[] nodes) {
102: return findContextMenuImpl(nodes, null);
103: }
104:
105: /** Method for finding popup menu for one or more nodes.
106: *
107: * @param nodes array of nodes
108: * @param actionMap maps keys to actions or null
109: * @return popup menu for this array
110: */
111: static JPopupMenu findContextMenuImpl(Node[] nodes,
112: ActionMap actionMap) {
113: Action[] arr = findActions(nodes);
114:
115: // prepare lookup representing all the selected nodes
116: List<Lookup> allLookups = new ArrayList<Lookup>();
117:
118: for (Node n : nodes) {
119: allLookups.add(n.getLookup());
120: }
121:
122: if (actionMap != null) {
123: allLookups.add(Lookups.singleton(actionMap));
124: }
125:
126: Lookup lookup = new ProxyLookup(allLookups
127: .toArray(new Lookup[allLookups.size()]));
128:
129: return Utilities.actionsToPopup(arr, lookup);
130: }
131:
132: /** Asks the provided nodes for their actions and those that are common,
133: * to all of them returns.
134: *
135: * @param nodes array of nodes to compose actions for
136: * @return array of actions for the nodes or empty array if no actions
137: * were found
138: * @since 3.29
139: */
140: public static Action[] findActions(Node[] nodes) {
141: Map<Action, Integer> actions = new HashMap<Action, Integer>();
142:
143: Action[][] actionsByNode = new Action[nodes.length][];
144:
145: // counts the number of occurences for each action
146: for (int n = 0; n < nodes.length; n++) {
147: actionsByNode[n] = nodes[n].getActions(false);
148:
149: if (actionsByNode[n] == null) {
150: // XXX is this permitted by the API?!
151: // use default actions
152: actionsByNode[n] = defaultActions;
153: }
154:
155: // keeps actions handled for this node iteration
156: Set<Action> counted = new HashSet<Action>();
157:
158: for (Action a : actionsByNode[n]) {
159: if (a != null) {
160: // if this action was handled for this node already, skip to next iteration
161: if (counted.contains(a)) {
162: continue;
163: }
164:
165: counted.add(a);
166:
167: Integer cntInt = actions.get(a);
168: actions.put(a, cntInt == null ? 1 : cntInt + 1);
169: }
170: }
171: }
172:
173: // take all actions that are nodes.length number times
174: if (!actions.isEmpty()) {
175: // keeps actions for which was menu item created already
176: List<Action> result = new ArrayList<Action>();
177: Set<Action> counted = new HashSet<Action>();
178:
179: for (Action action : actionsByNode[0]) {
180:
181: if (action != null) {
182: // if this action has menu item already, skip to next iteration
183: if (counted.contains(action)) {
184: continue;
185: }
186:
187: counted.add(action);
188:
189: Integer cntInt = actions.get(action);
190:
191: int cnt = (cntInt == null) ? 0 : cntInt;
192:
193: if (cnt == nodes.length) {
194: result.add(action);
195: }
196: } else {
197: // place a separator there
198: result.add(null);
199: }
200: }
201:
202: return result.toArray(new Action[result.size()]);
203: } else {
204: // no available actions
205: return new Action[0];
206: }
207: }
208:
209: /** Test whether the second node is a (direct) child of the first one.
210: * @param parent parent node
211: * @param son son node
212: * @return <code>true</code> if so
213: */
214: public static boolean isSon(Node parent, Node son) {
215: return son.getParentNode() == parent;
216: }
217:
218: /** Find a path (by name) from one node to the root or a parent.
219: * @param node the node to start in
220: * @param parent parent node to stop in (can be <code>null</code> for the root)
221: * @return list of child names--i.e. a path from the parent to the child node
222: * @exception IllegalArgumentException if <code>node</code>'s getName()
223: * method returns <code>null</code>
224: */
225: public static String[] createPath(Node node, Node parent) {
226: LinkedList<String> ar = new LinkedList<String>();
227:
228: while ((node != null) && (node != parent)) {
229: if (node.getName() == null) {
230: boolean isFilter = false;
231:
232: if (node instanceof FilterNode) {
233: isFilter = true;
234: }
235:
236: throw new IllegalArgumentException(
237: "Node:"
238: + node.getClass() // NOI18N
239: + "["
240: + node.getDisplayName()
241: + "]" // NOI18N
242: + (isFilter ? (" of original:" + ((FilterNode) node)
243: .getOriginal().getClass())
244: : "") // NOI18N
245: + " gets null name!"); // NOI18N
246: }
247:
248: ar.addFirst(node.getName());
249: node = node.getParentNode();
250: }
251:
252: String[] res = new String[ar.size()];
253: ar.toArray(res);
254:
255: return res;
256: }
257:
258: /** Look for a node child of given name.
259: * @param node node to search in
260: * @param name name of child to look for
261: * @return the found child, or <code>null</code> if there is no such child
262: */
263: public static Node findChild(Node node, String name) {
264: return node.getChildren().findChild(name);
265: }
266:
267: /** Traverse a path from a parent node down, by an enumeration of names.
268: * @param start node to start searching at
269: * @param names enumeration of names of nodes
270: * along the path
271: * @return the node with such a path from the start node
272: * @exception NodeNotFoundException if the node with such name
273: * does not exists; the exception contains additional information
274: * about the failure.
275: */
276: public static Node findPath(Node start, Enumeration<String> names)
277: throws NodeNotFoundException {
278: int depth = 0;
279:
280: while (names.hasMoreElements()) {
281: String name = names.nextElement();
282: Node next = findChild(start, name);
283:
284: if (next == null) {
285: // no element in list matched the name => fail
286: // fire exception with the last accessed node and the
287: // name of child that does not exists
288: throw new NodeNotFoundException(start, name, depth);
289: } else {
290: // go on next node
291: start = next;
292: }
293:
294: // continue on next depth
295: depth++;
296: }
297:
298: return start;
299: }
300:
301: /** Traverse a path from a parent node down, by an enumeration of names.
302: * @param start node to start searching at
303: * @param names names of nodes
304: * along the path
305: * @return the node with such a path from the start node
306: * @exception NodeNotFoundException if the node with such name
307: * does not exists; the exception contains additional information
308: * about the failure.
309: */
310: public static Node findPath(Node start, String[] names)
311: throws NodeNotFoundException {
312: return findPath(start, Enumerations.array(names));
313: }
314:
315: /** Find the root for a given node.
316: * @param node the node
317: * @return its root
318: */
319: public static Node findRoot(Node node) {
320: for (;;) {
321: Node parent = node.getParentNode();
322:
323: if (parent == null) {
324: return node;
325: }
326:
327: node = parent;
328: }
329: }
330:
331: /** Compute a permutation between two arrays of nodes. The arrays
332: * must have the same size. The permutation then can be
333: * applied to the first array to create the
334: * second array.
335: *
336: * @param arr1 first array
337: * @param arr2 second array
338: * @return the permutation, or <code>null</code> if the arrays are the same
339: * @exception IllegalArgumentException if the arrays cannot be permuted to each other. Either
340: * they have different sizes or they do not contain the same elements.
341: */
342: public static int[] computePermutation(Node[] arr1, Node[] arr2)
343: throws IllegalArgumentException {
344: if (arr1.length != arr2.length) {
345: int max = Math.max(arr1.length, arr2.length);
346: StringBuffer sb = new StringBuffer();
347:
348: for (int i = 0; i < max; i++) {
349: sb.append(i + " "); // NOI18N
350:
351: if (i < arr1.length) {
352: sb.append(arr1[i].getName());
353: } else {
354: sb.append("---"); // NOI18N
355: }
356:
357: sb.append(" = "); // NOI18N
358:
359: if (i < arr2.length) {
360: sb.append(arr2[i].getName());
361: } else {
362: sb.append("---"); // NOI18N
363: }
364:
365: sb.append('\n');
366: }
367:
368: throw new IllegalArgumentException(sb.toString());
369: }
370:
371: // creates map that assignes to nodes their original
372: // position
373: Map<Node, Integer> map = new HashMap<Node, Integer>();
374:
375: for (int i = 0; i < arr2.length; i++) {
376: map.put(arr2[i], i);
377: }
378:
379: // takes nodes one by one in the new order and
380: // creates permutation array
381: int[] perm = new int[arr1.length];
382: int diff = 0;
383:
384: for (int i = 0; i < arr1.length; i++) {
385: // get the position of the i-th argument in the second array
386: Integer newPos = map.get(arr1[i]);
387:
388: if (newPos == null) {
389: // not permutation i-th element is missing in the array
390: throw new IllegalArgumentException(
391: "Missing permutation index " + i); // NOI18N
392: }
393:
394: // perm must move the object to the newPos
395: perm[i] = newPos;
396:
397: if (perm[i] != i) {
398: diff++;
399: }
400: }
401:
402: return (diff == 0) ? null : perm;
403: }
404:
405: /** Takes array of nodes and creates array of handles. The nodes that do not
406: * have handles are not included in the resulting array.
407: *
408: * @param nodes array of nodes
409: * @return array of Node.Handles
410: */
411: public static Node.Handle[] toHandles(Node[] nodes) {
412: List<Node.Handle> ll = new LinkedList<Node.Handle>();
413:
414: for (Node n : nodes) {
415: Node.Handle h = n.getHandle();
416:
417: if (h != null) {
418: ll.add(h);
419: }
420: }
421:
422: return ll.toArray(new Node.Handle[ll.size()]);
423: }
424:
425: /** Takes array of handles and creates array of nodes.
426: * @param handles array of handles
427: * @return array of nodes
428: * @exception IOException if a node cannot be created from the handle
429: */
430: public static Node[] fromHandles(Node.Handle[] handles)
431: throws IOException {
432: Node[] arr = new Node[handles.length];
433:
434: for (int i = 0; i < handles.length; i++) {
435: arr[i] = handles[i].getNode();
436: }
437:
438: return arr;
439: }
440:
441: /** Creates a weak implementation of NodeListener.
442: *
443: * @param l the listener to delegate to
444: * @param source the source that the listener should detach from when
445: * listener <CODE>l</CODE> is freed, can be <CODE>null</CODE>
446: * @return a NodeListener delegating to <CODE>l</CODE>.
447: * @since 4.10
448: */
449: public static NodeListener weakNodeListener(NodeListener l,
450: Object source) {
451: return WeakListeners.create(NodeListener.class, l, source);
452: }
453:
454: /** Utility method to remove dependency of this package on
455: * org.openide.actions. This method takes names of classes from
456: * that package and creates their instances.
457: *
458: * @param arr the array of names like "Tools", "Properties", etc. can
459: * contain nulls
460: */
461: static SystemAction[] createFromNames(String[] arr) {
462: List<SystemAction> ll = new LinkedList<SystemAction>();
463:
464: for (String n : arr) {
465: if (n == null) {
466: ll.add(null);
467:
468: continue;
469: }
470:
471: String name = "org.openide.actions." + n + "Action"; // NOI18N
472:
473: try {
474: ClassLoader l = Lookup.getDefault().lookup(
475: ClassLoader.class);
476: if (l == null) {
477: l = Thread.currentThread().getContextClassLoader();
478: }
479: if (l == null) {
480: l = NodeOp.class.getClassLoader();
481: }
482: Class<? extends SystemAction> c = Class.forName(name,
483: true, l).asSubclass(SystemAction.class);
484: ll.add(SystemAction.get(c));
485: } catch (ClassNotFoundException ex) {
486: Logger.getAnonymousLogger().log(Level.WARNING,
487: "NodeOp.java: Missing class " + name, ex); // NOI18N
488:
489: // otherwise it is probably ok, that the class is missing
490: }
491: }
492:
493: return ll.toArray(new SystemAction[ll.size()]);
494: }
495:
496: /** Notifies an exception to error manager or prints its it to stderr.
497: * @param ex exception to notify
498: */
499: static void exception(Throwable ex) {
500: Logger.getLogger(NodeOp.class.getName()).log(Level.WARNING,
501: null, ex);
502: }
503:
504: /** Notifies an exception to error manager or prints its it to stderr.
505: * @param ex exception to notify
506: */
507: static void warning(Throwable ex) {
508: Logger.getLogger(NodeOp.class.getName()).log(Level.WARNING,
509: null, ex);
510: }
511: }
|