/*
 * @(#)ZooContainer.java    1.1  23 August 2001
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.gui.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

/**
 * <P>Abstract superclass for containers 
 * used in a <CODE>{@link Zoo Zoo}</CODE>.</P>
 * 
 * <P>Top-level components in a zoo container are subject
 * to a <I>z</I>-ordering that affects the display
 * of the components.
 *
 * <I>Z</I>-order is implemented in the same manner
 * as it is for standard Java containers:
 * in a set of <I>n</I> contained components,
 * a <I>z</I>-order position of 0 represents the top component 
 * and a <I>z</I>-order position of <I>n</I> - 1 
 * represents the bottom component.
 *
 * The <I>z</I>-order position of a given component 
 * is equal to the number of components 
 * relatively higher than the given component.</P>
 *
 * <P>Within a zoo container, the top-level components
 * are considered "child items" of the container.
 *
 * Components which reside in a zoo container
 * at any level of nesting, including the top-level,
 * are considered "items" of the container.</P>
 *
 * <P>A zoo container has the capability of associating
 * collections of top-level components into groups.
 *
 * A group may contain any number of nested groups.
 *
 * A group at top level has a single <I>z</I>-order position
 * and is represented by a <CODE>{@link ZooGroup ZooGroup}</CODE>.
 *
 * When a group at top level is disassociated,
 * its grouped components are returned to the top level
 * where they assume the relative <I>z</I>-order of the group,
 * and actual <I>z</I>-order positions defined by their
 * <I>z</I>-order within the disassociated group.</P>
 *
 * <P>Note that this panel assumes an <CODE>AbsoluteLayout</CODE>.
 *
 * Behavior is undefined if this panel is set to have a layout
 * other than an <CODE>AbsoluteLayout</CODE>.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.1
 */
public class ZooContainer extends DisplayPanel {

    /** Constructs a new container. */
    public ZooContainer() {
        setLayout(new AbsoluteLayout());
    }

    ////////////////
    // Public API //
    ////////////////

    /**
     * Adds the given component to this container
     * at the highest <I>z</I>-order position.
     *
     * @param c a component to be added to this container
     * @return the added component
     * @see #addChildItem(Component, int)
     */
    public Component addChildItem(Component c) {
        return addChildItemImpl(c, 0);
    }
    
    /**
     * Adds the given component to this container
     * at the given <I>z</I>-order position.
     *
     * @param c a component to be added to this zoo
     * @param z the <I>z</I>-order position at which 
     *      to add the component, or -1 if the component 
     *      is to be added at the deepest <I>z</I>-order position
     * @return the added component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order is invalid
     * @see #addChildItem(Component)
     */
    public Component addChildItem(Component c, int z) {
        return addChildItemImpl(c, z);
    }
    
    /** 
     * <P>Returns the visible item that contains the given position, 
     * <CODE>null</CODE> if the given position is out of bounds,
     * or this container if there is no item at the given position.
     * 
     * The item at the highest <I>z</I>-order is returned 
     * in the case where there is overlap in the components. 
     * 
     * Items which are not visible are ignored during the search.</P>
     *
     * <P>The <CODE>findItemAt</CODE> method is different from the
     * <CODE>{@link #findChildItemAt(int, int) findChildItemAt}</CODE>
     * method in that <CODE>findChildItemAt</CODE> only searches 
     * the top-level components in this container; 
     * if the top-level component contained the given position 
     * is a <CODE>ZooGroup</CODE>, <CODE>findItemAt</CODE> 
     * will search the group to find a nested component.</P>
     * 
     * @param x the <I>x</I>-coordinate of the desired component
     *      given in terms of this container's coordinate space
     * @param y the <I>y</I>-coordinate of the desired component
     *      given in terms of this container's coordinate space
     * @see #findItemAt(Point)
     * @see #findChildItemAt(int, int)
     * @see #findChildItemAt(Point)
     */
    public Component findItemAt(int x, int y) {
        return findItemImpl(x, y, true);
    }
   
