/*
 * @(#)BaseShape.java    1.0  23 August 2003
 *
 * 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.util.*;

import java.awt.*;
import java.awt.geom.*;
import java.beans.*;


/**
 * <P>Class <CODE>BaseShape</CODE> encapsulates a <CODE>Shape</CODE> defined
 * using vertex and tangent data.</P>
 *
 * <P>Specifically, <CODE>BaseShape</CODE> encapsulates:</P>
 *
 * <UL>
 *  <LI> A 2-dimensional array of vertices for a polygon or Bezier curve</LI>
 *  <LI> A 2-dimensional array of tangents to define the Bezier segments
 *       in the case of a Bezier curve.</LI>
 * </UL>
 *
 * <P>A <CODE>BaseShape</CODE> is parametrized by three additional settings:</P>
 * <UL>
 *   <LI>A <CODE>Path.Strategy</CODE> setting</LI>
 *   <LI>A <CODE>ClosureMode</CODE> setting</LI>
 *   <LI>A <CODE>WindingRule</CODE> setting</LI>
 * </UL>
 *
 * <P>Class <CODE>BaseShape</CODE> contains the common code and data for the
 * derived classes <CODE>AutomaticShape</CODE> and <CODE>TweakableShape</CODE>.</P>
 *
 * <P>Class <CODE>BaseShape</CODE> may not be instantiated directly since it
 * does not have a public constructor.</P>
 *
 * @author  Richard Rasala
 * @version 2.3
 * @since   2.3
 */
