/*
 * @(#)BaseShape.java    2.4.0   8 September 2005
 *
 * Copyright 2005
 * 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 cubic 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:</p>
 *
 * <ul>
 *   <li><code>BaseVertexShape</code></li>
 *   <li><code>PolygonShape</code></li>
 *   <li><code>PolygonDotsShape</code></li>
 *   <li><code>AutomaticShape</code></li>
 *   <li><code>AutomaticCurve</code></li>
 *   <li><code>TweakableShape</code></li>
 *   <li><code>TweakableCurve</code></li>
 * </ul>
 *
 * <p>Class <code>BaseShape</code> may not be instantiated directly since it
 * does not have a public constructor.</p>
 *
 * <p>In 2.4.0, the restriction that the internal vertex and tangent array must be
 * non-<code>null</code> and of form <code>float[N][2]</code> for the same N was
 * <i>weakened</i> to permit the tangent array to be <code>null</code>.  It is
 * still the case that the vertex array must be non-<code>null</code> and of form
 * <code>float[N][2]</code> and that if the tangent array is non-<code>null</code>
 * then it must have the same form.  The relaxation was introduced to support
 * the derived classes whose <code>Path.Strategy</code> does not use tangents and
 * for which creation of a tangent array is unnecessary.</p>
 *
 * <p>If the internal tangent array is <code>null</code> and a tangent is requested
 * by some method then it is treated as <code>[0;0]</code>.</p>
 *
 * @author  Richard Rasala
 * @version 2.4.0
 * @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
     * <code>float[N][2]</code>.</p>
     *
     * <p>The initial default is <code>new float[0][2]</code>.</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 either
     * <code>null</code> or that it has the form
     * <code>float[N][2]</code> for the same N as in the vertex array.</p>
     *
     * <p>The requirement on the tangent array to permit <code>null</code>
     * was introduced in 2.4.0.</p>
     *
     * <p>The initial default is <code>null</code>.</p>
     */
    protected float[][] tangent = null;
    
    
    /** The <code>GeneralPath</code> used to implement the <code>Shape</code>. */
    private GeneralPath path = new GeneralPath(GeneralPath.WIND_NON_ZERO);
    
    
    /**
     * <p>The <code>Path.Strategy</code>.</p>.
     *
     * <p>The initial default is <code>Path.POLYGON</code>.</p>
     */
    private Path.Strategy pathstrategy = Path.POLYGON;
    
    
    /**
     * <p>The <code>ClosureMode</code>: OPEN or CLOSED.
     *
     * <p>The initial default is <code>ClosureMode.CLOSED</code>.</p>
     */
    private ClosureMode closuremode = ClosureMode.CLOSED;
    
    
    /**
     * <p>The <code>WindingRule</code>: WIND_NON_ZERO or WIND_EVEN_ODD.
     *
     * <p>The initial default is <code>WindingRule.WIND_NON_ZERO</code>.</p>
     */
    private WindingRule windingrule = WindingRule.WIND_NON_ZERO;
    
    
    /**
     * <p>The main listener for this <code>BaseShape</code> 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 <code>BaseShape</code> object
     * to implement the interface <code>SupportsPropertyChange</code>.</p>
     */
    private final PropertyChangeForwardingListener forwardingListener
        = new PropertyChangeForwardingListener(this);
    

    /**
     * <p>The protected default constructor.</p>
     *
     * <p>The default settings are as follows:</p>
     *
     * <ul>
     *   <li>Vertex data:      <code>new float[0][2]</code></li>
     *   <li>Tangent data:     <code>null</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 the length N of the vertex data; if the tangent data
     * is non-<code>null</code> then this data has the same length.
     */
    public final int length() {
        return vertex.length;
    }
    
    
    /**
     * Returns true if the internal tangent data is <code>null</code>.
     */
    public final boolean isTangentNull() {
        return tangent == null;
    }
    
    
    /**
     * Returns the x-coordinate of the vertex data if the given index
     * is in bounds or 0 if the index is out of bounds.
     *
     * @param index the index of the vertex
     */
    public final float getVX(int index) {
        int N = vertex.length;
        
        if ((index < 0) || (index >= N))
            return 0;
        
        return vertex[index][0];
    }
    
    
    /**
     * Returns the y-coordinate of the vertex data if the given index
     * is in bounds or 0 if the index is out of bounds.
     *
     * @param index the index of the vertex
     */
    public final float getVY(int index) {
        int N = vertex.length;
        
        if ((index < 0) || (index >= N))
            return 0;
        
        return vertex[index][1];
    }
    
    
    /**
     * <p>Returns a copy of the vertex at the given index.</p>
     *
     * <p>More precisely, returns:</p>
     *
     * <pre>  new float[] { getVX(index), getVY(index) }</pre>
     *
     * <p>In particular, returns <code>[0;0]</code> if the given
     * index is out of bounds.</p>
     */
    public final float[] getVertex(int index) {
        return new float[] { getVX(index), getVY(index) };
    }
    
    
    /**
     * Returns the x-coordinate of the tangent data if the given index
     * is in bounds or 0 if the index is out of bounds or if the
     * tangent data is <code>null</code>.
     *
     * @param index the index of the tangent
     */
    public final float getTX(int index) {
        if (tangent == null)
            return 0;
        
        int N = vertex.length;
        
        if ((index < 0) || (index >= N))
            return 0;
        
        return tangent[index][0];
    }
    
    
    /**
     * Returns the y-coordinate of the tangent data if the given index
     * is in bounds or 0 if the index is out of bounds or if the
     * tangent data is <code>null</code>.
     *
     * @param index the index of the tangent
     */
    public final float getTY(int index) {
        if (tangent == null)
            return 0;
        
        int N = vertex.length;
        
        if ((index < 0) || (index >= N))
            return 0;
        
        return tangent[index][1];
    }
    
    
    /**
     * <p>Returns a copy of the tangent at the given index.</p>
     *
     * <p>More precisely, returns:</p>
     *
     * <pre>  new float[] { getTX(index), getTY(index) }</pre>
     *
     * <p>In particular, returns <code>[0;0]</code> if the given
     * index is out of bounds or if the tangent data is
     * <code>null</code>.</p>
     */
    public final float[] getTangent(int index) {
        return new float[] { getTX(index), getTY(index) };
    }
    
    
    /**
     * <p>Returns the x-coordinate of the Bezier control point
     * that is <i>ahead</i> of the vertex at the given index.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  getVX(index) + getTX(index)</pre>
     *
     * @param index the index of the <i>ahead</i> control point
     */
    public final float getAX(int index) {
        return getVX(index) + getTX(index);
    }
    
    
    /**
     * <p>Returns the y-coordinate of the Bezier control point
     * that is <i>ahead</i> of the vertex at the given index.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  getVY(index) + getTY(index)</pre>
     *
     * @param index the index of the <i>ahead</i> control point
     */
    public final float getAY(int index) {
        return getVY(index) + getTY(index);
    }
    
    
    /**
     * <p>Returns a copy of the  Bezier control point
     * that is <i>ahead</i> of the vertex at the given index.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  new float[] { getAX(index), getAY(index) }</pre>
     *
     * @param index the index of the <i>ahead</i> control point
     */
    public final float[] getControlA(int index) {
        return new float[] { getAX(index), getAY(index) };
    }
    
    
    /**
     * <p>Returns the x-coordinate of the Bezier control point
     * that is <i>behind</i> the vertex at the given index.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  getVX(index) - getTX(index)</pre>
     *
     * @param index the index of the <i>behind</i> control point
     */
    public final float getBX(int index) {
        return getVX(index) - getTX(index);
    }
    
    
    /**
     * <p>Returns the y-coordinate of the Bezier control point
     * that is <i>behind</i> the vertex at the given index.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  getVY(index) - getTY(index)</pre>
     *
     * @param index the index of the <i>behind</i> control point
     */
    public final float getBY(int index) {
        return getVY(index) - getTY(index);
    }
    
    
    /**
     * <p>Returns a copy of the  Bezier control point
     * that is <i>behind</i> the vertex at the given index.</p>
     *
     * <p>Specifically, returns:</p>
     *
     * <pre>  new float[] { getBX(index), getBY(index) }</pre>
     *
     * @param index the index of the <i>behind</i> control point
     */
    public final float[] getControlB(int index) {
        return new float[] { getBX(index), getBY(index) };
    }
    
    
    /**
     * <p>Returns a copy of the combined vertex and tangent data
     * at the given index.</p>
     *
     * <p>More precisely, returns:</p>
     *
     * <pre>  new float[] { getVX(index), getVY(index), getTX(index), getTY(index) }</pre>
     *
     * <p>In particular, returns <code>[0;0]</code> if the given
     * index is out of bounds.</p>
     */
    public final float[] getVertexTangent(int index) {
        return new float[] { getVX(index), getVY(index), getTX(index), getTY(index) };
    }
    
    
    /**
     * <p>Returns a copy of the vertex data.</p>
     *
     * <p>The returned array will have the form <code>float[N][2]</code>
     * where N is the length.</p>
     *
     * <p>The row at index <code>i</code> will contain:</p>
     *
     * <pre>  getVertex(i)</pre>
     *
     * @return a copy of the vertex data
     */
    public final float[][] getVertexData() {
        int N = vertex.length;
        
        float[][] result = new float[N][];
        
        for (int i = 0; i < N; i++)
            result[i] = getVertex(i);
        
        return result;
    }
    
    
    /**
     * <p>Returns a copy of the tangent data.</p>
     *
     * <p>The returned array will have the form <code>float[N][2]</code>
     * where N is the length even if the internal tangent
     * array is <code>null</code>.</p>
     *
     * <p>The row at index <code>i</code> will contain:</p>
     *
     * <pre>  getTangent(i)</pre>
     *
     * <p>Use the method <code>isTangentNull()</code> to test
     * if the internal tangent array is <code>null</code>.</p>
     *
     * @return a copy of the tangent data
     */
    public final float[][] getTangentData() {
        int N = vertex.length;
        
        float[][] result = new float[N][];
        
        for (int i = 0; i < N; i++)
            result[i] = getTangent(i);
        
        return result;
    }
    
    
    /**
     * <p>Returns a copy of the vertex and tangent data.</p>
     *
     * <p>The returned array will have the form <code>float[N][4]</code>
     * where N is the length.</p>
     *
     * <p>The row at index <code>i</code> will contain:</p>
     *
     * <pre>  getVertexTangent(i)</pre>
     *
     * @return the a copy of the vertex and tangent data
     */
    public final float[][] getVertexTangentData() {
        int N = vertex.length;
        
        float[][] result = new float[N][];
        
        for (int i = 0; i < N; i++)
            result[i] = getVertexTangent(i);
        
        return result;
    }
    
    
    /**
     * <p>Returns an array <code>float[2]</code> with the coordinates of a
     * point on the closed 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&nbsp;==&nbsp;0</code>, return <code>[0;0]</code>.</p>
     *
     * <p>If <code>N&nbsp;==&nbsp;1</code> or
     * if <code>N&nbsp;&gt;&nbsp;1</code> and
     * either <code>t&nbsp;&lt;=&nbsp;0</code>
     * or <code>t&nbsp;&gt;=&nbsp;N</code>,
     * return <code>getVertex(0)</code>.</p>
     *
     * <p>Otherwise, let <code>t&nbsp;=&nbsp;a&nbsp;+&nbsp;f</code>
     * where <code>a</code> is an integer and
     * <code>0&nbsp;&lt;=&nbsp;f&nbsp;&lt;&nbsp;1</code>.
     * Let <code>b&nbsp;=&nbsp;a+1</code> modulo <code>N</code>.</p>
     *
     * <p>Then, return the linear interpolation between the points
     * <code>getVertex(a)</code> and <code>getVertex(b)</code>
     * corresponding to the parameter f.</p>
     *
     * @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 getVertex(0);
        
        int a = (int) t;
        float f = t - a;
        
        if (f == 0)
            return getVertex(a);
        
        int b = (a + 1) % N;
        
        float x0 = getVX(a);
        float x1 = getVX(b);
        
        float y0 = getVY(a);
        float y1 = getVY(b);
        
        return new float[] {
            Bezier.bezierF(f, x0, x1),
            Bezier.bezierF(f, y0, y1)
        };
    }
    
    
    /**
     * <p>Returns an array <code>float[2]</code> with the coordinates of a
     * point on the closed 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&nbsp;==&nbsp;0</code>, return <code>[0;0]</code>.</p>
     *
     * <p>If <code>N&nbsp;==&nbsp;1</code> or
     * if <code>N&nbsp;&gt;&nbsp;1</code> and
     * either <code>t&nbsp;&lt;=&nbsp;0</code>
     * or <code>t&nbsp;&gt;=&nbsp;N</code>,
     * return <code>getVertex(0)</code>.</p>
     *
     * <p>Otherwise, let <code>t&nbsp;=&nbsp;a&nbsp;+&nbsp;f</code>
     * where <code>a</code> is an integer and
     * <code>0&nbsp;&lt;=&nbsp;f&nbsp;&lt;&nbsp;1</code>.
     * Let <code>b&nbsp;=&nbsp;a+1</code> modulo <code>N</code>.</p>
     *
     * <p>Then, return the Bezier cubic point corresponding to the parameter
     * <code>f</code> and the 4 Bezier control points given below.</p>
     *
     * <ul>
     *   <li><code>getVertex(a)</code></li>
     *   <li><code>getVertex(a) + getTangent(a)</code>, that is,
     *       <code>getControlA(a)</code></li>
     *   <li><code>getVertex(b) - getTangent(b)</code>, that is,
     *       <code>getControlB(b)</code></li>
     *   <li><code>getVertex(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 getVertex(0);
        
        int a = (int) t;
        float f = t - a;
        
        if (f == 0)
            return getVertex(a);
        
        int b = (a + 1) % N;
        
        float x0 = getVX(a);
        float x3 = getVX(b);
        
        float x1 = x0 + getTX(a);
        float x2 = x3 - getTX(b);
        
        float y0 = getVY(a);
        float y3 = getVY(b);
        
        float y1 = y0 + getTY(a);
        float y2 = y3 - getTY(b);
        
        return new float[] {
            Bezier.bezierF(f, x0, x1, x2, x3),
            Bezier.bezierF(f, y0, y1, y2, y3)
        };
    }
    
    
    /**
     * <p>Returns the index of the first vertex
     * that is within epsilon of (x,y)
     * relative to the metric <code>Metric.MAX</code>;
     * returns -1 if no such vertex exists.</p>
     *
     * <p>The intended use of this method is to locate
     * a vertex
     * using an approximate mouse position.</p>
     *
     * @param x       the x-coordinate of the search point
     * @param y       the y-coordinate of the search point
     * @param epsilon the measure of closeness
     */
    public final int findVertex(double x, double y, double epsilon) {
        return findVertex(x, y, epsilon, Metric.MAX);
    }
    
    
    /**
     * <p>Returns the index of the first vertex
     * that is within epsilon of (x,y)
     * relative to the given metric;
     * returns -1 if no such vertex exists.</p>
     *
     * <p>If the given metric is <code>null</code>
     * then <code>Metric.MAX</code> is used.</p>
     *
     * <p>The intended use of this method is to locate
     * a vertex
     * using an approximate mouse position.</p>
     *
     * @param x       the x-coordinate of the search point
     * @param y       the y-coordinate of the search point
     * @param epsilon the measure of closeness
     * @param metric  the metric to do the distance computation
     */
    public final int findVertex(double x, double y, double epsilon, Metric metric) {
        if (metric == null)
            metric = Metric.MAX;
        
        int N = vertex.length;
        
        for (int i = 0; i < N; i++)
            if (metric.isNear(x, y, getVX(i), getVY(i), epsilon))
                return i;
        
        return -1;
    }
    
    
    /**
     * <p>Returns the index of the first ahead control point
     * that is within epsilon of (x,y)
     * relative to the metric <code>Metric.MAX</code>;
     * returns -1 if no such ahead control point exists.</p>
     *
     * <p>The intended use of this method is to locate
     * an ahead control point
     * using an approximate mouse position.</p>
     *
     * @param x       the x-coordinate of the search point
     * @param y       the y-coordinate of the search point
     * @param epsilon the measure of closeness
     */
    public final int findControlA(double x, double y, double epsilon) {
        return findControlA(x, y, epsilon, Metric.MAX);
    }
    
    
    /**
     * <p>Returns the index of the first ahead control point
     * that is within epsilon of (x,y)
     * relative to the given metric;
     * returns -1 if no such ahead control point exists.</p>
     *
     * <p>If the given metric is <code>null</code>
     * then <code>Metric.MAX</code> is used.</p>
     *
     * <p>The intended use of this method is to locate
     * an ahead control point
     * using an approximate mouse position.</p>
     *
     * @param x       the x-coordinate of the search point
     * @param y       the y-coordinate of the search point
     * @param epsilon the measure of closeness
     * @param metric  the metric to do the distance computation
     */
    public final int findControlA(double x, double y, double epsilon, Metric metric) {
        if (metric == null)
            metric = Metric.MAX;
        
        int N = vertex.length;
        
        for (int i = 0; i < N; i++)
            if (metric.isNear(x, y, getAX(i), getAY(i), epsilon))
                return i;
        
        return -1;
    }
    
    
    /**
     * <p>Returns the index of the first behind control point
     * that is within epsilon of (x,y)
     * relative to the metric <code>Metric.MAX</code>;
     * returns -1 if no such behind control point exists.</p>
     *
     * <p>The intended use of this method is to locate
     * a behind control point
     * using an approximate mouse position.</p>
     *
     * @param x       the x-coordinate of the search point
     * @param y       the y-coordinate of the search point
     * @param epsilon the measure of closeness
     */
    public final int findControlB(double x, double y, double epsilon) {
        return findControlB(x, y, epsilon, Metric.MAX);
    }
    
    
    /**
     * <p>Returns the index of the first behind control point
     * that is within epsilon of (x,y)
     * relative to the given metric;
     * returns -1 if no such behind control point exists.</p>
     *
     * <p>If the given metric is <code>null</code>
     * then <code>Metric.MAX</code> is used.</p>
     *
     * <p>The intended use of this method is to locate
     * a behind control point
     * using an approximate mouse position.</p>
     *
     * @param x       the x-coordinate of the search point
     * @param y       the y-coordinate of the search point
     * @param epsilon the measure of closeness
     * @param metric  the metric to do the distance computation
     */
    public final int findControlB(double x, double y, double epsilon, Metric metric) {
        if (metric == null)
            metric = Metric.MAX;
        
        int N = vertex.length;
        
        for (int i = 0; i < N; i++)
            if (metric.isNear(x, y, getBX(i), getBY(i), epsilon))
                return i;
        
        return -1;
    }
    
    
    /**
     * <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>
     *
     * <p>This method may be overridden in a derived class but only if the
     * following contract is observed:</p>
     *
     * <ul>
     *   <li>The derived class calls <code>super.setPathStrategy</code> in
     *       all of its constructors to select a specific strategy that
     *       will be the same for all objects and remain fixed.</li>
     *   <li>The override method <code>setPathStrategy</code> does nothing.</li>
     * </ul>
     *
     * <p>This contract permits the definition of derived classes that are
     * intended to support a specific <code>Path.Strategy</code>.</p>
     *
     * @param pathstrategy the path strategy to set
     */
    public 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);
    }
    
    
    /**
     * <p>Sets the winding rule via an int and makes a new
     * path.</p>
     *
     * <p><code>GeneralPath.WIND_NON_ZERO</code> corresponds
     * to <code>WindingRule.WIND_NON_ZERO</code>.</p>
     *
     * <p><code>GeneralPath.WIND_EVEN_ODD</code> corresponds
     * to <code>WindingRule.WIND_EVEN_ODD</code>.</p>
     *
     * <p>Ignores an invalid parameter.<p>
     *
     * <p>Fires property change: SET_WINDING_RULE.</p>
     *
     * @param rule the winding rule as an int
     */
    public final void setWindingRule(int rule) {
        if (rule == GeneralPath.WIND_NON_ZERO)
            setWindingRule(WindingRule.WIND_NON_ZERO);
        else
        if (rule == GeneralPath.WIND_EVEN_ODD)
            setWindingRule(WindingRule.WIND_EVEN_ODD);
    }
    
    
    /**
     * Get the winding rule.
     *
     * @return the winding rule
     */
    public final WindingRule getWindingRule() {
        return windingrule;
    }
    
    
    /**
     * <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];
        
        if (tangent != null)
            tangent = new float[0][2];
        
        makePath();
        
        firePropertyChange(REMOVE_SHAPE_DATA, null, null);
    }
    
    
    /**
     * <p>Returns a copy of the <code>GeneralPath</code> that defines this
     * <code>Shape</code> based on the vertex data, tangent data, path
     * strategy, closure mode, and winding rule.</p>
     *
     * <p>Specifically, calls <code>pathstrategy.makePath</code> using the
     * current path strategy and passing the current vertex data, tangent
     * data, closure mode, and winding rule.</p>
     */
    public final GeneralPath getPath() {
        return pathstrategy.makePath(vertex, tangent, closuremode, windingrule);
    }
    
    
    /**
     * <p>Make the path for the shape using the internal vertex and tangent data
     * and the path strategy, closure mode, and winding rule settings.</p>
     *
     * <p>May be overridden in a derived class if some settings such as the
     * tangent data must be computed before the path is constructed and set.</p>
     *
     * <p>To assist in a possible override, we reveal additional detail about
     * the implementation.</p>
     *
     * <p><code>BaseShape</code> maintains a private member <code>path</code> of
     * type <code>GeneralPath</code>.  The implementation of this method is:</p>
     *
     * <pre>  path = getPath();</pre>
     *
     * <p>An override method should make any necessary changes to the vertex data,
     * tangent data, closure mode, and winding rule, and then call:</p>
     *
     * <pre>  super.makePath();</pre>
     *
     * <p>Normally, an override method will change only the tangent data.</p>
     *
     * <p>An override method may not directly access the <code>path</code> member
     * variable since it is private.</p>
     */
    protected void makePath() {
        path = getPath();
    }
    
    
    /**
     * Returns a new closed <code>PolygonShape</code>
     * whose vertex data is a copy of the vertex data of this shape.
     */
    public final PolygonShape makeClosedPolygon() {
        return
            new PolygonShape
                (getVertexData(),
                 ClosureMode.CLOSED,
                 getWindingRule());
    }
    
    
    /**
     * Returns a new open <code>PolygonShape</code>
     * whose vertex data is a copy of the vertex data of this shape.
     */
    public final PolygonShape makeOpenPolygon() {
        return
            new PolygonShape
                (getVertexData(),
                 ClosureMode.OPEN,
                 getWindingRule());
    }
    
    
    /**
     * Returns a new <code>PolygonDotsShape</code>
     * whose vertex data is a copy of the vertex data of this shape.
     */
    public final PolygonDotsShape makePolygonDots() {
        return new PolygonDotsShape(getVertexData());
    }
    
    
    /**
     * Returns a new closed <code>PolygonShape</code>
     * whose vertex data is the closed Bezier frame corresponding
     * to the vertex and tangent data of this shape
     * and whose winding rule is the same as this shape.
     */
    public final PolygonShape makeClosedBezierFrame() {
        return
            new PolygonShape
                (getClosedBezierFrameData(),
                 ClosureMode.CLOSED,
                 getWindingRule());
    }
    
    
    /**
     * Returns a new open <code>PolygonShape</code>
     * whose vertex data is the open Bezier frame corresponding
     * to the vertex and tangent data of this shape
     * and whose winding rule is the same as this shape.
     */
    public final PolygonShape makeOpenBezierFrame() {
        return
            new PolygonShape(
                getOpenBezierFrameData(),
                ClosureMode.OPEN,
                getWindingRule());
    }
    
    
    /**
     * Returns a new <code>PolygonDotsShape</code>
     * whose vertex data is the closed Bezier control point list
     * corresponding to the vertex and tangent data of this shape.
     */
    public final PolygonDotsShape makeClosedControlDots() {
        return new PolygonDotsShape(getClosedBezierControlData());
    }
    
    
    /**
     * Returns a new <code>PolygonDotsShape</code>
     * whose vertex data is the open Bezier control point list
     * corresponding to the vertex and tangent data of this shape.
     */
    public final PolygonDotsShape makeOpenControlDots() {
        return new PolygonDotsShape(getOpenBezierControlData());
    }
    
    
    /**
     * Returns a new <code>TweakableShape</code>
     * whose vertex and tangent data is a copy of the corresponding
     * vertex and tangent data of this shape,
     * whose <code>Path.Strategy</code> is
     * <code>Path.BEZIER_TANGENT_SEGMENTS</code>,
     * and whose winding rule is the same as this shape.
     */
    public final TweakableShape makeBezierTangentSegments() {
        return
            new TweakableShape(
                getVertexData(),
                getTangentData(),
                Path.BEZIER_TANGENT_SEGMENTS,
                ClosureMode.OPEN,
                getWindingRule());
    }
    
    
    /**
     * <p>Returns the closed Bezier frame data corresponding to the
     * vertex and tangent data.</p>
     *
     * <p>If <code>N</code> is length of the vertex data
     * then the returned array will have the form
     * <code>float[3*N][2]</code>.</p>
     *
     * <p>If the internal tangent array is <code>null</code> then it
     * is treated as an array of zeroes.</p>
     *
     * <p>For each index <code>i</code> less than <code>N</code>
     * with <code>j=(i+1)%N</code>,
     * this method places the following three points into successive
     * cells in the result array:</p>
     *
     * <pre>  getVertex(i)</pre>
     * <pre>  getControlA(i)</pre>
     * <pre>  getControlB(j)</pre>
     *
     * @return the closed Bezier frame data
     */
    public final float[][] getClosedBezierFrameData() {
        int N = vertex.length;
        int M = 3 * N;
        
        float[][] result = new float[M][];
        
        for (int i = 0; i < N; i++) {
            int j = (i + 1) % N;
            
            int r = 3 * i;
            int s = r + 1;
            int t = s + 1;
            
            result[r] = getVertex(i);
            result[s] = getControlA(i);
            result[t] = getControlB(j);
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns the open Bezier frame data corresponding to the
     * vertex and tangent data.</p>
     *
     * <p>If <code>N</code> is length of the vertex data
     * then the returned array will have the form
     * <code>float[3*N-2][2]</code> if N is positive and
     * <code>float[0][2]</code> if N is zero.</p>
     *
     * <p>If the internal tangent array is <code>null</code> then it
     * is treated as an array of zeroes.</p>
     *
     * <p>For each index <code>i</code> less than <code>(N-1)</code>
     * with <code>j=(i+1)</code>,
     * this method places the following three points into successive
     * cells in the result array:</p>
     *
     * <pre>  getVertex(i)</pre>
     * <pre>  getControlA(i)</pre>
     * <pre>  getControlB(j)</pre>
     *
     * <p>The last point placed into the array is:</p>
     *
     * <pre>  getVertex(N-1)</pre>
     *
     * @return the open Bezier frame data
     */
    public final float[][] getOpenBezierFrameData() {
        int N = vertex.length;
        
        if (N == 0)
            return new float[0][2];
        
        int L = N - 1;
        int M = 3 * N - 2;
        
        float[][] result = new float[M][];
        
        for (int i = 0; i < L; i++) {
            int j = i + 1;
            
            int r = 3 * i;
            int s = r + 1;
            int t = s + 1;
            
            result[r] = getVertex(i);
            result[s] = getControlA(i);
            result[t] = getControlB(j);
        }
        
        result[M - 1] = getVertex(L);
        
        return result;
    }
    
    
    /**
     * <p>Returns the closed Bezier control points corresponding to
     * the vertex and tangent data.</p>
     *
     * <p>If <code>N</code> is length of the vertex data
     * then the returned array will have the form
     * <code>float[2*N][2]</code>.</p>
     *
     * <p>If the internal tangent array is <code>null</code> then it
     * is treated as an array of zeroes.</p>
     *
     * <p>For each index <code>i</code> less than <code>N</code>
     * with <code>j=(i+1)%N</code>,
     * this method places the following two points into successive
     * cells in the result array:</p>
     *
     * <pre>  getControlA(i)</pre>
     * <pre>  getControlB(j)</pre>
     *
     * @return the closed Bezier control points
     */
    public final float[][] getClosedBezierControlData() {
        int N = vertex.length;
        int M = 2 * N;
        
        float[][] result = new float[M][];
        
        for (int i = 0; i < N; i++) {
            int j = (i + 1) % N;
            
            int r = 2 * i;
            int s = r + 1;
            
            result[r] = getControlA(i);
            result[s] = getControlB(j);
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns the open Bezier control points corresponding to
     * the vertex and tangent data.</p>
     *
     * <p>If <code>N</code> is length of the vertex data
     * then the returned array will have the form
     * <code>float[2*N-2][2]</code> if N is greater than 1 and
     * <code>float[0][2]</code> if N is 0 or 1.</p>
     *
     * <p>If the internal tangent array is <code>null</code> then it
     * is treated as an array of zeroes.</p>
     *
     * <p>For each index <code>i</code> less than <code>(N-1)</code>
     * with <code>j=(i+1)</code>,
     * this method places the following two points into successive
     * cells in the result array:</p>
     *
     * <pre>  getControlA(i)</pre>
     * <pre>  getControlB(j)</pre>
     *
     * @return the open Bezier control points
     */
    public final float[][] getOpenBezierControlData() {
        int N = vertex.length;
        
        if (N <= 1)
            return new float[0][2];
        
        int L = N - 1;
        int M = 2 * N - 2;
        
        float[][] result = new float[M][];
        
        for (int i = 0; i < L; i++) {
            int j = i + 1;
            
            int r = 3 * i;
            int s = r + 1;
            
            result[r] = getControlA(i);
            result[s] = getControlB(j);
        }
        
        return result;
    }
    
    
    /**
     * <p>Returns the Bezier tangent segment data corresponding to the
     * vertex and tangent data.</p>
     *
     * <p>If <code>N</code> is length of the vertex data
     * then the returned array will have the form
     * <code>float[2*N][2]</code>.</p>
     *
     * <p>If the internal tangent array is <code>null</code> then it
     * is treated as an array of zeroes.</p>
     *
     * <p>For each index <code>i</code> less than <code>N</code>,
     * this method places the following two points into successive
     * cells in the result array:</p>
     *
     * <pre>  getControlB(i)</pre>
     * <pre>  getControlA(i)</pre>
     *
     * @return the Bezier tangent segment data
     */
    public final float[][] getBezierTangentSegmentData() {
        int N = vertex.length;
        int M = 2 * N;
        
        float[][] result = new float[M][];
        
        for (int i = 0; i < N; i++) {
            int r = 2 * i;
            int s = r + 1;
            
            result[r] = getControlB(i);
            result[s] = getControlA(i);
        }
        
        return result;
    }
    
    
    /**
     * <p>Static method that
     * returns a float array that contains the points on the closed
     * Bezier frame for the given vertex and tangent arrays.</p>
     *
     * <p>Precondition: For some integer N:</p>
     *
     * <ul>
     *   <li>vertex  is <code>float[N][2]</code></li>
     *   <li>tangent is <code>float[N][2]</code> or <code>null</code></li>
     * </ul>
     * 
     * <p>If the precondition holds, a new <code>BaseShape</code> is
     * constructed and this method then returns the result of
     * <code>getClosedBezierFrameData</code> on that shape.</p>
     *
     * <p>If the precondition fails, then <code>float[0][2]</code> is returned.</p>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the closed Bezier frame points
     */
    public static float[][] closedBezierFramePoints
        (float[][] vertex, float[][] tangent)
    {
        if (tangent == null) {
            if (! FloatArray.checkArray(vertex, 2))
                return new float[0][2];
        }
        else {
            if (! FloatArray.checkArrayPair(vertex, tangent, 2))
                return new float[0][2];
        }
        
        BaseShape shape = new BaseShape();
        
        shape.vertex  = vertex;
        shape.tangent = tangent;
        
        return shape.getClosedBezierFrameData();
    }
    
    
    /**
     * <p>Static method that
     * returns a float array that contains the points on the open
     * Bezier frame for the given vertex and tangent arrays.</p>
     *
     * <p>Precondition: For some integer N:</p>
     *
     * <ul>
     *   <li>vertex  is <code>float[N][2]</code></li>
     *   <li>tangent is <code>float[N][2]</code> or <code>null</code></li>
     * </ul>
     * 
     * <p>If the precondition holds, a new <code>BaseShape</code> is
     * constructed and this method then returns the result of
     * <code>getOpenBezierFrameData</code> on that shape.</p>
     *
     * <p>If the precondition fails, then <code>float[0][2]</code> is returned.</p>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the open Bezier frame points
     */
    public static float[][] openBezierFramePoints
        (float[][] vertex, float[][] tangent)
    {
        if (tangent == null) {
            if (! FloatArray.checkArray(vertex, 2))
                return new float[0][2];
        }
        else {
            if (! FloatArray.checkArrayPair(vertex, tangent, 2))
                return new float[0][2];
        }
        
        BaseShape shape = new BaseShape();
        
        shape.vertex  = vertex;
        shape.tangent = tangent;
        
        return shape.getOpenBezierFrameData();
    }
    
    
    /**
     * <p>Static method that
     * returns a float array that contains the Bezier control
     * points for the closed curve with the given vertex and
     * tangent arrays.</p>
     *
     * <p>Precondition: For some integer N:</p>
     *
     * <ul>
     *   <li>vertex  is <code>float[N][2]</code></li>
     *   <li>tangent is <code>float[N][2]</code> or <code>null</code></li>
     * </ul>
     * 
     * <p>If the precondition holds, a new <code>BaseShape</code> is
     * constructed and this method then returns the result of
     * <code>getClosedBezierControlData</code> on that shape.</p>
     *
     * <p>If the precondition fails, then <code>float[0][2]</code> is returned.</p>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the closed Bezier control points
     */
    public static float[][] closedBezierControlPoints
        (float[][] vertex, float[][] tangent)
    {
        if (tangent == null) {
            if (! FloatArray.checkArray(vertex, 2))
                return new float[0][2];
        }
        else {
            if (! FloatArray.checkArrayPair(vertex, tangent, 2))
                return new float[0][2];
        }
        
        BaseShape shape = new BaseShape();
        
        shape.vertex  = vertex;
        shape.tangent = tangent;
        
        return shape.getClosedBezierControlData();
    }
    
    
    /**
     * <p>Static method that
     * returns a float array that contains the Bezier control
     * points for the open curve with the given vertex and
     * tangent arrays.</p>
     *
     * <p>Precondition: For some integer N:</p>
     *
     * <ul>
     *   <li>vertex  is <code>float[N][2]</code></li>
     *   <li>tangent is <code>float[N][2]</code> or <code>null</code></li>
     * </ul>
     * 
     * <p>If the precondition holds, a new <code>BaseShape</code> is
     * constructed and this method then returns the result of
     * <code>getOpenBezierControlData</code> on that shape.</p>
     *
     * <p>If the precondition fails, then <code>float[0][2]</code> is returned.</p>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the open Bezier control points
     */
    public static float[][] openBezierControlPoints
        (float[][] vertex, float[][] tangent)
    {
        if (tangent == null) {
            if (! FloatArray.checkArray(vertex, 2))
                return new float[0][2];
        }
        else {
            if (! FloatArray.checkArrayPair(vertex, tangent, 2))
                return new float[0][2];
        }
        
        BaseShape shape = new BaseShape();
        
        shape.vertex  = vertex;
        shape.tangent = tangent;
        
        return shape.getOpenBezierControlData();
    }
    
    
    /**
     * <p>Static method that
     * returns a float array that contains the Bezier tangent
     * segments for the given vertex and tangent arrays.</p>
     *
     * <p>Precondition: For some integer N:</p>
     *
     * <ul>
     *   <li>vertex  is <code>float[N][2]</code></li>
     *   <li>tangent is <code>float[N][2]</code> or <code>null</code></li>
     * </ul>
     * 
     * <p>If the precondition holds, a new <code>BaseShape</code> is
     * constructed and this method then returns the result of
     * <code>getBezierTangentSegmentData</code> on that shape.</p>
     *
     * <p>If the precondition fails, then <code>float[0][2]</code> is returned.</p>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the Bezier tangent segments
     */
    public static float[][] bezierTangentSegmentPoints
        (float[][] vertex, float[][] tangent)
    {
        if (tangent == null) {
            if (! FloatArray.checkArray(vertex, 2))
                return new float[0][2];
        }
        else {
            if (! FloatArray.checkArrayPair(vertex, tangent, 2))
                return new float[0][2];
        }
        
        BaseShape shape = new BaseShape();
        
        shape.vertex  = vertex;
        shape.tangent = tangent;
        
        return shape.getBezierTangentSegmentData();
    }
    
    
    /** 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 transform to apply to the path
     */
    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 transform to apply to the path
     * @param flatness the flatness to use to simplify the path
     */
    public final PathIterator getPathIterator(AffineTransform at, double flatness) {
        return path.getPathIterator(at, flatness);
    }
    
    
    /**
     * <p>Returns a new <code>Shape</code> obtained from this object by
     * using the path iterator corresponding to the given transform.</p>
     *
     * @param at the transform to apply to the path
     */
    public final Shape getTransformedShape(AffineTransform at) {
        GeneralPath newpath = new GeneralPath(windingrule.rule());
        newpath.append(getPathIterator(at), false);
        return newpath;
    }
    
    
    /**
     * <p>Returns a new <code>Shape</code> obtained from this object by
     * using the path iterator corresponding to the given transform
     * and flatness.</p>
     *
     * @param at the transform to apply to the path
     * @param flatness the flatness to use to simplify the path
     */
    public final Shape getTransformedShape(AffineTransform at, double flatness) {
        GeneralPath newpath = new GeneralPath(windingrule.rule());
        newpath.append(getPathIterator(at, flatness), false);
        return newpath;
    }
    
    
    /** 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);
    }    
    
}