    /** 
     * <P>Returns the visible item that contains the given point, 
     * <CODE>null</CODE> if the given point is out of bounds,
     * or this container if there is no item at the given position.
     * 
     * The item at the highest <I>z</I>-order is returned 
     * in the case where there is overlap in the components. 
     * 
     * Items which are not visible are ignored during the search.</P>
     *
     * <P>The <CODE>findItemAt</CODE> method is different from the
     * <CODE>{@link #findChildItemAt(int, int) findChildItemAt}</CODE>
     * method in that <CODE>findChildItemAt</CODE> only searches 
     * the top-level components in this container; 
     * if the top-level component contained the given point
     * is a <CODE>ZooGroup</CODE>, <CODE>findItemAt</CODE> 
     * will search the group to find a nested component.</P>
     * 
     * @param p the point location of the desired component
     *      given in terms of this container's coordinate space
     * @throws NullPointerException
     *      if the given point is <CODE>null</CODE>
     * @see #findItemAt(int, int)
     * @see #findChildItemAt(Point)
     * @see #findChildItemAt(int, int)
     */
    public Component findItemAt(Point p) {
        return findItemImpl(p.x, p.y, true);
    }
    
    /** 
     * <P>Returns the visible top-level component
     * that contains the given position, 
     * <CODE>null</CODE> if the given position is out of bounds,
     * or this container if there is no top-level component
     * at the given position.
     * 
     * The top-level component at the highest <I>z</I>-order 
     * is returned in the case where there is overlap 
     * in the components. 
     * 
     * Top-level components which are not visible 
     * are ignored during the search.</P>
     *
     * <P>The <CODE>findItemAt</CODE> method is different from the
     * <CODE>{@link #findChildItemAt(int, int) findChildItemAt}</CODE>
     * method in that <CODE>findChildItemAt</CODE> only searches 
     * the top-level components in this container; 
     * if the top-level component contained the given position 
     * is a <CODE>ZooGroup</CODE>, <CODE>findItemAt</CODE> 
     * will search the group to find a nested component.</P>
     * 
     * @param x the <I>x</I>-coordinate of the desired component
     *      given in terms of this container's coordinate space
     * @param y the <I>y</I>-coordinate of the desired component
     *      given in terms of this container's coordinate space
     * @see #findChildItemAt(Point)
     * @see #findItemAt(int, int)
     * @see #findItemAt(Point)
     */
    public Component findChildItemAt(int x, int y) {
        return findItemImpl(x, y, false);
    }
    
    /** 
     * <P>Returns the visible top-level component
     * that contains the given point, 
     * <CODE>null</CODE> if the given point is out of bounds,
     * or this container if there is no top-level component
     * at the given point.
     * 
     * The top-level component at the highest <I>z</I>-order 
     * is returned in the case where there is overlap 
     * in the components. 
     * 
     * Top-level components which are not visible 
     * are ignored during the search.</P>
     *
     * <P>The <CODE>findItemAt</CODE> method is different from the
     * <CODE>{@link #findChildItemAt(int, int) findChildItemAt}</CODE>
     * method in that <CODE>findChildItemAt</CODE> only searches 
     * the top-level components in this container; 
     * if the top-level component contained the given point
     * is a <CODE>ZooGroup</CODE>, <CODE>findItemAt</CODE> 
     * will search the group to find a nested component.</P>
     * 
     * @param p the point location of the desired component
     *      given in terms of this container's coordinate space
     * @throws NullPointerException
     *      if the given point is <CODE>null</CODE>
     * @see #findChildItemAt(int, int)
     * @see #findItemAt(Point)
     * @see #findItemAt(int, int)
     */
    public Component findChildItemAt(Point p) {
        return findItemImpl(p.x, p.y, false);
    }
    
    /**
     * Returns the component in this container
     * at the given index in the overall collection 
     * of components contained in this container,
     * including components that are nested within groups.
     *
     * Components in the returned array 
     * are provided in the order of their <I>z</I>-order positions.
     *
     * Since grouped components exist at the same <I>z</I>-order,
     * components within a nested group appear in the returned array
     * in the order of their <I>z</I>-order positions
     * within the nested group.
     *
     * @param index the index of the desired component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given index is invalid
     * @see #getChildItem(int)
     * @see #getItems()
     * @see #getItemCount()
     * @see #inverseGetItem(Component)
     */
    public Component getItem(int index) {
        return getItems()[index];
    }
    