public class BaseShape
    implements Shape, SupportsPropertyChange
{
    /** Bound property name for set path strategy. */
    public static final String SET_PATH_STRATEGY = "set.path.strategy";
        
    /** Bound property name for set closure mode. */
    public static final String SET_CLOSURE_MODE  = "set.closure.mode";
        
    /** Bound property name for set winding rule. */
    public static final String SET_WINDING_RULE  = "set.winding.rule";
        
    /** Bound property name to clear/remove all vertex and tangent data. */
    public static final String REMOVE_SHAPE_DATA = "remove.shape.data";
        
    
    /**
     * <P>The vertex data of the shape.</P>
     *
     * <P>Derived classes must ensure that this member data is not
     * <CODE>null</CODE> and that it has the form float[N][2].</P>
     */
    protected float[][] vertex  = new float[0][2];
    
    
    /**
     * <P>The tangent data of the shape.</P>
     *
     * <P>Derived classes must ensure that this member data is not
     * <CODE>null</CODE> and that it has the form float[N][2]
     * for the same N as in the vertex array.</P>
     */
    protected float[][] tangent = new float[0][2];
    
    
    /** The GeneralPath used to implement the shape. */
    private GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
    
    
    /** The Path Strategy. */
    private Path.Strategy pathstrategy = Path.POLYGON;
    
    /** The ClosureMode: OPEN or CLOSED. */
    private ClosureMode closuremode = ClosureMode.CLOSED;
    
    /** The WindingRule: WIND_NON_ZERO or WIND_EVEN_ODD. */
    private WindingRule windingrule = WindingRule.WIND_NON_ZERO;
    
    
    /**
     * <P>The main listener for this BaseShape object
     * to implement the interface <CODE>SupportsPropertyChange</CODE>.</P>
     *
     * <P>Note: In this implementation, <CODE>PropertyChangeSupport</CODE>
     * is used rather than <CODE>SwingPropertyChangeSupport</CODE> to
     * ensure thread safety.</P>
     */
    private final PropertyChangeSupport changeAdapter
        = new PropertyChangeSupport(this);

    
    /**
     * <P>The forwarding listener for this BaseShape object
     * to implement the interface <CODE>SupportsPropertyChange</CODE>.</P>
     *
     * @see #getForwardingListener()
     */
    private final PropertyChangeForwardingListener forwardingListener
        = new PropertyChangeForwardingListener(this);
    

    /**
     * <P>The default constructor with an empty shape.</P>
     *
     * <P>The default settings are as follows:</P>
     *
     * <UL>
     *   <LI>Vertex data:      <CODE>new float[0][2]</CODE></LI>
     *   <LI>Tangent data:     <CODE>new float[0][2]</CODE></LI>
     *   <LI>Path strategy:    <CODE>Path.POLYGON</CODE></LI>
     *   <LI>Closure mode:     <CODE>ClosureMode.CLOSED</CODE></LI>
     *   <LI>Winding rule:     <CODE>WindingRule.WIND_NON_ZERO</CODE>.</LI>
     * </UL>
     */
    protected BaseShape() {}
    
    
    /**
     * Returns an open <CODE>TweakableShape</CODE> whose vertex data is a copy
     * of the vertex data of this shape, whose tangent data is zero, and whose
     * <CODE>Path.Strategy</CODE> is <CODE>Path.POLYGON_DOTS</CODE>.
     */
    public final TweakableShape makePolygonDots() {
        return
            new TweakableShape(
                getVertexData(),
                null,
                Path.POLYGON_DOTS,
                ClosureMode.OPEN,
                getWindingRule());
    }
    
    
    /**
     * Returns a closed <CODE>TweakableShape</CODE> whose vertex data is the
     * closed Bezier frame corresponding to the vertex and tangent data of
     * this shape, whose tangent data is zero, and whose
     * <CODE>Path.Strategy</CODE> is <CODE>Path.POLYGON</CODE>.
     */
    public final TweakableShape makeClosedBezierFrame() {
        return
            new TweakableShape(
                getClosedBezierFrameData(),
                null,
                Path.POLYGON,
                ClosureMode.CLOSED,
                getWindingRule());
    }
    
    
    /**
     * Returns an open <CODE>TweakableShape</CODE> whose vertex data is the
     * open Bezier frame corresponding to the vertex and tangent data of
     * this shape, whose tangent data is zero, and whose
     * <CODE>Path.Strategy</CODE> is <CODE>Path.POLYGON</CODE>.
     */
    public final TweakableShape makeOpenBezierFrame() {
        return
            new TweakableShape(
                getOpenBezierFrameData(),
                null,
                Path.POLYGON,
                ClosureMode.OPEN,
                getWindingRule());
    }
    
    
    /**
     * Returns an open <CODE>TweakableShape</CODE> whose vertex and tangent
     * data is a copy of the corresponding vertex and tangent data of this
     * shape and whose <CODE>Path.Strategy</CODE> is
     * <CODE>Path.BEZIER_TANGENT_SEGMENTS</CODE>.
     */
    public final TweakableShape makeBezierTangentSegments() {
        return
            new TweakableShape(
                getVertexData(),
                getTangentData(),
                Path.BEZIER_TANGENT_SEGMENTS,
                ClosureMode.OPEN,
                getWindingRule());
    }
    
    
    /** Returns the common length N of the vertex and tangent data. */
    public final int length() {
        return vertex.length;
    }
    
    
    /**
     * <P>Returns a copy of the vertex data.</P>
     *
     * <P>The returned array will have the form float[N][2].</P>
     *
     * @return a copy of the vertex data
     * @see #getTangentData()
     * @see #getClosedBezierFrameData()
     * @see #getOpenBezierFrameData()
     * @see #getBezierTangentSegmentData()
     * @see #getMergedVertexTangentData()
     */
    public final float[][] getVertexData() {
        return FloatArray.deepclone(vertex); 
    }
    
    
    /**
     * <P>Returns a copy of the tangent data.</P>
     *
     * <P>The returned array will have the form float[N][2]
     * for the same N as in the vertex data.</P>
     *
     * @return a copy of the tangent data
     * @see #getVertexData()
     * @see #getClosedBezierFrameData()
     * @see #getOpenBezierFrameData()
     * @see #getBezierTangentSegmentData()
     * @see #getMergedVertexTangentData()
     */
    public final float[][] getTangentData() {
        return FloatArray.deepclone(tangent);
    }
    
    
    /**
     * <P>Returns an array <CODE>float[2]</CODE> with the coordinates of
     * a point on the polygon determined by the internal vertex data and
     * the <CODE>float</CODE> parameter <CODE>t</CODE>.</P>
     *
     * <P>Let <CODE>N</CODE> denote the length of the vertex data array.</P>
     *
     * <P>If <CODE>N == 0</CODE>, return <CODE>new float[2]</CODE>.</P>
     *
     * <P>If <CODE>N == 1</CODE> or if <CODE>N &gt; 1</CODE> and
     * either <CODE>t &lt;= 0</CODE> or <CODE>t &gt;= N</CODE>,
     * return a copy of <CODE>vertex[0]</CODE>.</P>
     *
     * <P>Otherwise, let <CODE>t = a + f</CODE> where <CODE>a</CODE> is an
     * integer and <CODE>0 &lt;= f &lt; 1</CODE>.  Let <CODE>b = a+1</CODE>
     * modulo <CODE>N</CODE>.  Then, return the point:
     *
     * <UL>
     *   <LI><CODE>(1 - f) * vertex[a] + f * vertex[b]</CODE></LI>
     * </UL>
     *
     * @param t the parameter that determines the point
     */
    public final float[] getPolygonPoint(float t) {
        int N = vertex.length;
        
        if (N == 0)
            return new float[2];
        
        if ((N == 1) || (t <= 0) || (t >= N))
            return FloatArray.deepclone(vertex[0]);
        
        int a = (int) t;
        int b = (a + 1) % N;
        
        float f = t - a;
        
        if (f == 0)
            return FloatArray.deepclone(vertex[a]);
        
        float e = 1 - f;
        
        return new float[] {
            e * vertex[a][0] + f * vertex[b][0],
            e * vertex[a][1] + f * vertex[b][1]
        };
    }
    
    
    /**
     * <P>Returns an array <CODE>float[2]</CODE> with the coordinates of
     * a point on the cubic curve determined by the internal vertex and
     * tangent data and the <CODE>float</CODE> parameter <CODE>t</CODE>.</P>
     *
     * <P>Let <CODE>N</CODE> denote the length of the vertex data array.</P>
     *
     * <P>If <CODE>N == 0</CODE>, return <CODE>new float[2]</CODE>.</P>
     *
     * <P>If <CODE>N == 1</CODE> or if <CODE>N &gt; 1</CODE> and
     * either <CODE>t &lt;= 0</CODE> or <CODE>t &gt;= N</CODE>,
     * return a copy of <CODE>vertex[0]</CODE>.</P>
     *
     * <P>Otherwise, let <CODE>t = a + f</CODE> where <CODE>a</CODE> is an
     * integer and <CODE>0 &lt;= f &lt; 1</CODE>.  Let <CODE>b = a+1</CODE>
     * modulo <CODE>N</CODE>.  Then, return the point corresponding to the
     * parameter <CODE>f</CODE> and the 4 Bezier control points given below.</P>
     *
     * <UL>
     *   <LI><CODE>vertex[a]</CODE></LI>
     *   <LI><CODE>vertex[a] + tangent[a]</CODE></LI>
     *   <LI><CODE>vertex[b] - tangent[b]</CODE></LI>
     *   <LI><CODE>vertex[b]</CODE></LI>
     * </UL>
     *
     * @param t the parameter that determines the point
     */
    public final float[] getCubicCurvePoint(float t) {
        int N = vertex.length;
        
        if (N == 0)
            return new float[2];
        
        if ((N == 1) || (t <= 0) || (t >= N))
            return FloatArray.deepclone(vertex[0]);
        
        int a = (int) t;
        int b = (a + 1) % N;
        
        float f = t - a;
        
        if (f == 0)
            return FloatArray.deepclone(vertex[a]);
        
        float e = 1 - f;
        
        float c0 = e * e * e;
        float c1 = 3 * e * e * f;
        float c2 = 3 * e * f * f;
        float c3 = f * f * f;
        
        return new float[] {
            c0 * (vertex[a][0]) + c1 * (vertex[a][0] + tangent[a][0]) +
            c2 * (vertex[b][0] - tangent[b][0]) + c3 * (vertex[b][0]),
            c0 * (vertex[a][1]) + c1 * (vertex[a][1] + tangent[a][1]) +
            c2 * (vertex[b][1] - tangent[b][1]) + c3 * (vertex[b][1])
        };
    }
    
    
    /**
     * <P>Returns the closed Bezier frame data corresponding to the
     * vertex and tangent data.</P>
     *
     * <P>If N is the common length of the vertex and tangent data
     * then the returned array will have the form float[3*N][2].</P>
     *
     * @return the Bezier frame data
     * @see #getVertexData()
     * @see #getTangentData()
     * @see #getOpenBezierFrameData()
     * @see #getBezierTangentSegmentData()
     * @see #getMergedVertexTangentData()
     * @see Path#closedBezierFrame(float[][], float[][])
     */
    public final float[][] getClosedBezierFrameData() {
        return Path.closedBezierFrame(vertex, tangent);
    }
    
    
    /**
     * <P>Returns the open Bezier frame data corresponding to the
     * vertex and tangent data.</P>
     *
     * <P>If N is the common length of the vertex and tangent data
     * then if N is non-zero
     * the returned array will have the form float[3*N-2][2]
     * otherwise the returned array will have the form float[0][2].</P>
     *
     * @return the Bezier frame data
     * @see #getVertexData()
     * @see #getTangentData()
     * @see #getClosedBezierFrameData()
     * @see #getBezierTangentSegmentData()
     * @see #getMergedVertexTangentData()
     * @see Path#openBezierFrame(float[][], float[][])
     */
    public final float[][] getOpenBezierFrameData() {
        return Path.openBezierFrame(vertex, tangent);
    }
    
    
    /**
     * <P>Returns the Bezier tangent segment data corresponding to the
     * vertex and tangent data.</P>
     *
     * <P>If N is the common length of the vertex and tangent data
     * then the returned array will have the form float[2*N][2].</P>
     *
     * @return the Bezier frame data
     * @see #getVertexData()
     * @see #getTangentData()
     * @see #getClosedBezierFrameData()
     * @see #getOpenBezierFrameData()
     * @see #getMergedVertexTangentData()
     * @see Path#bezierTangentSegments(float[][], float[][])
     */
    public final float[][] getBezierTangentSegmentData() {
        return Path.bezierTangentSegments(vertex, tangent);
    }
    
    
    /**
     * <P>Returns the deep merge of the vertex and tangent data.</P>
     *
     * <P>If N is the common length of the vertex and tangent data
     * then the returned array will have the form float[N][4].</P>
     *
     * @return the deep merge of the vertex and tangent data
     * @see #getVertexData()
     * @see #getTangentData()
     * @see #getClosedBezierFrameData()
     * @see #getOpenBezierFrameData()
     * @see #getBezierTangentSegmentData()
     */
    public final float[][] getMergedVertexTangentData() {
        return FloatArray.deepmerge(vertex, tangent);
    }
    
    
    /**
     * <P>Removes all vertex and tangent data and makes a new path.</P>
     *
     * <P>Fires property change: REMOVE_SHAPE_DATA.</P>
     */
    public final void removeShapeData() {
        vertex  = new float[0][2];
        tangent = new float[0][2];
        
        makePath();
        
        firePropertyChange(REMOVE_SHAPE_DATA, null, null);
    }
    
    
    /**
     * <P>Sets the path strategy and makes a new path.</P>
     *
     * <P>Does nothing if its parameter is <CODE>null</CODE>.</P>
     *
     * <P>Fires property change: SET_PATH_STRATEGY.</P>
     *
     * @param pathstrategy the path strategy to set
     */
    public final void setPathStrategy(Path.Strategy pathstrategy) {
        if ((pathstrategy == null) || (pathstrategy == this.pathstrategy))
            return;
        
        this.pathstrategy = pathstrategy;
        
        makePath();
        
        firePropertyChange(SET_PATH_STRATEGY, null, null);
    }
    
    
    /**
     * Get the PathStrategy.
     *
     * @return the path strategy
     */
    public final Path.Strategy getPathStrategy() {
        return pathstrategy;
    }
    
    
    /**
     * <P>Sets the closure mode and makes a new path.</P>
     *
     * <P>Does nothing if its parameter is <CODE>null</CODE>.</P>
     *
     * <P>Fires property change: SET_CLOSURE_MODE.</P>
     *
     * @param closuremode the closure mode to set
     */
    public final void setClosureMode(ClosureMode closuremode) {
        if ((closuremode == null) || (closuremode == this.closuremode))
            return;
        
        this.closuremode = closuremode;
        
        makePath();
        
        firePropertyChange(SET_CLOSURE_MODE, null, null);
    }
    
    
    /**
     * Get the closure mode.
     *
     * @return the closure mode
     */
    public final ClosureMode getClosureMode() {
        return closuremode;
    }
    
    
    /**
     * <P>Sets the winding rule and makes a new path.</P>
     *
     * <P>Does nothing if its parameter is <CODE>null</CODE>.</P>
     *
     * <P>Fires property change: SET_WINDING_RULE.</P>
     *
     * @param windingrule the winding rule to set
     */
    public final void setWindingRule(WindingRule windingrule) {
        if ((windingrule == null) || (windingrule == this.windingrule))
            return;
        
        this.windingrule = windingrule;
        
        makePath();
        
        firePropertyChange(SET_WINDING_RULE, null, null);
    }
    
    
    /**
     * Get the winding rule.
     *
     * @return the winding rule
     */
    public final WindingRule getWindingRule() {
        return windingrule;
    }
    
    
    /**
     * Returns a copy of the <CODE>GeneralPath</CODE> that defines this
     * <CODE>Shape</CODE>.
     */
    public final GeneralPath getPath() {
        return pathstrategy.makePath(vertex, tangent, closuremode, windingrule);
    }
    
    
    /**
     * Make the path for the shape using the internal vertex and tangent data
     * and the path strategy, closure mode, and winding rule settings.
     */
    protected final void makePath() {
        path = getPath();
    }
    
    
    /** Shape API. */
    
    /**
     * Tests if the specified coordinates are inside the boundary of the Shape.
     *
     * @param x the x-coordinate of the position tested
     * @param y the y-coordinate of the position tested
     */
    public final boolean contains(double x, double y) {
        return path.contains(x, y);
    }
    
    
    /**
     * Tests if a specified Point2D is inside the boundary of the Shape.
     *
     * @param p the position tested
     */
    public final boolean contains(Point2D p) {
        return path.contains(p);
    }
    
    
    /**
     * <P>Tests if the interior of the Shape entirely contains the
     * specified rectangular area. All coordinates that lie inside the
     * rectangular area must lie within the Shape for the entire
     * rectangular area to be considered contained within the Shape.</P>
     *
     * <P>This method might conservatively return false when:</P>
     * <UL>
     *   <LI>the intersect method returns true</LI>
     *   <LI>the calculations to determine whether or not the Shape
     *       entirely contains the rectangular area are prohibitively
     *       expensive.</LI>
     * </UL>
     *
     * <P>This means that this method might return false even though
     * the Shape contains the rectangular area. The Area class can be
     * used to perform more accurate computations of geometric
     * intersection for any Shape object if a more precise answer is
     * required.</P>
     *
     * @param x the x-coordinate of the rectangle's topleft corner
     * @param y the y-coordinate of the rectangle's topleft corner
     * @param w the rectangle's width
     * @param h the rectangle's height
     */
    public final boolean contains(double x, double y, double w, double h) {
        return path.contains(x, y, w, h);
    }
    
    
    /**
     * <P>Tests if the interior of the Shape entirely contains the
     * specified rectangular area. All coordinates that lie inside the
     * rectangular area must lie within the Shape for the entire
     * rectangular area to be considered contained within the Shape.</P>
     *
     * <P>This method might conservatively return false when:</P>
     * <UL>
     *   <LI>the intersect method returns true</LI>
     *   <LI>the calculations to determine whether or not the Shape
     *       entirely contains the rectangular area are prohibitively
     *       expensive.</LI>
     * </UL>
     *
     * <P>This means that this method might return false even though
     * the Shape contains the rectangular area. The Area class can be
     * used to perform more accurate computations of geometric
     * intersection for any Shape object if a more precise answer is
     * required.</P>
     *
     * @param r the rectangle
     */
    public final boolean contains(Rectangle2D r) {
        return path.contains(r);
    }
    
    
    /**
     * <P>Tests if the interior of the Shape intersects the interior of
     * a specified rectangular area. The rectangular area is
     * considered to intersect the Shape if any point is contained in
     * both the interior of the Shape and the specified rectangular area.</P>
     *
     * <P>This method might conservatively return true when:</P>
     * <UL>
     *   <LI>there is a high probability that the rectangular area and
     *       the Shape intersect, but</LI>
     *   <LI>the calculations to accurately determine this intersection
     *       are prohibitively expensive.</LI>
     * </UL>
     *
     * <P>This means that this method might return true even though the
     * rectangular area does not intersect the Shape. The Area class can
     * be used to perform more accurate computations of geometric
     * intersection for any Shape object if a more precise answer is
     * required.</P>
     *
     * @param x the x-coordinate of the rectangle's topleft corner
     * @param y the y-coordinate of the rectangle's topleft corner
     * @param w the rectangle's width
     * @param h the rectangle's height
     */
    public final boolean intersects(double x, double y, double w, double h) {
        return path.intersects(x, y, w, h);
    }
    
    
    /**
     * <P>Tests if the interior of the Shape intersects the interior of
     * a specified rectangular area. The rectangular area is
     * considered to intersect the Shape if any point is contained in
     * both the interior of the Shape and the specified rectangular area.</P>
     *
     * <P>This method might conservatively return true when:</P>
     * <UL>
     *   <LI>there is a high probability that the rectangular area and
     *       the Shape intersect, but</LI>
     *   <LI>the calculations to accurately determine this intersection
     *       are prohibitively expensive.</LI>
     * </UL>
     *
     * <P>This means that this method might return true even though the
     * rectangular area does not intersect the Shape. The Area class can
     * be used to perform more accurate computations of geometric
     * intersection for any Shape object if a more precise answer is
     * required.</P>
     *
     * @param r the rectangle
     */
    public final boolean intersects(Rectangle2D r) {
        return path.intersects(r);
    }
    
    
    /**
     * Returns an integer Rectangle that completely encloses the Shape.
     * Note that there is no guarantee that the returned Rectangle is
     * the smallest bounding box that encloses the Shape, only that the
     * Shape lies entirely within the indicated Rectangle. The returned
     * Rectangle might also fail to completely enclose the Shape if the
     * Shape overflows the limited range of the integer data type. The
     * getBounds2D method generally returns a tighter bounding box due
     * to its greater flexibility in representation.
     */
    public final Rectangle getBounds() {
        return path.getBounds();
    }
    
    
    /**
     * Returns a high precision and more accurate bounding box of the
     * Shape than the getBounds method. Note that there is no guarantee
     * that the returned Rectangle2D is the smallest bounding box that
     * encloses the Shape, only that the Shape lies entirely within the
     * indicated Rectangle2D. The bounding box returned by this method
     * is usually tighter than that returned by the getBounds method
     * and never fails due to overflow problems since the return value
     * can be an instance of the Rectangle2D that uses double precision
     * values to store the dimensions.
     */
    public final Rectangle2D getBounds2D() {
        return path.getBounds2D();
    }
    
    
    /**
     * <P>Returns an iterator object that iterates along the Shape
     * boundary and provides access to the geometry of the Shape outline.</P>
     *
     * <P>If an optional AffineTransform  is specified, the coordinates
     * returned in the iteration are transformed accordingly.</P>
     *
     * <P>Each call to this method returns a fresh PathIterator object
     * that traverses the geometry of the Shape object independently
     * from any other PathIterator objects in use at the same time.</P>
     *
     * @param at the optional transform to apply to the PathIterator
     */
    public final PathIterator getPathIterator(AffineTransform at) {
        return path.getPathIterator(at);
    }
    
    
    /**
     * <P>Returns an iterator object that iterates along the Shape
     * boundary and provides access to a flattened view of the Shape
     * outline geometry.</P>
     *
     * <P>Only SEG_MOVETO, SEG_LINETO, and SEG_CLOSE point types are
     * returned by the iterator.</P>
     *
     * <P>If an optional AffineTransform is specified, the coordinates
     * returned in the iteration are transformed accordingly.</P>
     *
     * <P>The amount of subdivision of the curved segments is controlled
     * by the flatness parameter, which specifies the maximum distance
     * that any point on the unflattened transformed curve can deviate
     * from the returned flattened path segments. Note that a limit on
     * the accuracy of the flattened path might be silently imposed,
     * causing very small flattening parameters to be treated as larger
     * values. This limit, if there is one, is defined by the particular
     * implementation that is used.</P>
     *
     * <P>Each call to this method returns a fresh PathIterator object
     * that traverses the Shape object geometry independently from any
     * other PathIterator objects in use at the same time.</P>
     *
     * @param at the optional transform to apply to the PathIterator
     * @param flatness the flatness to use to simplify the PathIterator
     */
    public final PathIterator getPathIterator(AffineTransform at, double flatness) {
        return path.getPathIterator(at, flatness);
    }
    
    
    /** Property Change. */
    
    /**
     * <P>Add a <CODE>PropertyChangeListener</CODE> to the listener list.
     * The listener is registered for all properties.</P>
     *
     * @param listener the PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(listener);
    }

    /**
     * <P>Add a <CODE>PropertyChangeListener</CODE> to the listener list for a
     * specific property.  The listener will be invoked only when a call on
     * <CODE>firePropertyChange</CODE> names that specific property.</P>
     *
     * @param propertyName the name of the property to listen on 
     * @param listener the PropertyChangeListener to be added
     */
    public final void addPropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.addPropertyChangeListener(propertyName, listener);
    }

    /**
     * <P>Add all items in the given <CODE>PropertyChangeListener</CODE> array
     * to the listener list.  These items are registered for all properties.</P>
     *
     * @param listeners the PropertyChangeListener array to be added
     */
    public void addPropertyChangeListeners(PropertyChangeListener[] listeners)
    {
        if (listeners == null)
            return;
        
        int length = listeners.length;
        
        for (int i = 0; i < length; i++)
            addPropertyChangeListener(listeners[i]);
    }
    
    
    /**
     * <P>Add all items in the given <CODE>PropertyChangeListener</CODE> array
     * to the listener list for a specific property.  These items will be invoked
     * only when a call on <CODE>firePropertyChange</CODE> names that specific
     * property.</P>
     *
     * @param listeners the PropertyChangeListener array to be added
     */
    public void addPropertyChangeListeners(
        String propertyName,
        PropertyChangeListener[] listeners)
    {
        if (listeners == null)
            return;
        
        int length = listeners.length;
        
        for (int i = 0; i < length; i++)
            addPropertyChangeListener(propertyName, listeners[i]);
    }
    
    
    /**
     * <P>Remove a <CODE>PropertyChangeListener</CODE> from the listener list.
     * This removes a <CODE>PropertyChangeListener</CODE> that was registered
     * for all properties.</P>
     *
     * @param listener the PropertyChangeListener to be removed
     */
    public final void removePropertyChangeListener(
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(listener);
    }

    /**
     * <P>Remove a <CODE>PropertyChangeListener</CODE> for a specific property.</P>
     *
     * @param propertyName the name of the property that was listened on 
     * @param listener the PropertyChangeListener to be removed
     */
    public final void removePropertyChangeListener(
        String propertyName,
        PropertyChangeListener listener) 
    {
        changeAdapter.removePropertyChangeListener(propertyName, listener);
    }
    
    
    /**
     * <P>Returns an array of all listeners that were added to this object.</P>
     *
     * @return an array with all listeners
     */
    public final PropertyChangeListener[] getPropertyChangeListeners() {
        return changeAdapter.getPropertyChangeListeners();
    }
    
    
    /**
     * <P>Returns an array of all listeners that were added to this object
     * and associated with the named property.</P>
     *
     * @param propertyName the name of the property to seek 
     */
    public final PropertyChangeListener[]
        getPropertyChangeListeners(String propertyName)
    {
        return changeAdapter.getPropertyChangeListeners(propertyName);
    }
    
    
    /**
     * <P>Check if there are any listeners for a specific property.</P>
     *
     * @param propertyName the name of the property to check 
     */
    public final boolean hasListeners(String propertyName) {
        return changeAdapter.hasListeners(propertyName);
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal and non-null.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        Object oldValue,
        Object newValue)
    {
        changeAdapter.firePropertyChange(propertyName, oldValue, newValue);
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        boolean oldValue,
        boolean newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange(propertyName, oldValue, newValue);
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        char oldValue,
        char newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Character(oldValue), new Character(newValue));
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        byte oldValue,
        byte newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Byte(oldValue), new Byte(newValue));
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        short oldValue,
        short newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Short(oldValue), new Short(newValue));
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        int oldValue,
        int newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange(propertyName, oldValue, newValue);
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        long oldValue,
        long newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Long(oldValue), new Long(newValue));
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        float oldValue,
        float newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Float(oldValue), new Float(newValue));
    }
    
    
    /**
     * <P>Report a bound property update to any registered listeners.
     * No event is fired if the old and new values are equal.</P>
     *
     * @param propertyName the programmatic name of the property that was changed
     * @param oldValue the old value of the property
     * @param newValue the new value of the property
     */
    public final void firePropertyChange(
        String propertyName,
        double oldValue,
        double newValue)
    {
        if (newValue != oldValue)
            changeAdapter.firePropertyChange
                (propertyName, new Double(oldValue), new Double(newValue));
    }
    
    
    /**
     * <P>Fire an existing <CODE>PropertyChangeEvent</CODE> to any registered
     * listeners.  No event is fired if the given event's old and new values
     * are equal and non-null.</P>
     *
     * @param evt the PropertyChangeEvent object
     */
    public final void firePropertyChange(PropertyChangeEvent evt) {
        changeAdapter.firePropertyChange(evt);
    }
    
    
    /**
     * <P>Returns the <CODE>PropertyChangeForwardingListener</CODE> that
     * will forward the property change events it receives to this object.</P>
     *
     * @return the forwarding listener
     */
    public final PropertyChangeForwardingListener getForwardingListener() {
        return forwardingListener;
    }
    

    /**
     * Add the forwarding listener as a property change listener
     * for the given object if the object supports property change.
     *
     * @param object the object that should add the forwarding listener
     */
    public final void addForwardingListener(Object object) {
        if (object instanceof SupportsPropertyChange) {
            SupportsPropertyChange spc = (SupportsPropertyChange) object;
            spc.addPropertyChangeListener(getForwardingListener());
        }
    }
    
    
    /**
     * Remove the forwarding listener as a property change listener
     * for the given object if the object supports property change.
     *
     * @param object the object that should remove the forwarding listener
     */
    public final void removeForwardingListener(Object object) {
        if (object instanceof SupportsPropertyChange) {
            SupportsPropertyChange spc = (SupportsPropertyChange) object;
            spc.removePropertyChangeListener(getForwardingListener());
        }
    }
    
    
    /**
     * Remove the forwarding listener as a property change listener
     * for the old object if the old object supports property change
     * and add the forwarding listener as a property change listener
     * for the new object if the new object supports property change.
     *
     * @param oldobject the old object that should remove the forwarding listener
     * @param newobject the new object that should add the forwarding listener
     */
    public final void removeAndAddForwardingListener(Object oldobject, Object newobject)
    {
        removeForwardingListener(oldobject);
        addForwardingListener(newobject);
    }    
    
}
