/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

package org.apache.batik.gvt;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

/**
 * A CompositeGraphicsNode is a graphics node that can contain graphics nodes.
 *
 * @author <a href="mailto:Thierry.Kormann@sophia.inria.fr">Thierry Kormann</a>
 * @version $Id: CompositeGraphicsNode.java,v 1.36 2003/08/09 16:58:40 deweese Exp $
 */
public class CompositeGraphicsNode extends AbstractGraphicsNode 
    implements List {

    public static final Rectangle2D VIEWPORT = new Rectangle(0, 0, 0, 0);

    /**
     * The children of this composite graphics node.
     */
    protected GraphicsNode [] children;

    /**
     * The number of children of this composite graphics node.
     */
    protected int count;

    /**
     * The number of times the children list has been structurally modified.
     */
    protected int modCount;

    /**
     * This flag indicates if this node has BackgroundEnable = 'new'.
     * If so traversal of the gvt tree can halt here.
     */
    protected Rectangle2D backgroundEnableRgn = null;

    /**
     * Internal Cache: Geometry bounds for this node, not taking into
     * account any of its children rendering attributes into account
     */
    private Rectangle2D geometryBounds;

    /**
     * Internal Cache: Primitive bounds.
     */
    private Rectangle2D primitiveBounds;

    /**
     * Internal Cache: Sensitive bounds.
     */
    private Rectangle2D sensitiveBounds;

    /**
     * Internal Cache: the outline.
     */
    private Shape outline;

    /**
     * Constructs a new empty <tt>CompositeGraphicsNode</tt>.
     */
    public CompositeGraphicsNode() {}

    //
    // Structural methods
    //

    /**
     * Returns the list of children.
     */
    public List getChildren() {
        return this;
    }

    /**
     * Sets the enable background property to the specified rectangle.
     *
     * @param bgRgn the region that defines the background enable property
     */
    public void setBackgroundEnable(Rectangle2D bgRgn) {
        backgroundEnableRgn = bgRgn;
    }

    /**
     * Returns the region defining the background enable property.
     */
    public Rectangle2D getBackgroundEnable() {
        return backgroundEnableRgn;
    }

    /**
     * Sets if this node is visible or not depending on the specified value.
     * Don't fire a graphicsNodeChange event because this doesn't really
     * effect us (it effects our children through CSS inheritence).
     *
     * @param isVisible If true this node is visible
     */
    public void setVisible(boolean isVisible) {
        // fireGraphicsNodeChangeStarted();
        this.isVisible = isVisible;
        // fireGraphicsNodeChangeCompleted();
    }


    //
    // Drawing methods
    //

    /**
     * Paints this node without applying Filter, Mask, Composite, and clip.
     *
     * @param g2d the Graphics2D to use
     */
    public void primitivePaint(Graphics2D g2d) {
        if (count == 0) {
            return;
        }

        // Paint children
        for (int i=0; i < count; ++i) {
            GraphicsNode node = children[i];
            if (node == null) {
                continue;
            }
            node.paint(g2d);
        }
    }

    //
    // Event support methods
    //


    //
    // Geometric methods
    //

    /**
     * Invalidates the cached geometric bounds. This method is called
     * each time an attribute that affects the bounds of this node
     * changed.
     */
    protected void invalidateGeometryCache() {
        super.invalidateGeometryCache();
        geometryBounds = null;
        primitiveBounds = null;
        sensitiveBounds = null;
        outline = null;
    }

    /**
     * Returns the bounds of the area covered by this node's primitive paint.
     */
    public Rectangle2D getPrimitiveBounds() {
        if (primitiveBounds == null) {
            int i=0;
            Rectangle2D bounds = null;
            while ((bounds == null) && i < count) {
                bounds = children[i++].getTransformedBounds(IDENTITY);
            }
            if (bounds == null) return null;
            primitiveBounds = bounds;

            Rectangle2D ctb = null;
            while (i < count) {
                ctb = children[i++].getTransformedBounds(IDENTITY);
                if (ctb != null) {
                    if (primitiveBounds == null) {
                        // another thread has set the primitive bounds to null,
                        // need to recall this function
                        return null;
                    } else {
                        primitiveBounds.add(ctb);
                    }
                }
            }

            // Make sure we haven't been interrupted
            if (Thread.currentThread().isInterrupted()) {
                // The Thread has been interrupted.
                // Invalidate any cached values and proceed.
                invalidateGeometryCache();
            }
        }
        return primitiveBounds;
    }

    /**
     * Returns the bounds of this node's primitivePaint after applying
     * the input transform (if any), concatenated with this node's
     * transform (if any).
     *
     * @param txf the affine transform with which this node's transform should
     *        be concatenated. Should not be null.  */
    public Rectangle2D getTransformedPrimitiveBounds(AffineTransform txf) {
        AffineTransform t = txf;
        if (transform != null) {
            t = new AffineTransform(txf);
            t.concatenate(transform);
        }
        int i = 0;
        Rectangle2D tpb = null;
        while (tpb == null && i < count) {
            tpb = children[i++].getTransformedBounds(t);
        }

        Rectangle2D ctb = null;
        while (i < count) {
            ctb = children[i++].getTransformedBounds(t);
            if(ctb != null){
                tpb.add(ctb);
            }
        }

        return tpb;
    }

    /**
     * Returns the bounds of the area covered by this node, without
     * taking any of its rendering attribute into account. i.e.,
     * exclusive of any clipping, masking, filtering or stroking, for
     * example.
     */
    public Rectangle2D getGeometryBounds() {
        if (geometryBounds == null) {
            // System.out.println("geometryBounds are null");
            int i=0;
            while(geometryBounds == null && i < count){
                geometryBounds = 
		    children[i++].getTransformedGeometryBounds (IDENTITY);
            }

            Rectangle2D cgb = null;
            while (i<count) {
                cgb = children[i++].getTransformedGeometryBounds(IDENTITY);
                if (cgb != null) {
                    if (geometryBounds == null) {
                        // another thread has set the geometry bounds to null,
                        // need to recall this function
                        return getGeometryBounds();
                    } else {
                        geometryBounds.add(cgb);
                    }
                }
            }
        }

        return geometryBounds;
    }

    /**
     * Returns the bounds of the area covered by this node, without taking any
     * of its rendering attribute into accoun. i.e., exclusive of any clipping,
     * masking, filtering or stroking, for example. The returned value is
     * transformed by the concatenation of the input transform and this node's
     * transform.
     *
     * @param txf the affine transform with which this node's transform should
     *        be concatenated. Should not be null.
     */
    public Rectangle2D getTransformedGeometryBounds(AffineTransform txf) {
        AffineTransform t = txf;
        if (transform != null) {
            t = new AffineTransform(txf);
            t.concatenate(transform);
        }
	
        Rectangle2D gb = null;
        int i=0;
        while (gb == null && i < count) {
            gb = children[i++].getTransformedGeometryBounds(t);
        }
	
        Rectangle2D cgb = null;
        while (i < count) {
            cgb = children[i++].getTransformedGeometryBounds(t);
            if (cgb != null) {
                gb.add(cgb);
            }
        }

        return gb;
    }

    /**
     * Returns the bounds of the sensitive area covered by this node,
     * This includes the stroked area but does not include the effects
     * of clipping, masking or filtering.
     */
    public Rectangle2D getSensitiveBounds() {
        if (sensitiveBounds != null)
            return sensitiveBounds;

        // System.out.println("sensitiveBoundsBounds are null");
        int i=0;
        while(sensitiveBounds == null && i < count){
            sensitiveBounds = 
                children[i++].getTransformedSensitiveBounds(IDENTITY);
        }

        Rectangle2D cgb = null;
        while (i<count) {
            cgb = children[i++].getTransformedSensitiveBounds(IDENTITY);
            if (cgb != null) {
                if (sensitiveBounds == null)
                    // another thread has set the geometry bounds to null,
                    // need to recall this function
                    return getSensitiveBounds();
                
                sensitiveBounds.add(cgb);
            }
        }

        return sensitiveBounds;
    }

    /**
     * Returns the bounds of the sensitive area covered by this node,
     * This includes the stroked area but does not include the effects
     * of clipping, masking or filtering. The returned value is
     * transformed by the concatenation of the input transform and
     * this node's transform.
     *
     * @param txf the affine transform with which this node's
     * transform should be concatenated. Should not be null.
     */
    public Rectangle2D getTransformedSensitiveBounds(AffineTransform txf) {
        AffineTransform t = txf;
        if (transform != null) {
            t = new AffineTransform(txf);
            t.concatenate(transform);
        }
	
        Rectangle2D sb = null;
        int i=0;
        while (sb == null && i < count) {
            sb = children[i++].getTransformedSensitiveBounds(t);
        }
	
        Rectangle2D csb = null;
        while (i < count) {
            csb = children[i++].getTransformedSensitiveBounds(t);
            if (csb != null) {
                sb.add(csb);
            }
        }

        return sb;
    }

    /**
     * Returns true if the specified Point2D is inside the boundary of this
     * node, false otherwise.
     *
     * @param p the specified Point2D in the user space
     */
    public boolean contains(Point2D p) {
        Rectangle2D bounds = getSensitiveBounds();
        if (count > 0 && bounds != null && bounds.contains(p)) {
            Point2D pt = null;
            Point2D cp = null; // Propagated to children
            for (int i=0; i < count; ++i) {
                AffineTransform t = children[i].getInverseTransform();
                if(t != null){
                    pt = t.transform(p, pt);
                    cp = pt;
                } else {
                    cp = p;
                }
                if (children[i].contains(cp)) {
                    return true;
                }
            }
        }

        return false;
    }


    /**
     * Returns the GraphicsNode containing point p if this node or one of its
     * children is sensitive to mouse events at p.
     *
     * @param p the specified Point2D in the user space
     */
    public GraphicsNode nodeHitAt(Point2D p) {
        Rectangle2D bounds = getSensitiveBounds();
        if (count > 0 && bounds != null && bounds.contains(p)) {
            // Go backward because the children are in rendering order
            Point2D pt = null;
            Point2D cp = null; // Propagated to children
            for (int i=count-1; i >= 0; --i) {
                AffineTransform t = children[i].getInverseTransform();
                if(t != null){
                    pt = t.transform(p, pt);
                    cp = pt;
                } else {
                    cp = p;
                }
                GraphicsNode node = children[i].nodeHitAt(cp);
                if (node != null) {
                    return node;
                }
            }
        }

        return null;
    }

    /**
     * Returns the outline of this node.
     */
    public Shape getOutline() {
        if (outline == null) {
            outline = new GeneralPath();
            for (int i = 0; i < count; i++) {
                Shape childOutline = children[i].getOutline();
                if (childOutline != null) {
                    AffineTransform tr = children[i].getTransform();
                    if (tr != null) {
                        ((GeneralPath)outline).append(tr.createTransformedShape(childOutline), false);
                    } else {
                        ((GeneralPath)outline).append(childOutline, false);
                    }
                }
            }
        }

        return outline;
    }

    //
    // Structural info
    //

    /**
     * Sets the root node of this grahics node and modify all its children.
     */
    protected void setRoot(RootGraphicsNode newRoot) {
        super.setRoot(newRoot);
        for (int i=0; i < count; ++i) {
            GraphicsNode node = children[i];
            ((AbstractGraphicsNode)node).setRoot(newRoot);
        }
    }

    //
    // List implementation
    //

    /**
     * Returns the number of children of this composite graphics node.
     */
    public int size() {
        return count;
    }

    /**
     * Returns true if this composite graphics node does not contain
     * graphics node, false otherwise.
     */
    public boolean isEmpty() {
        return (count == 0);
    }

    /**
     * Returns true if this composite graphics node contains the
     * specified graphics node, false otherwise.
     * @param node the node to check
     */
    public boolean contains(Object node) {
        return (indexOf(node) >= 0);
    }

    /**
     * Returns an iterator over the children of this graphics node.
     */
    public Iterator iterator() {
        return new Itr();
    }

    /**
     * Returns an array containing all of the graphics node in the
     * children list of this composite graphics node in the correct
     * order. If the children list fits in the specified array, it is
     * returned therein. Otherwise, a new array is allocated.
     */
    public Object [] toArray() {
        GraphicsNode [] result = new GraphicsNode[count];
        for (int i=0; i < count; ++i) {
            result[i] = children[i];
        }
        return result;
    }

    /**
     * Returns an array containing all of the graphics node in the children list
     * of this composite graphics node in the correct order.
     *
     * @param a the array to fit if possible 
     */
    public Object[] toArray(Object [] a) {
        if (a.length < count) {
            a = new GraphicsNode[count];
        }
        System.arraycopy(children, 0, a, 0, count);
        if (a.length > count) {
            a[count] = null;
        }
        return a;
    }

    /**
     * Returns the graphics node at the specified position in the children list.
     *
     * @param index the index of the graphics node to return
     * @exception IndexOutOfBoundsException if the index is out of range
     */
    public Object get(int index) {
        checkRange(index);
        return children[index];
    }

    // Modification Operations

    /**
     * Replaces the graphics node at the specified position in the children list
     * with the specified graphics node.
     *
     * @param index the index of the graphics node to replace
     * @param o the graphics node to be stored at the specified position
     * @return the graphics node previously  at the specified position
     * @exception IndexOutOfBoundsException if the index is out of range
     * @exception IllegalArgumentException if the node is not an
     * instance of GraphicsNode 
     */
    public Object set(int index, Object o) {
        // Check for correct arguments
        if (!(o instanceof GraphicsNode)) {
            throw new IllegalArgumentException(o+" is not a GraphicsNode");
        }
        checkRange(index);
        GraphicsNode node = (GraphicsNode) o;
        {
            fireGraphicsNodeChangeStarted(node);
        }
        // Reparent the graphics node and tidy up the tree's state
        if (node.getParent() != null) {
            node.getParent().getChildren().remove(node);
        }
        // Replace the node to the children list
        GraphicsNode oldNode = children[index];
        children[index] = node;
        // Set the parents of the graphics nodes
        ((AbstractGraphicsNode) node).setParent(this);
        ((AbstractGraphicsNode) oldNode).setParent(null);
        // Set the root of the graphics node
        ((AbstractGraphicsNode) node).setRoot(this.getRoot());
        ((AbstractGraphicsNode) oldNode).setRoot(null);
        // Invalidates cached values
        invalidateGeometryCache();
        // Create and dispatch events
        // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_REMOVED;
        // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, oldNode));
        // id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_ADDED;
        // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, node));
        fireGraphicsNodeChangeCompleted();
        return oldNode;
     }

    /**
     * Adds the specified graphics node to this composite graphics node.
     *
     * @param o the graphics node to add
     * @return true (as per the general contract of Collection.add)
     * @exception IllegalArgumentException if the node is not an
     * instance of GraphicsNode
     */
    public boolean add(Object o) {
        // Check for correct argument
        if (!(o instanceof GraphicsNode)) {
            throw new IllegalArgumentException(o+" is not a GraphicsNode");
        }
        GraphicsNode node = (GraphicsNode) o;
        {
            fireGraphicsNodeChangeStarted(node);
        }
        // Reparent the graphics node and tidy up the tree's state
        if (node.getParent() != null) {
            node.getParent().getChildren().remove(node);
        }
        // Add the graphics node to the children list
        ensureCapacity(count + 1);  // Increments modCount!!
        children[count++] = node;
        // Set the parent of the graphics node
        ((AbstractGraphicsNode) node).setParent(this);
        // Set the root of the graphics node
        ((AbstractGraphicsNode) node).setRoot(this.getRoot());
        // Invalidates cached values
        invalidateGeometryCache();
        // Create and dispatch event
        // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_ADDED;
        // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, node));
        fireGraphicsNodeChangeCompleted();
        return true;
    }

    /**
     * Inserts the specified graphics node at the specified position in this
     * children list. Shifts the graphics node currently at that position (if
     * any) and any subsequent graphics nodes to the right (adds one to their
     * indices).
     *
     * @param index the position at which the specified graphics node is to
     * be inserted.
     * @param o the graphics node to be inserted.
     * @exception IndexOutOfBoundsException if the index is out of range
     * @exception IllegalArgumentException if the node is not an
     * instance of GraphicsNode 
     */
    public void add(int index, Object o) {
        // Check for correct arguments
        if (!(o instanceof GraphicsNode)) {
            throw new IllegalArgumentException(o+" is not a GraphicsNode");
        }
        if (index > count || index < 0) {
            throw new IndexOutOfBoundsException(
                "Index: "+index+", Size: "+count);
        }
        GraphicsNode node = (GraphicsNode) o;
        {
            fireGraphicsNodeChangeStarted(node);
        }
        // Reparent the graphics node and tidy up the tree's state
        if (node.getParent() != null) {
            node.getParent().getChildren().remove(node);
        }
        // Insert the node to the children list
        ensureCapacity(count+1);  // Increments modCount!!
        System.arraycopy(children, index, children, index+1, count-index);
        children[index] = node;
        count++;
        // Set parent of the graphics node
        ((AbstractGraphicsNode) node).setParent(this);
        // Set root of the graphics node
        ((AbstractGraphicsNode) node).setRoot(this.getRoot());
        // Invalidates cached values
        invalidateGeometryCache();
        // Create and dispatch event
        // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_ADDED;
        // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, node));
        fireGraphicsNodeChangeCompleted();
    }

    /**
     * <b>Not supported</b> -
     * Throws <tt>UnsupportedOperationException</tt> exception.
     */
    public boolean addAll(Collection c) {
        throw new UnsupportedOperationException();
    }

    /**
     * <b>Not supported</b> -
     * Throws <tt>UnsupportedOperationException</tt> exception.
     */
    public boolean addAll(int index, Collection c) {
        throw new UnsupportedOperationException();
    }

    /**
     * Removes the specified graphics node from the children list.
     *
     * @param o the node the remove
     * @return true if the children list contains the specified graphics node
     * @exception IllegalArgumentException if the node is not an
     * instance of GraphicsNode
     */
    public boolean remove(Object o) {
        // Check for correct argument
        if (!(o instanceof GraphicsNode)) {
            throw new IllegalArgumentException(o+" is not a GraphicsNode");
        }
        GraphicsNode node = (GraphicsNode) o;
        if (node.getParent() != this) {
            return false;
        }
        // Remove the node
        int index = 0;
        for (; node != children[index]; index++);
        remove(index);
        return true;
    }

    /**
     * Removes the graphics node at the specified position in the children list.
     * Shifts any subsequent graphics nodes to the left (subtracts one from
     * their indices).
     *
     * @param index the position of the graphics node to remove
     * @return the graphics node that was removed
     * @exception IndexOutOfBoundsException if index out of range <tt> 
     */
    public Object remove(int index) {
        // Check for correct argument
        checkRange(index);
        GraphicsNode oldNode = children[index];
        {
            fireGraphicsNodeChangeStarted(oldNode);
        }
        // Remove the node at the specified index
        modCount++;
        int numMoved = count - index - 1;
        if (numMoved > 0) {
            System.arraycopy(children, index+1, children, index, numMoved);
        }
        children[--count] = null; // Let gc do its work
        if (count == 0) {
            children = null;
        }
        // Set parent of the node
        ((AbstractGraphicsNode) oldNode).setParent(null);
        // Set root of the node
        ((AbstractGraphicsNode) oldNode).setRoot(null);
        // Invalidates cached values
        invalidateGeometryCache();
        // Create and dispatch event
        // int id = CompositeGraphicsNodeEvent.GRAPHICS_NODE_REMOVED;
        // dispatchEvent(new CompositeGraphicsNodeEvent(this, id, oldNode));
        fireGraphicsNodeChangeCompleted();
        return oldNode;
    }

    /**
     * <b>Not supported</b> -
     * Throws <tt>UnsupportedOperationException</tt> exception.
     */
    public boolean removeAll(Collection c) {
        throw new UnsupportedOperationException();
    }

    /**
     * <b>Not supported</b> -
     * Throws <tt>UnsupportedOperationException</tt> exception.
     */
    public boolean retainAll(Collection c) {
        throw new UnsupportedOperationException();
    }

    /**
     * <b>Not supported</b> -
     * Throws <tt>UnsupportedOperationException</tt> exception.
     */
    public void clear() {
        throw new UnsupportedOperationException();
    }

    /**
     * Returns true if this composite graphics node contains all the graphics
     * node in the specified collection, false otherwise.
     *
     * @param c the collection to be checked for containment 
     */
    public boolean containsAll(Collection c) {
        Iterator i = c.iterator();
        while (i.hasNext()) {
            if (!contains(i.next())) {
                    return false;
            }
        }
        return true;
    }

    // Search Operations

    /**
     * Returns the index in the children list of the specified graphics node or
     * -1 if the children list does not contain this graphics node.
     *
     * @param node the graphics node to search for 
     */
    public int indexOf(Object node) {
        if (node == null || !(node instanceof GraphicsNode)) {
            return -1;
        }
        if (((GraphicsNode) node).getParent() == this) {
            for (int i = 0; i < count; i++) {
                if (node == children[i]) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Returns the index in this children list of the last occurence of the
     * specified graphics node, or -1 if the list does not contain this graphics
     * node.
     *
     * @param node the graphics node to search for 
     */
    public int lastIndexOf(Object node) {
        if (node == null || !(node instanceof GraphicsNode)) {
            return -1;
        }
        if (((GraphicsNode) node).getParent() == this) {
            for (int i = count-1; i >= 0; i--) {
                if (node == children[i]) {
                    return i;
                }
            }
        }
        return -1;
    }

    // List Iterators

    /**
     * Returns an iterator over the children of this graphics node.
     */
    public ListIterator listIterator() {
        return listIterator(0);
    }

    /**
     * Returns an iterator over the children of this graphics node, starting at
     * the specified position in the children list.
     *
     * @param index the index of the first graphics node to return
     * from the children list 
     */
    public ListIterator listIterator(int index) {
        if (index < 0 || index > count) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
        return new ListItr(index);
    }

    // View

    /**
     * <b>Not supported</b> -
     * Throws <tt>UnsupportedOperationException</tt> exception.
     */
    public List subList(int fromIndex, int toIndex) {
        throw new UnsupportedOperationException();
    }

    /**
     * Checks if the given index is in range.  If not, throws an appropriate
     * runtime exception.
     *
     * @param index the index to check
     */
    private void checkRange(int index) {
        if (index >= count || index < 0) {
            throw new IndexOutOfBoundsException(
                "Index: "+index+", Size: "+count);
        }
    }

    /**
     * Increases the capacity of the children list, if necessary, to ensure that
     * it can hold at least the number of graphics nodes specified by the
     * minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity.
     */
    public void ensureCapacity(int minCapacity) {
        if (children == null) {
            children = new GraphicsNode[4];
        }
        modCount++;
        int oldCapacity = children.length;
        if (minCapacity > oldCapacity) {
            GraphicsNode [] oldData = children;
            int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
            }
            children = new GraphicsNode[newCapacity];
            System.arraycopy(oldData, 0, children, 0, count);
        }
    }

    /**
     * An implementation of the java.util.Iterator interface.
     */
    private class Itr implements Iterator {

        /**
         * Index of graphics node to be returned by subsequent call to next.
         */
        int cursor = 0;

        /**
         * Index of graphics node returned by most recent call to next or
         * previous.  Reset to -1 if this graphics node is deleted by a call
         * to remove.
         */
        int lastRet = -1;

        /**
         * The modCount value that the iterator believes that the backing
         * List should have.  If this expectation is violated, the iterator
         * has detected concurrent modification.
         */
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != count;
        }

        public Object next() {
            try {
                Object next = get(cursor);
                checkForComodification();
                lastRet = cursor++;
                return next;
            } catch(IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet == -1) {
                throw new IllegalStateException();
            }
            checkForComodification();

            try {
                CompositeGraphicsNode.this.remove(lastRet);
                if (lastRet < cursor) {
                    cursor--;
                }
                lastRet = -1;
                expectedModCount = modCount;
            } catch(IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }
    }


    /**
     * An implementation of the java.util.ListIterator interface.
     */
    private class ListItr extends Itr implements ListIterator {

        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public Object previous() {
            try {
                Object previous = get(--cursor);
                checkForComodification();
                lastRet = cursor;
                return previous;
            } catch(IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor-1;
        }

        public void set(Object o) {
            if (lastRet == -1) {
                throw new IllegalStateException();
            }
            checkForComodification();
            try {
                CompositeGraphicsNode.this.set(lastRet, o);
                expectedModCount = modCount;
            } catch(IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(Object o) {
            checkForComodification();
            try {
                CompositeGraphicsNode.this.add(cursor++, o);
                lastRet = -1;
                expectedModCount = modCount;
            } catch(IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }
    }
}