    /**
     * Returns an array containing 
     * all of the components contained in this container,
     * including components that are nested within groups.
     *
     * Components in the returned array 
     * are provided in the order of their <I>z</I>-order positions.
     *
     * Since components in a top-level group exist 
     * at the same <I>z</I>-order position,
     * such components appear in the returned array
     * in the order of their <I>z</I>-order positions
     * within the nested group.
     *
     * @see #getChildItems()
     * @see #getItem(int)
     * @see #getItemCount()
     */
    public Component[] getItems() {
        Vector items = new Vector();
        
        Component    c     = null;
        ZooGroup     group = null;
        Component[]  child = null;
        for (int i = 0; i < getComponentCount(); i++) {
            c = getComponent(i);

            // recurse into groups
            if (c instanceof ZooGroup) {
                group = (ZooGroup)c;
                child = group.getItems();
                for (int j = 0; j < child.length; j++)
                    items.add(child[j]);
            }
            
            // add single components
            else {
                items.add(c);
            }
        }
        
        // return the resulting array
        return (Component[])items.toArray(new Component[0]);
    }
    
    /**
     * Returns the number of components in this container,
     * including components that are nested within groups.
     *
     * @see #getChildItemCount()
     * @see #getItem(int)
     * @see #getItems()
     */     
    public int getItemCount() {
        return getItems().length;
    }
    
    /**
     * Returns the index at which the given component
     * is located in the array returned by the
     * <CODE>{@link #getItems getItems}</CODE> method,
     * or -1 if the given component is not in this container.
     *
     * @param c a component whose index is to be retrieved
     * @see #inverseGetChildItem(Component)
     * @see #getItem(int)
     * @see #getItems()
     * @see #getItemCount()
     */
    public int inverseGetItem(Component c) {

        // search through nested components in order
        Component[] items = getItems();
        for (int i = 0; i < items.length; i++) {

            // if the component is found, return the index
            if (items[i] == c)
                return i;
        }
        
        // return -1 if component not found
        return -1;
    }
    
    /**
     * Returns the top-level component in this container
     * at the given <I>z</I>-order.
     *
     * @param z the <I>z</I>-order of the desired component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order is invalid
     * @see #getItem(int)
     * @see #getChildItems()
     * @see #getChildItemCount()
     */
    public Component getChildItem(int z) {
        return getComponent(z);
    }

    /**
     * Returns an array containing 
     * all of the top-level components in this container.
     *
     * Components in the returned array 
     * are provided in order of their <I>z</I>-order positions.
     *
     * Since a group may be at the top-level,
     * the array may contain groups 
     * as well as single components.
     *
     * @see #getItems()
     * @see #getChildItem(int)
     * @see #getChildItemCount()
     */
    public Component[] getChildItems() {
        return getComponents();
    }

    /**
     * Returns the number of top-level components 
     * in this container.
     *
     * The returned value is one greater than 
     * the <I>z</I>-order position of the component
     * at the deepest <I>z</I>-order position.
     *
     * @see #getItemCount()
     * @see #getChildItem(int)
     * @see #getChildItems()
     */
    public int getChildItemCount() {
        return getComponentCount();
    }
    
    /**
     * Returns the <I>z</I>-order position in this container
     * of the given component, or -1 if the component
     * is not in this container.
     *
     * @param c a component or group in this zoo
     *      whose <I>z</I>-order is desired
     * @see #inverseGetItem(Component)
     * @see #getChildItem(int)
     * @see #getChildItems()
     * @see #getChildItemCount()
     */
    public int inverseGetChildItem(Component c) {
        
        // search through top-level components by z-order
        Component[] child = getChildItems();
        for (int i = 0; i < child.length; i++) {
        
            // if the component is found, return the position
            if (child[i] == c)
                return i;
        }
        
        // return -1 if component not found
        return -1;
    }

