/* * 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-2001 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.nodes; import java.util.*; import javax.swing.event.ChangeListener; import javax.swing.event.ChangeEvent; /** Index cookie providing operations useful for reordering * child nodes. {@link IndexedNode} is the common implementation. * * @author Jaroslav Tulach, Dafe Simonek */ public interface Index extends Node.Cookie { /** Get the number of nodes. * @return the count */ public int getNodesCount (); /** Get the child nodes. * @return array of nodes that can be sorted by this index */ public Node[] getNodes (); /** Get the index of a given node. * @param node node to find index of * @return index of the node, or <code>-1</code> if no such node was found */ public int indexOf (final Node node); /** Invoke a dialog for reordering the children. */ public void reorder (); /** Reorder all children with a given permutation. * @param perm permutation with the length of current nodes * @exception IllegalArgumentException if the permutation is not valid */ public void reorder (int[] perm); /** Move the element at the <code>x</code>-th position to the <code>y</code>-th position. All * elements after the <code>y</code>-th position are moved down. * * @param x the position to remove the element from * @param y the position to insert the element to * @exception IndexOutOfBoundsException if an index is out of bounds */ public void move (int x, int y); /** Exchange two elements. * @param x position of the first element * @param y position of the second element * @exception IndexOutOfBoundsException if an index is out of bounds */ public void exchange (int x, int y); /** Move an element up. * @param x index of element to move up * @exception IndexOutOfBoundsException if an index is out of bounds */ public void moveUp (int x); /** Move an element down. * @param x index of element to move down * @exception IndexOutOfBoundsException if an index is out of bounds */ public void moveDown (int x); /** Add a new listener to the listener list. The listener will be notified of * any change in the order of the nodes. * * @param chl new listener */ public void addChangeListener (final ChangeListener chl); /** Remove a listener from the listener list. * * @param chl listener to remove */ public void removeChangeListener (final ChangeListener chl); /*********************** Inner classes ***********************/ /** A support class implementing some methods of the <code>Index</code> * cookie. */ public static abstract class Support implements Index { /** Registered listeners */ private HashSet listeners; /** Default constructor. */ public Support () { } /* Moves element at x-th position to y-th position. All * elements after the y-th position are moved down. * * @param x the position to remove the element from * @param y the position to insert the element to * @exception IndexOutOfBoundsException if an index is out of bounds */ public void move (final int x, final int y) { int[] perm = new int[getNodesCount()]; // if the positions are the same then no move if (x==y) return ; for (int i = 0; i < perm.length; i++) { if ((i<x && i<y) || (i>x && i>y)) { // this area w/o change perm[i] = i; } else { if (i>x && i<y) { // i-th element moves backward perm[i] = i-1; } else { // i-th element moves forward perm[i] = i+1; } } } // set new positions for the elemets on x-th and y-th position perm[x] = y; if (x<y) { perm[y] = y-1; } else { perm[y] = y+1; } reorder (perm); } /* Exchanges two elements. * @param x position of the first element * @param y position of the second element * @exception IndexOutOfBoundsException if an index is out of bounds */ public void exchange (final int x, final int y) { int[] perm = new int[getNodesCount ()]; for (int i = 0; i < perm.length; i++) { perm[i] = i; } perm[x] = y; perm[y] = x; reorder (perm); } /* Moves element up. * @param x index of element to move up * @exception IndexOutOfBoundsException if an index is out of bounds */ public void moveUp (final int x) { exchange (x, x - 1); } /* Moves element down. * @param x index of element to move down * @exception IndexOutOfBoundsException if an index is out of bounds */ public void moveDown (final int x) { exchange (x, x + 1); } /* Adds new listener to the listener list. Listener is notified of * any change in ordering of nodes. * * @param chl new listener */ public void addChangeListener (final ChangeListener chl) { if (listeners == null) listeners = new HashSet(); listeners.add(chl); } /* Removes listener from the listener list. * Removed listener isn't notified no more. * * @param chl listener to remove */ public void removeChangeListener (final ChangeListener chl) { if (listeners == null) return; listeners.remove(chl); } /** Fires notification about reordering to all * registered listeners. * * @param che change event to fire off */ protected void fireChangeEvent (ChangeEvent che) { if (listeners == null) return; HashSet cloned; // clone listener list synchronized (this) { cloned = (HashSet)listeners.clone(); } // fire on cloned list to prevent from modifications when firing for (Iterator iter = cloned.iterator(); iter.hasNext(); ) { ((ChangeListener)iter.next()).stateChanged(che); } } /** Get the nodes; should be overridden if needed. * @return the nodes */ public abstract Node[] getNodes (); /** Get the index of a node. Simply scans through the array returned by {@link #getNodes}. * @param node the node * @return the index, or <code>-1</code> if the node was not found */ public int indexOf (final Node node) { Node[] arr = getNodes (); for (int i = 0; i < arr.length; i++) { if (node.equals (arr[i])) { return i; } } return -1; } /** Reorder the nodes with dialog; should be overridden if needed. */ public void reorder () { showIndexedCustomizer(this); } /** Utility method to create and show an indexed customizer. * Displays some sort of dialog permitting the index cookie to be reordered. * Blocks until the reordering is performed (or cancelled). * @param idx the index cookie to reorder based on * @since 1.37 */ public static void showIndexedCustomizer(Index idx) { TMUtil.showIndexedCustomizer(idx); } /** Get the node count. Subclasses must provide this. * @return the count */ public abstract int getNodesCount (); /** Reorder by permutation. Subclasses must provide this. * @param perm the permutation */ public abstract void reorder (int[] perm); } // end of Support inner class /** Reorderable children list stored in an array. */ public static class ArrayChildren extends Children.Array implements Index { /** Support instance for delegation of some <code>Index</code> methods. */ protected Index support; /** Constructor for the support. */ public ArrayChildren () { this (null); } /** Constructor. * @param ar the array */ private ArrayChildren (List ar) { super (ar); // create support instance for delegation of common tasks support = new Support() { public Node[] getNodes () { return ArrayChildren.this.getNodes (); } public int getNodesCount () { return ArrayChildren.this.getNodesCount(); } public void reorder (int[] perm) { ArrayChildren.this.reorder(perm); fireChangeEvent(new ChangeEvent(ArrayChildren.this)); } }; } /** If default constructor is used, then this method is called to lazily create * the collection. Even it claims that it returns Collection only subclasses * of List are valid values. * <P> * This implementation returns ArrayList. * * @return any List collection. */ protected java.util.Collection initCollection () { return new ArrayList (); } /* Reorders all children with given permutation. * @param perm permutation with the length of current nodes * @exception IllegalArgumentException if the perm is not valid permutation */ public void reorder (final int[] perm) { try { PR.enterWriteAccess (); Object[] n = nodes.toArray (); List l = (List)nodes; for (int i = 0; i < n.length; i++) { l.set (perm[i], n[i]); } refresh (); } finally { PR.exitWriteAccess (); } } /** Invokes a dialog for reordering children using {@link IndexedCustomizer}. */ public void reorder () { try { PR.enterReadAccess (); Support.showIndexedCustomizer(this); } finally { PR.exitReadAccess (); } } /* Moves element at x-th position to y-th position. All * elements after the y-th position are moved down. * Delegates functionality to Index.Support. * * @param x the position to remove the element from * @param y the position to insert the element to * @exception IndexOutOfBoundsException if an index is out of bounds */ public void move (final int x, final int y) { support.move(x, y); } /* Exchanges two elements. * Delegates functionality to Index.Support. * @param x position of the first element * @param y position of the second element * @exception IndexOutOfBoundsException if an index is out of bounds */ public void exchange (final int x, final int y) { support.exchange(x, y); } /* Moves element up. * Delegates functionality to Index.Support. * @param x index of element to move up * @exception IndexOutOfBoundsException if an index is out of bounds */ public void moveUp (final int x) { support.exchange(x, x - 1); } /* Moves element down. * Delegates functionality to Index.Support. * @param x index of element to move down * @exception IndexOutOfBoundsException if an index is out of bounds */ public void moveDown (final int x) { support.exchange(x, x + 1); } /* Returns the index of given node. * @param node Node to find index of. * @return Index of the node, -1 if no such node was found. */ public int indexOf (final Node node) { try { PR.enterWriteAccess (); return ((List)nodes).indexOf(node); } finally { PR.exitWriteAccess (); } } /* Adds new listener to the listener list. Listener is notified of * any change in ordering of nodes. * * @param chl new listener */ public void addChangeListener (final ChangeListener chl) { support.addChangeListener(chl); } /* Removes listener from the listener list. * Removed listener isn't notified no more. * * @param chl listener to remove */ public void removeChangeListener (final ChangeListener chl) { support.removeChangeListener(chl); } } // End of ArrayChildren inner class /** Implementation of index interface that operates on an list of * objects that are presented by given nodes. */ public abstract class KeysChildren extends Children.Keys { /** Support instance for delegation of some <code>Index</code> methods. */ private Index support; // JST: Maybe made protected /** list of objects that should be manipulated with this keys */ protected final List list; /** Constructor. * @param ar the array of any objects */ public KeysChildren (List ar) { list = ar; update (); } /** Getter for the index that works with this children. * @return the index */ public Index getIndex () { synchronized (this) { if (support == null) { support = createIndex (); } return support; } } /** The method that creates the supporting index for this * children object. */ protected Index createIndex () { // create support instance for delegation of common tasks return new Support() { /** Returns only nodes that are indexable. Not any fixed ones. */ public Node[] getNodes () { List l = Arrays.asList(KeysChildren.this.getNodes ()); if (KeysChildren.this.nodes != null) { l.removeAll (KeysChildren.this.nodes); } return (Node[])l.toArray (new Node[l.size ()]); } public int getNodesCount () { return list.size(); } public void reorder (int[] perm) { KeysChildren.this.reorder (perm); update (); fireChangeEvent(new ChangeEvent(this)); } }; } /* Reorders the list with given permutation. * @param perm permutation with the length of current nodes * @exception IllegalArgumentException if the perm is not valid permutation */ protected void reorder (final int[] perm) { synchronized (lock ()) { Object[] n = list.toArray (); for (int i = 0; i < n.length; i++) { list.set (perm[i], n[i]); } } } /** The lock to use when accessing the list. * By default this implementation returns the list itself, but can * be changed to lock more properly. * * @return lock to use for accessing the list */ protected Object lock () { return list; } /** Update the status of the list if it has changed. */ public final void update () { Object[] keys; synchronized (lock ()) { keys = list.toArray (); } super.setKeys (keys); } } }