/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.nodes;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeMap;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Enumeration;
import javax.swing.Action;
import javax.swing.JPopupMenu;
import org.openide.ErrorManager;
import org.openide.util.enums.ArrayEnumeration;
import org.openide.util.Lookup;
/** Utility class for operations on nodes.
*
* @author Jaroslav Tulach, Petr Hamernik, Dafe Simonek
*/
public final class NodeOp extends Object {
private NodeOp() {}
/** default node actions */
private static org.openide.util.actions.SystemAction[] defaultActions;
/** Get the default actions for all nodes.
* @return array of default actions
* @deprecated Do not use this method. It is useless now.
*/
public static org.openide.util.actions.SystemAction[] getDefaultActions () {
if (defaultActions == null) {
defaultActions = createFromNames (new String [] {
"Tools", "Properties" // NOI18N
});
}
return defaultActions;
}
/** @deprecated Useless. */
public static void setDefaultActions (org.openide.util.actions.SystemAction[] def) {
throw new SecurityException ();
}
/** Compute common menu for specified nodes.
* Provides only those actions supplied by all nodes in the list.
* @param nodes the nodes
* @return the menu for all nodes
*/
public static JPopupMenu findContextMenu (Node[] nodes) {
return findContextMenuImpl (nodes, null);
}
/** Method for finding popup menu for one or more nodes.
*
* @param nodes array of nodes
* @param actionMap maps keys to actions or null
* @return popup menu for this array
*/
static JPopupMenu findContextMenuImpl (Node[] nodes, javax.swing.ActionMap actionMap) {
Action[] arr = findActions (nodes);
// prepare lookup representing all the selected nodes
ArrayList allLookups = new ArrayList ();
for (int i = 0; i < nodes.length; i++) {
allLookups.add (nodes[i].getLookup ());
}
if (actionMap != null) {
allLookups.add (org.openide.util.lookup.Lookups.singleton(actionMap));
}
Lookup lookup = new org.openide.util.lookup.ProxyLookup (
(Lookup[])allLookups.toArray (new Lookup[0])
);
return org.openide.util.Utilities.actionsToPopup(arr, lookup);
}
/** Asks the provided nodes for their actions and those that are common,
* to all of them returns.
*
* @param nodes array of nodes to compose actions for
* @return array of actions for the nodes or empty array if no actions
* were found
* @since 3.29
*/
public static Action[] findActions (Node[] nodes) {
// hashtable: SystemAction -> Integer
HashMap actions = new HashMap ();
// counts the number of occurences for each action
for (int n = 0; n < nodes.length; n++) {
Action[] arr = nodes[n].getActions (false);
if (arr == null) {
// use default actions
arr = defaultActions;
}
// keeps actions handled for this node iteration
HashSet counted = new HashSet ();
for (int i = 0; i < arr.length; i++) {
if (arr[i] != null) {
// if this action was handled for this node already, skip to next iteration
if (counted.contains (arr[i]))
continue;
counted.add (arr[i]);
Integer cntInt = (Integer)actions.get (arr[i]);
int cnt = cntInt == null ? 0 : cntInt.intValue ();
actions.put (arr[i], new Integer (cnt + 1));
}
}
}
// take all actions that are nodes.length number times
if (!actions.isEmpty ()) {
Action[] arr = nodes[0].getActions (false);
if (arr == null) {
// use default
arr = defaultActions;
}
// keeps actions for which was menu item created already
ArrayList result = new ArrayList ();
HashSet counted = new HashSet ();
for (int i = 0; i < arr.length; i++) {
Action action = arr[i];
if (action != null) {
// if this action has menu item already, skip to next iteration
if (counted.contains (action))
continue;
counted.add (action);
Integer cntInt = (Integer)actions.get (action);
int cnt = cntInt == null ? 0 : cntInt.intValue ();
if (cnt == nodes.length) {
result.add (action);
}
} else {
// place a separator there
result.add (null);
}
}
return (Action[])result.toArray(new Action[0]);
} else {
// no available actions
return new Action[0];
}
}
/** Test whether the second node is a (direct) child of the first one.
* @param parent parent node
* @param son son node
* @return <code>true</code> if so
*/
public static boolean isSon (Node parent, Node son) {
return son.getParentNode () == parent;
}
/** Find a path (by name) from one node to the root or a parent.
* @param node the node to start in
* @param parent parent node to stop in (can be <code>null</code> for the root)
* @return list of child names--i.e. a path from the parent to the child node
* @exception IllegalArgumentException if <code>node</code>'s getName()
* method returns <code>null</code>
*/
public static String[] createPath (Node node, Node parent) {
LinkedList ar = new LinkedList ();
while (node != null && node != parent) {
if (node.getName() == null) {
boolean isFilter = false;
if(node instanceof FilterNode) {
isFilter = true;
}
throw new IllegalArgumentException("Node:" + node.getClass() // NOI18N
+ "[" + node.getDisplayName() +"]" // NOI18N
+ (isFilter ? (" of original:" + ((FilterNode)node).getOriginal().getClass()) : "") // NOI18N
+ " gets null name!"); // NOI18N
}
ar.addFirst (node.getName ());
node = node.getParentNode ();
}
String[] res = new String [ar.size ()];
ar.toArray (res);
return res;
}
/** Look for a node child of given name.
* @param node node to search in
* @param name name of child to look for
* @return the found child, or <code>null</code> if there is no such child
*/
public static Node findChild (Node node, String name) {
return node.getChildren ().findChild (name);
}
/** Traverse a path from a parent node down, by an enumeration of names.
* @param start node to start searching at
* @param names enumeration of <code>String</code>s containing names of nodes
* along the path
* @return the node with such a path from the start node
* @exception NodeNotFoundException if the node with such name
* does not exists; the exception contains additional information
* about the failure.
*/
public static Node findPath (Node start, Enumeration names)
throws NodeNotFoundException {
int depth = 0;
while (names.hasMoreElements ()) {
String name = (String)names.nextElement ();
Node next = findChild (start, name);
if (next == null) {
// no element in list matched the name => fail
// fire exception with the last accessed node and the
// name of child that does not exists
throw new NodeNotFoundException (start, name, depth);
} else {
// go on next node
start = next;
}
// continue on next depth
depth++;
}
return start;
}
/** Traverse a path from a parent node down, by an enumeration of names.
* @param start node to start searching at
* @param names names of nodes
* along the path
* @return the node with such a path from the start node
* @exception NodeNotFoundException if the node with such name
* does not exists; the exception contains additional information
* about the failure.
*/
public static Node findPath (Node start, String[] names)
throws NodeNotFoundException {
return findPath (start, new ArrayEnumeration (names));
}
/** Find the root for a given node.
* @param node the node
* @return its root
*/
public static Node findRoot (Node node) {
for (;;) {
Node parent = node.getParentNode ();
if (parent == null) return node;
node = parent;
}
}
/** Compute a permutation between two arrays of nodes. The arrays
* must have the same size. The permutation then can be
* applied to the first array to create the
* second array.
*
* @param arr1 first array
* @param arr2 second array
* @return the permutation, or <code>null</code> if the arrays are the same
* @exception IllegalArgumentException if the arrays cannot be permuted to each other. Either
* they have different sizes or they do not contain the same elements.
*/
public static int[] computePermutation (Node[] arr1, Node[] arr2)
throws IllegalArgumentException {
if (arr1.length != arr2.length) {
int max = Math.max (arr1.length, arr2.length);
StringBuffer sb = new StringBuffer ();
for (int i = 0; i < max; i++) {
sb.append (i + " "); // NOI18N
if (i < arr1.length) {
sb.append (arr1[i].getName ());
} else {
sb.append ("---"); // NOI18N
}
sb.append (" = "); // NOI18N
if (i < arr2.length) {
sb.append (arr2[i].getName ());
} else {
sb.append ("---"); // NOI18N
}
sb.append ('\n');
}
throw new IllegalArgumentException (sb.toString ());
}
// creates map that assignes to nodes their original
// position
HashMap map = new HashMap ();
for (int i = 0; i < arr2.length; i++) {
map.put (arr2[i], new Integer (i));
}
// takes nodes one by one in the new order and
// creates permutation array
int[] perm = new int[arr1.length];
int diff = 0;
for (int i = 0; i < arr1.length; i++) {
// get the position of the i-th argument in the second array
Integer newPos = (Integer)map.get (arr1[i]);
if (newPos == null) {
// not permutation i-th element is missing in the array
throw new IllegalArgumentException ("Missing permutation index " + i); // NOI18N
}
// perm must move the object to the newPos
perm[i] = newPos.intValue ();
if (perm[i] != i) {
diff++;
}
}
return diff == 0 ? null : perm;
}
/** Takes array of nodes and creates array of handles. The nodes that do not
* have handles are not included in the resulting array.
*
* @param nodes array of nodes
* @return array of Node.Handles
*/
public static Node.Handle[] toHandles (Node[] nodes) {
LinkedList ll = new LinkedList ();
for (int i = 0; i < nodes.length; i++) {
Node.Handle h = nodes[i].getHandle();
if (h != null) {
ll.add (h);
}
}
return (Node.Handle[])ll.toArray (new Node.Handle[ll.size ()]);
}
/** Takes array of handles and creates array of nodes.
* @param handles array of handles
* @return array of nodes
* @exception IOException if a node cannot be created from the handle
*/
public static Node[] fromHandles (Node.Handle[] handles)
throws java.io.IOException {
Node[] arr = new Node[handles.length];
for (int i = 0; i < handles.length; i++) {
arr[i] = handles[i].getNode ();
}
return arr;
}
/** Utility method to remove dependency of this package on
* org.openide.actions. This method takes names of classes from
* that package and creates their instances.
*
* @param arr the array of names like "Tools", "Properties", etc. can
* contain nulls
*/
static org.openide.util.actions.SystemAction[] createFromNames (String[] arr) {
ErrorManager err = (ErrorManager)
org.openide.util.Lookup.getDefault ().lookup (ErrorManager.class);
LinkedList ll = new LinkedList ();
for (int i = 0; i < arr.length; i++) {
if (arr[i] == null) {
ll.add (null);
continue;
}
String name = "org.openide.actions." + arr[i] + "Action"; // NOI18N
try {
Class c = Class.forName (name);
ll.add (org.openide.util.actions.SystemAction.get (c));
} catch (ClassNotFoundException ex) {
if (err != null) {
err.log (err.INFORMATIONAL, "NodeOp.java: Missing class " + name); // NOI18N
}
// otherwise it is probably ok, that the class is missing
}
}
return (org.openide.util.actions.SystemAction[])ll.toArray (new org.openide.util.actions.SystemAction[ll.size ()]);
}
/** Notifies an exception to error manager or prints its it to stderr.
* @param ex exception to notify
*/
static void exception (Throwable ex) {
ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex);
}
/** Notifies an exception to error manager or prints its it to stderr.
* @param ex exception to notify
*/
static void warning (Throwable ex) {
ErrorManager.getDefault ().notify (ErrorManager.WARNING, ex);
}
}