    /**
     * Returns the parent container for the given component,
     * or <CODE>null</CODE> if the given component
     * is not an item in this container.
     *
     * @param c a component whose parent container 
     *      is to be retrieved
     * @see #getRootZoo()
     * @see #getAncestorZoo()
     */
    public ZooContainer getParentContainer(Component c) {

        // search top-level components first
        Component[] child = getChildItems();
        for (int i = 0; i < child.length; i++) {
            if (child[i] == c)
                return this;
        }
        
        // recur depth-first into nested groups
        ZooGroup     group  = null;
        ZooContainer parent = null;

        for (int i = 0; i < child.length; i++) {
            if (child[i] instanceof ZooGroup) {
                group  = (ZooGroup)child[i];
                parent = group.getParentContainer(c);

                if (parent != null)
                    return parent;
            }
        }
        
        // return null if not found
        return null;
    }
    
    /**
     * Returns the <CODE>Zoo</CODE> at the highest level
     * of the containment hierarchy for this container,
     * or <CODE>null</CODE> if the containment hierarchy
     * does not include a root zoo.
     *
     * An arbitrary amount of nesting may exist between
     * this component and its root zoo.
     *
     * @see #getParentContainer(Component)
     * @see #getAncestorZoo()
     */
    public Zoo getRootZoo() {
        Container c = this;
        
        // go up the tree to the root container
        while ((c != null) && (c instanceof ZooContainer))
            c = c.getParent();
        
        // return the root if it is a zoo
        if (c instanceof Zoo)
            return (Zoo)c;
            
        // return null otherwise
        return null;
    }
    
    /**
     * Returns the <CODE>Zoo</CODE> at the next-higher level
     * of the containment hierarchy for this container,
     * or <CODE>null</CODE> if there is no zoo at a higher level
     * in the containment hierarchy.
     *
     * An arbitrary amount of nesting may exist between
     * this component and its ancestor zoo.
     *
     * @see #getParentContainer(Component)
     * @see #getRootZoo()
     */
    public Zoo getAncestorZoo() {
        if (getRootZoo() == this)
            return (Zoo)this;
        
        Container c = this;
        
        // go up the tree to the next zoo
        while (c != null) {
            c = c.getParent();
            
            if (c instanceof Zoo)
                return (Zoo)c;
        }
        
        // otherwise return null
        return null;
    }
    
    /**
     * Removes the given top-level component
     * from this container.
     * 
     * If the given component is not at the top level
     * of this container, this container is not changed.
     *
     * If the given component is a group,
     * the group and its contents are removed.
     *
     * @param c a component to remove from this container
     * @return the removed component, or <CODE>null</CODE>
     *      if the given component is not at the top level
     *      of this container
     * @see #removeChildItem(int)
     * @see #removeAllItems()
     */
    public Component removeChildItem(Component c) {
        return removeChildItemImpl(c);
    }

    /**
     * Removes the top-level component 
     * at the given <I>z</I>-order position
     * from this container.
     * 
     * If the top-level component 
     * at the given <I>z</I>-order position is a group, 
     * the group and its contents are removed.
     *
     * @param z the <I>z</I>-order position of a component
     *      to be removed from this container
     * @return the removed component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order position is out of bounds
     * @see #removeChildItem(Component)
     * @see #removeAllItems()
     */
    public Component removeChildItem(int z) {
        return removeChildItemImpl(getChildItem(z));
    }
    
    /** 
     * Removes all of the components from this container. 
     *
     * @see #removeChildItem(Component)
     * @see #removeChildItem(int)
     */
    public void removeAllItems() {
        removeAll();
    }

    /**
     * Moves the given component 
     * to the highest <I>z</I>-order position in this container.
     *
     * If the given component is not in this container,
     * this container is not changed.
     *
     * @param c the component to move to the front
     * @return the <I>z</I>-order position of the component 
     *      after the move is completed
     * @see #sendToBack(Component)
     * @see #moveUp(Component)
     * @see #moveDown(Component)
     * @see #moveToZOrder(Component, int)
     * @see #nextHigherInZOrder(int)
     * @see #nextDeeperInZOrder(int)
     */
    public int bringToFront(Component c) {
        return moveToZOrder(c, 0);
    }
    
    /**
     * Moves the given component 
     * to the deepest <I>z</I>-order position in this container.
     *
     * If the given component is not in this container,
     * this container is not changed.
     *
     * @param c the component to send to the back
     * @return the <I>z</I>-order position of the component 
     *      after the move is completed
     * @see #bringToFront(Component)
     * @see #moveUp(Component)
     * @see #moveDown(Component)
     * @see #moveToZOrder(Component, int)
     * @see #nextHigherInZOrder(int)
     * @see #nextDeeperInZOrder(int)
     */
    public int sendToBack(Component c) {
        return moveToZOrder(c, getChildItemCount() - 1);
    }
    
    /**
     * Moves the given component 
     * to the next higher relative <I>z</I>-order position
     * in this container.
     *
     * The next higher relative <I>z</I>-order position
     * may not be numerically one less than 
     * a given <I>z</I>-order position.
     *
     * If the bounding box of the given component 
     * is not overlapped by the bounding box 
     * of any other component in this container,
     * the given component is brought to the front.
     *
     * If the given component is not in this container,
     * this container is not changed.
     *
     * @param c the component to move up
     * @return the <I>z</I>-order position of the component 
     *      after the move is completed
     * @see #bringToFront(Component)
     * @see #sendToBack(Component)
     * @see #moveDown(Component)
     * @see #moveToZOrder(Component, int)
     * @see #nextHigherInZOrder(int)
     * @see #nextDeeperInZOrder(int)
     */
    public int moveUp(Component c) {
        int z = inverseGetChildItem(c);
        if (z == -1)
            return -1;
            
        int newZ = nextHigherInZOrder(z);
        return moveToZOrder(c, newZ);
    }
    
    /**
     * Moves the given component 
     * to the next deeper relative <I>z</I>-order position
     * in this container.
     *
     * The next deeper relative <I>z</I>-order position
     * may not be numerically one greater than 
     * a given <I>z</I>-order position.
     *
     * If the bounding box of the given component 
     * does not overlap the bounding box 
     * of any other component in this container,
     * the given component is sent to the back.
     *
     * If the given component is not in this container,
     * this container is not changed.
     *
     * @param c the component to move down
     * @return the <I>z</I>-order position of the component 
     *      after the move is completed
     * @see #bringToFront(Component)
     * @see #sendToBack(Component)
     * @see #moveUp(Component)
     * @see #moveToZOrder(Component, int)
     * @see #nextHigherInZOrder(int)
     * @see #nextDeeperInZOrder(int)
     */
    public int moveDown(Component c) {
        int z = inverseGetChildItem(c);
        if (z == -1)
            return -1;
            
        int newZ = nextDeeperInZOrder(z);
        return moveToZOrder(c, newZ);
    }
    
    /**
     * Moves the given component 
     * to the given <I>z</I>-order position in this container.
     *
     * The given <I>z</I>-order is to be expressed in terms of 
     * the <I>z</I>-order of this container before the move.
     *
     * The resulting <I>z</I>-order position of the moved component
     * may not be equal to the given <I>z</I>-order position
     * after the component is moved.
     *
     * If the given component is not in this container,
     * this container is not changed.
     *
     * @param c the component to move
     * @param newZ the <I>z</I>-order position 
     *      to which to move the component
     * @return the <I>z</I>-order position of the component 
     *      after the move is completed, or -1 if the component
     *      is not in this zoo
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order is invalid
     * @see #bringToFront(Component)
     * @see #sendToBack(Component)
     * @see #moveUp(Component)
     * @see #moveDown(Component)
     * @see #nextHigherInZOrder(int)
     * @see #nextDeeperInZOrder(int)
     */
    public int moveToZOrder(Component c, int newZ) {
    
        // ensure z-order is valid
        if ((newZ < 0) || (newZ > getChildItemCount())) {
            throw new ArrayIndexOutOfBoundsException(
                "Z-order is invalid: " + newZ);
        }
        
        // get current z-order of component
        int z = inverseGetChildItem(c);
        
        // quit if component is not at top-level of this container
        if (z == -1)
            return -1;
            
        // quit if component does not have to move
        if (z == newZ)
            return z;
        
        // remove the component
        removeChildItem(c);
        
        // add the component at the right z-order
        addChildItem(c, newZ);
        
        // return the new z-order
        return newZ;
    }

    /**
     * Returns the <I>z</I>-order position of the component 
     * whose bounding box overlaps the bounding box 
     * of the component at the given <I>z</I>-order position.
     *
     * If the bounding box of more than one component
     * overlaps the bounding box of the component 
     * at the given <I>z</I>-order position, 
     * the <I>z</I>-order position of the overlapping component
     * whose <I>z</I>-order position is closest 
     * to the given <I>z</I>-order position is returned.
     *
     * @param z the <I>z</I>-order position for which
     *      to find the next higher position
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order is invalid
     * @see #bringToFront(Component)
     * @see #sendToBack(Component)
     * @see #moveUp(Component)
     * @see #moveDown(Component)
     * @see #moveToZOrder(Component, int)
     * @see #nextDeeperInZOrder(int)
     */
    public int nextHigherInZOrder(int z) {
    
        // ensure z-order is valid
        if ((z < 0) || (z > getChildItemCount())) {
            throw new ArrayIndexOutOfBoundsException(
                "Z-order is invalid: " + z);
        }
        
        // get components by z-order
        Component[] child = getChildItems();
        
        // search for occluding component
        for (int i = z - 1; i > 0; i--) {
            if (child[i].isVisible() && 
                child[i].getBounds().intersects(child[z].getBounds()))
            {
                return i;
            }
        }
        
        // return 0 if no higher component found
        return 0;
    }

    /**
     * Returns the <I>z</I>-order position of the component 
     * whose bounding box is overlapped by the bounding box 
     * of the component at the given <I>z</I>-order position.
     *
     * If the bounding box of more than one component
     * is overlapped by the bounding box of the component 
     * at the given <I>z</I>-order position, 
     * the <I>z</I>-order position of the overlapped component
     * whose <I>z</I>-order position is closest 
     * to the given <I>z</I>-order position is returned.
     *
     * @param z the <I>z</I>-order position for which
     *      to find the next deepest position
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order is invalid
     * @see #bringToFront(Component)
     * @see #sendToBack(Component)
     * @see #moveUp(Component)
     * @see #moveDown(Component)
     * @see #moveToZOrder(Component, int)
     * @see #nextHigherInZOrder(int)
     */
    public int nextDeeperInZOrder(int z) {
    
        // ensure z-order is valid
        if ((z < 0) || (z >= getChildItemCount())) {
            throw new ArrayIndexOutOfBoundsException(
                "Z-order is invalid: " + z);
        }
        
        // get components by z-order
        Component[] child = getChildItems();
        
        // search for occluding component
        for (int i = z + 1; i < child.length; i--) {
            if (child[i].isVisible() && 
                child[i].getBounds().intersects(child[z].getBounds()))
            {
                return i;
            }
        }
        
        // return deepest z-order 
        // if no deeper component found
        return child.length - 1;
    }

    /**
     * Groups the given top-level components of this container.
     *
     * The group will exist at 
     * the relative <I>z</I>-order position
     * of the highest <I>z</I>-order position
     * among the components it contains.
     *
     * The resulting <I>z</I>-order position 
     * for the group may not be equal to
     * the actual <I>z</I>-order position 
     * of the highest <I>z</I>-order position
     * among the components the group contains.
     *
     * @param components the components to be grouped
     * @return the resulting group of components, 
     *      or <CODE>null</CODE> if there were 
     *      less than two components to be grouped
     * @throws NullPointerException 
     *      if the given array is <CODE>null</CODE>
     * @see #ungroup(ZooGroup)
     */
    public ZooGroup group(Component[] components) {
    
        // collect valid components, 
        // and find highest z-order
        Vector  valid  = new Vector();
        int     temp   = 0,
                z      = Integer.MAX_VALUE;

        for (int i = 0; i < components.length; i++) {
            temp = inverseGetChildItem(components[i]);
            
            // collect each valid component
            if (temp > -1) {
                valid.add(components[i]);
                
                // and maintain the highest z-order
                if (temp < z) 
                    z = temp;
            }
        }
        
        // quit if less than 2 components to group
        if (valid.size() < 2)
            return null;
        
        // find anchor for relative z-order
        Component above = ((z == 0) ? null : getChildItem(z - 1));
        
        // remove collected components from zoo
        for (int i = 0; i < valid.size(); i++)
            removeChildItem((Component)valid.get(i));
        
        // create a new group to hold the components
        ZooGroup group = new ZooGroup(valid);

        // find the right z-order for the group
        z = ((above == null) ? 0 : inverseGetChildItem(above) + 1);
        
        // add the group at the right z-order
        addChildItem(group, z);
        
        // return the created group
        return group;
    }
    
    /**
     * Disassociates the components in the given group.
     *
     * If the given group is not at the top level of this container,
     * this container is not changed.
     *
     * The individual components will exist at 
     * the relative <I>z</I>-order position
     * of the disassociated group.
     *
     * The resulting <I>z</I>-order positions 
     * for the individual components may not be equal to
     * the actual <I>z</I>-order position 
     * of the group prior to disassociation.
     *
     * @param group the group of components to disassociate
     * @return an array containing the disassociated components
     *      or a zero-length array if the given group 
     *      is not at the top level of this zoo
     * @see #group(Component[])
     */
    public Component[] ungroup(ZooGroup group) {

        // find z-order position of group
        int z = inverseGetChildItem(group);
        
        // quit if group not found
        if (z == -1)
            return new Component[0];
            
        // find anchor for relative z-order
        Component above = ((z == 0) ? null : getChildItem(z - 1));

        // retrieve components in group
        Component[] components = group.getComponents();
        
        // remove group from zoo
        removeChildItem(group);

        // find the right z-order for first component
        z = ((above == null) ? 0 : inverseGetChildItem(above) + 1);
        
        // add translated components back to zoo
        for (int i = 0; i < components.length; i++) {
            components[i].setLocation(
                components[i].getX() + group.getX(), 
                components[i].getY() + group.getY());

            addChildItem(components[i], z++);
        }
        
        // return disassociated components
        return components;
    }

    /** 
     * Returns whether or not the keyboard focus
     * can traverse over the components in this container.
     */
    public boolean isFocusTraversable() {
        return false;
    }

    /** 
     * Returns whether or not this container
     * is managing the focus for its child components. 
     */
    public boolean isManagingFocus() {
        return false;
    }
    
    /////////////////////
    // Private methods //
    /////////////////////
    
    /** 
     * Adds the given component at the given z-order. 
     *
     * @param c a component to be added
     * @param z the desired z-order for the component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given z-order is invalid
     */
    private Component addChildItemImpl(Component c, int z) {
        if (c == null)
            return null;
            
        return add(c, z);
    }
    
    /** 
     * Returns the child component at the given position,
     * the container itself, or null as specified in the contracts
     * for the findItemAt and findChildItemAt methods.
     *
     * @param x the x-coordinate of the component to locate
     * @param y the y-coordinate of the component to locate
     * @param recurse whether or not to recur the search
     *      into nested containers
     */
    private Component findItemImpl(int x, int y, boolean recurse) {
        if (!contains(x, y))
            return null;
            
        Component[] components = getChildItems();
        Component found = this;

        // for each component in this container
        for (int i = 0; i < components.length; i++) {
        
            // test to see if it is visible and contains the point
            if (components[i].isVisible() && 
               (components[i].contains(x, y)))
            {
                // if it does, we've found something at least
                found = components[i];
                
                // recurse search if it is a container
                if (recurse && (found instanceof ZooContainer)) {
                    ZooContainer child = (ZooContainer)found;
                
                    // translate the search location
                    found = child.findComponentAt(
                        x - child.getX(), y - child.getY());
                }
                
                // quit the loop to avoid overlap possibility
                break;
            }
        }
        
        // return the found component
        return found;
    }

    /** 
     * Removes the given component from this zoo. 
     *
     * @param c a component to be removed
     */
    private Component removeChildItemImpl(Component c) {
        
        // search through components with z-order
        Component[] child = getChildItems();
        for (int i = 0; i < child.length; i++) {
        
            // if the component is not in a group
            // remove the component and its laminate
            if (child[i] == c) {
                remove(c);
                return c;
            }
        }
        
        // return null if component not found
        return null;
    }
}
