/*
 * @(#)PathList.java    2.4.0   15 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.*;
import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import java.io.*;
import java.util.*;
import java.text.ParseException;

/**
 * <p>The <code>PathList</code> class combines an explicit sequence
 * of <code>PathNode</code> objects together with a winding rule to
 * provide a flexible alternative to the Java class
 * <code>GeneralPath</code>.</p>
 *
 * <p>A <code>PathList</code> may be constructed or modifed using a list of
 * <code>PathNode</code> objects or a <code>Shape</code> or
 * a <code>PathIterator</code>.</p>
 *
 * <p>Conversely, from a <code>PathList</code>, one can construct a
 * <code>Shape</code> or a <code>PathIterator</code>.</p>
 *
 * <p>Unlike <code>GeneralPath</code>, <code>PathList</code> does not
 * implement <code>Shape</code> directly since we separate the aspects of
 * collecting the path node data from the shape issues.  This class provides
 * methods to create various <code>Shape</code> objects as desired.</p>
 *
 * <p><code>PathList</code> utilizes the Java class <code>GeneralPath</code>
 * for the implementation of the construction of a <code>Shape</code> given
 * the data in this class.</p>
 *
 * <p>In construction of a <code>Shape</code> via <code>GeneralPath</code>,
 * Java requires that the start node have type MOVE.  If this condition
 * does not hold, Java throws <code>IllegalPathStateException</code>.  In
 * this class, the requirement that the start node have type MOVE is tested
 * by the method <code>isValid()</code>.  If this method returns false and
 * the user calls <code>makeShape</code>, an empty <code>Shape</code> will
 * be returned by this call rather than throwing an exception.</p>
 */
public class PathList
    implements Stringable
{
    /** Shorthand constant for PathIterator.SEG_MOVETO. */
    public static final int MOVE  = PathIterator.SEG_MOVETO;
    
    /** Shorthand constant for PathIterator.SEG_LINETO. */
    public static final int LINE  = PathIterator.SEG_LINETO;
    
    /** Shorthand constant for PathIterator.SEG_QUADTO. */
    public static final int QUAD  = PathIterator.SEG_QUADTO;
    
    /** Shorthand constant for PathIterator.SEG_CUBICTO. */
    public static final int CUBIC = PathIterator.SEG_CUBICTO;
    
    /** Shorthand constant for PathIterator.SEG_CLOSE. */
    public static final int CLOSE = PathIterator.SEG_CLOSE;
    
    
    /** Constant used to contruct stroke. */
    private static final int CAP_ROUND  = BasicStroke.CAP_ROUND;
    
    /** Constant used to contruct stroke. */
    private static final int JOIN_ROUND = BasicStroke.JOIN_ROUND;
    
    
    /** The standard error message for fromStringData. */
    public static final String standardMessage =
        "\nPathList error\n"
        + "Line 1 should be either of the 2 strings\n"
        + "WIND_NON_ZERO or WIND_EVEN_ODD\n\n"
        + "The remaining lines should give the data\n"
        + "for the path nodes with one node per line\n";
    
    /** The String constant for WindingRule.WIND_NON_ZERO. */
    public static final String String_WIND_NON_ZERO =
        "WIND_NON_ZERO";
    
    /** The String constant for WindingRule.WIND_EVEN_ODD. */
    public static final String String_WIND_EVEN_ODD =
        "WIND_EVEN_ODD";
    
    
    /** The path list. */
    protected Vector pathlist = new Vector();
    
    /** The winding rule as an object. */
    protected WindingRule windingrule = WindingRule.WIND_NON_ZERO;
    
    
    /** The file filter for file I/O that restricts to .txt files. */
    protected final FileExtensionFilter textFileFilter =
        new FileExtensionFilter("txt");
    
    /** Initialization for home directory as directory ".". */
    protected final File home = new File(".");
    
    /** The file chooser used for file I/O. */
    protected final JFileChooser filechooser = new JFileChooser();
    
    
    /**
     * The default constructor that sets the path list to empty
     * and the winding rule to WindingRule.WIND_NON_ZERO.
     */
    public PathList() {
        filechooser.setFileFilter(textFileFilter);
        filechooser.setCurrentDirectory(home);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the given array of path nodes
     * and sets the winding rule to WindingRule.WIND_NON_ZERO.</p>
     *
     * @param nodes the nodes to add to the path list
     */
    public PathList(PathNode[] nodes) {
        this();
        append(nodes);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the given array of path nodes and the given winding rule.</p>
     *
     * @param nodes the nodes to add to the path list
     * @param rule the winding rule as an object
     */
    public PathList
        (PathNode[] nodes, WindingRule rule)
    {
        this();
        append(nodes);
        setWindingRule(rule);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the given array of path nodes and the given winding rule.</p>
     *
     * @param nodes the nodes to add to the path list
     * @param rule the winding rule as an int
     */
    public PathList
        (PathNode[] nodes, int rule)
    {
        this();
        append(nodes);
        setWindingRule(rule);
    }
    
    
    /**
     * <p>The constructor that makes a deep clone of an existing
     * path list.</p>
     *
     * @param list the path list to clone
     */
    public PathList(PathList list) {
        this();
        setPathList(list);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the path nodes and winding rule obtained from the given
     * <code>PathIterator</code>.</p>
     *
     * <p>The constructor must execute the iteration so when it is
     * complete then <code>iterator.isDone()</code> will be true.</p>
     *
     * @param iterator the iterator whose data is cloned to this list
     */
    public PathList(PathIterator iterator) {
        this();
        setPathList(iterator);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the path nodes and winding rule obtained from the given
     * <code>Shape</code>.</p>
     *
     * @param shape the shape whose data is cloned to this list
     */
    public PathList(Shape shape) {
        this();
        setPathList(shape);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the path nodes and winding rule obtained from the given
     * <code>Shape</code>
     * after applying the given transform
     * to the shape data.</p>
     *
     * @param shape the shape whose data is used to make this list
     * @param transform the transform to apply to the shape data
     */
    public PathList
        (Shape shape, AffineTransform transform)
    {
        this();
        setPathList(shape, transform);
    }
    
    
    /**
     * <p>The constructor that initializes the path list using
     * the path nodes and winding rule obtained from the given
     * <code>Shape</code>
     * after applying the given transform and flatness
     * to the shape data.</p>
     *
     * @param shape the shape whose data is used to make this list
     * @param transform the transform to apply to the shape data
     * @param flatness the flatness to require
     */
    public PathList
        (Shape shape, AffineTransform transform, double flatness)
    {
        this();
        setPathList(shape, transform, flatness);
    }
    
    
    /**
     * <p>The constructor that makes a polygon path list using
     * the given array points of point data; the boolean close
     * determines whether or the polygon is open or closed.</p>
     *
     * <p>Does nothing if points is <code>null</code>.</p>
     *
     * <p>Ignores <code>null</code> data items in points.</p>
     *
     * <p>The path list is constructed by doing MOVE to the
     * first point and LINE to all succeeding points.  If the
     * boolean close is true then CLOSE is appended at the end.</p>
     *
     * @param points the polygon points
     * @param close whether or not to close the polygon
     */
    public PathList(Point2D[] points, boolean close) {
        this();
        
        if (points == null)
            return;
        
        int N = points.length;
        
        boolean first = true;
        
        for (int i = 0; i < N; i++) {
            if (points[i] == null)
                continue;
            
            int type = first ? MOVE : LINE;
            
            float x = (float) points[i].getX();
            float y = (float) points[i].getY();
            
            append(new PathNode(type, x, y, 0, 0, 0, 0));
            
            first = false;
        }
        
        if (close)
            append(new PathNode(CLOSE, 0, 0, 0, 0, 0, 0));
    }
    
    
    /**
     * <p>The constructor that makes a polygon path list using
     * the given array points of point data; the boolean close
     * determines whether or the polygon is open or closed;
     * the given winding rule determines the winding rule.</p>
     *
     * <p>Utilizes <code>PathList(Point2D[], boolean)</code>.</p>
     *
     * @param points the polygon points
     * @param close whether or not to close the polygon
     * @param rule the winding rule as an object
     */
    public PathList
        (Point2D[] points, boolean close, WindingRule rule)
    {
        this(points, close);
        setWindingRule(rule);
    }
    
    
    /**
     * <p>The constructor that makes a polygon path list using
     * the given array points of point data; the boolean close
     * determines whether or the polygon is open or closed;
     * the given winding rule determines the winding rule.</p>
     *
     * <p>Utilizes <code>PathList(Point2D[], boolean)</code>.</p>
     *
     * @param points the polygon points
     * @param close whether or not to close the polygon
     * @param rule the winding rule as an int
     */
    public PathList
        (Point2D[] points, boolean close, int rule)
    {
        this(points, close);
        setWindingRule(rule);
    }
    
    
    /**
     * <p>Factory method that returns a new <code>Shape</code> based on the
     * current data in this path list.  The <code>Shape</code> is constructed
     * using the Java class <code>GeneralPath</code>.<p>
     *
     * <p>In construction of a <code>Shape</code> via <code>GeneralPath</code>,
     * Java requires that the start node have type MOVE.  If this condition
     * does not hold, Java throws <code>IllegalPathStateException</code>.  In
     * this class, the requirement that the start node have type MOVE is tested
     * by the method <code>isValid()</code>.  If this method returns false and
     * the user calls <code>makeShape</code>, an empty <code>Shape</code> will
     * be returned by this call rather than throwing an exception.</p>
     */
    public final Shape makeShape() {
        GeneralPath gp = new GeneralPath(getWindingRule().rule());
        
        if (! isValid())
            return gp;
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                    gp.moveTo(node.getX1(), node.getY1());
                    break;
                
                case LINE:
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case QUAD:
                    gp.quadTo
                        (node.getX1(), node.getY1(),
                         node.getX2(), node.getY2());
                    break;
                
                case CUBIC:
                    gp.curveTo
                        (node.getX1(), node.getY1(),
                         node.getX2(), node.getY2(),
                         node.getX3(), node.getY3());
                    break;
                
                case CLOSE:
                    gp.closePath();
                    break;
                
                default:
                    break;
            }
        }
        
        return gp;
    }
    
    
    /**
     * <p>Factory method that returns a new <code>PathListIterator</code>
     * based on the current data in this path list.<p>
     */
    public final PathListIterator makePathListIterator() {
        return new PathListIterator(this);
    }
    
    
    /**
     * <p>Returns a <code>PaintableSequence</code> whose components
     * show the structure of the <code>Shape</code> associated with
     * this <code>PathList</code>.</p>
     *
     * <p>Each component in the sequence corresponds to one of the
     * <code>Color</code> parameters and if a particular parameter
     * is <code>null</code> or has an alpha value of 0 then that
     * particular component is omitted from the sequence.<p>
     *
     * <p>The components are inserted into the sequence from top to
     * bottom as follows.</p>
     *
     * <p>At the top, the dots corresponding to the Bezier control
     * points are inserted as an internal paintable sequence.  Each
     * dot uses a <code>PointPaintable</code> with the control dots
     * color.</p>
     *
     * <p>Next, the dots corresponding to the vertex points are
     * inserted as an internal paintable sequence.  Each dot uses a
     * <code>PointPaintable</code> with the vertex dots color.</p>
     *
     * <p>Next, the shape constructed by <code>makeShape</code> is
     * inserted into the sequence as a <code>ShapePaintable</code> to
     * be drawn in the draw color.</p>
     *
     * <p>Next, the frame constructed by <code>makeBezierShape</code>
     * is inserted into the sequence as a <code>ShapePaintable</code>
     * to be drawn in the bezier frame color.</p>
     *
     * <p>Next, the frame constructed by <code>makeVertexShape</code>
     * is inserted into the sequence as a <code>ShapePaintable</code>
     * to be drawn in the vertex frame color.</p>
     *
     * <p>Finally, the shape constructed by <code>makeShape</code> is
     * inserted into the sequence as a <code>ShapePaintable</code> to
     * be filled in the fill color.</p>
     *
     * <p>This order of insertion ensures that the dots are on top,
     * the drawn shape is next, the frames are next, and the filled
     * shape is on the bottom.  This order gives the maximum amount
     * of information to the user of this method.</p>
     *
     * <p>The thickness controls the thickness of the drawing stroke.
     * Also, the <code>PointPaintable</code> for the dots uses a size
     * equal to (thickness+2).</p>
     *
     * @param fillColor        the fill color for the shape
     * @param drawColor        the draw color for the shape
     * @param vertexDotsColor  the color for the vertex dots
     * @param controlDotsColor the color for the control dots
     * @param vertexFrameColor the color for the vertex frame
     * @param bezierFrameColor the color for the bezier frame
     * @param thickness        the stroke thickness
     */
    public final PaintableSequence makeStructurePaintable
        (Color fillColor,
         Color drawColor,
         Color vertexDotsColor,
         Color controlDotsColor,
         Color vertexFrameColor,
         Color bezierFrameColor,
         int   thickness)
    {
        if (thickness < 1)
            thickness = 1;
        
        int epsilon = thickness + 2;
        
        BasicStroke stroke = new BasicStroke(thickness, CAP_ROUND, JOIN_ROUND);
        
        PlotMark plotmark = new PlotMark(PlotMarkAlgorithm.Square, epsilon, true);
        
        Shape mainshape = makeShape();
        
        Shape shape = null;
        Color color = null;
        Point2D[] points = null;
        
        PaintableSequence sequence = new PaintableSequence();
        
        
        color = controlDotsColor;
        
        if ((color != null) && (color.getAlpha() > 0)) {
            points = makeControlPoints();
            
            sequence.appendPaintable
                (PointPaintable.makePaintableSequence(points, plotmark, color));
        }
        
        
        color = vertexDotsColor;
        
        if ((color != null) && (color.getAlpha() > 0)) {
            points = makeVertexPoints();
            
            sequence.appendPaintable
                (PointPaintable.makePaintableSequence(points, plotmark, color));
        }
        
        
        color = drawColor;
        
        if ((color != null) && (color.getAlpha() > 0)) {
            shape = mainshape;
            sequence.appendPaintable
                (new ShapePaintable(shape, PaintMode.DRAW, null, color, stroke));
        }
        
        
        color = bezierFrameColor;
        
        if ((color != null) && (color.getAlpha() > 0)) {
            shape = makeBezierShape();
            sequence.appendPaintable
                (new ShapePaintable(shape, PaintMode.DRAW, null, color, stroke));
        }
        
        
        color = vertexFrameColor;
        
        if ((color != null) && (color.getAlpha() > 0)) {
            shape = makeVertexShape();
            sequence.appendPaintable
                (new ShapePaintable(shape, PaintMode.DRAW, null, color, stroke));
        }
        
        
        color = fillColor;
        
        if ((color != null) && (color.getAlpha() > 0)) {
            shape = mainshape;
            sequence.appendPaintable
                (new ShapePaintable(shape, PaintMode.FILL, color));
        }
        
        
        return sequence;
    }
    
    
    /**
     * <p>Returns a <code>PaintableSequence</code> whose components
     * show the structure of the <code>Shape</code> associated with
     * this <code>PathList</code>.</p>
     *
     * <p>Calls the more general method with the following defaults.</p>
     *
     * <pre>  vertexDotsColor:  Colors.red</pre>
     * <pre>  controlDotsColor: Colors.darkorange</pre>
     * <pre>  vertexFrameColor: Colors.transparent</pre>
     * <pre>  bezierFrameColor: Colors.darkorchid</pre>
     *
     * <p>In particular, with these defaults, the vertex frame is
     * not included in the <code>PaintableSequence</code>.</p>
     *
     * @param fillColor        the fill color for the shape
     * @param drawColor        the draw color for the shape
     * @param thickness        the stroke thickness
     */
    public final PaintableSequence makeStructurePaintable
        (Color fillColor,
         Color drawColor,
         int   thickness)
    {
         Color vertexDotsColor  = Colors.red;
         Color controlDotsColor = Colors.darkorange;
         Color vertexFrameColor = Colors.transparent;
         Color bezierFrameColor = Colors.darkorchid;
         
         return makeStructurePaintable
            (fillColor, drawColor, vertexDotsColor, controlDotsColor,
             vertexFrameColor, bezierFrameColor, thickness);
    }
    
    
    /**
     * <p>Returns a <code>PaintableSequence</code> whose components
     * show the structure of the <code>Shape</code> associated with
     * this <code>PathList</code>.</p>
     *
     * <p>Calls the more general method with the following defaults.</p>
     *
     * <pre>  fillColor:        Colors.lime</pre>
     * <pre>  drawColor:        Colors.black</pre>
     * <pre>  vertexDotsColor:  Colors.red</pre>
     * <pre>  controlDotsColor: Colors.darkorange</pre>
     * <pre>  vertexFrameColor: Colors.transparent</pre>
     * <pre>  bezierFrameColor: Colors.darkorchid</pre>
     *
     * <p>In particular, with these defaults, the vertex frame is
     * not included in the <code>PaintableSequence</code>.</p>
     *
     * @param thickness        the stroke thickness
     */
    public final PaintableSequence makeStructurePaintable(int thickness)
    {
        Color fillColor        = Colors.lime;
        Color drawColor        = Colors.black;
        Color vertexDotsColor  = Colors.red;
        Color controlDotsColor = Colors.darkorange;
        Color vertexFrameColor = Colors.transparent;
        Color bezierFrameColor = Colors.darkorchid;
         
        return makeStructurePaintable
            (fillColor, drawColor, vertexDotsColor, controlDotsColor,
             vertexFrameColor, bezierFrameColor, thickness);
    }
    
    
    /**
     * <p>Returns a <code>PaintableSequence</code> whose components
     * show the structure of the <code>Shape</code> associated with
     * this <code>PathList</code>.</p>
     *
     * <p>Calls the more general method with the following defaults.</p>
     *
     * <pre>  fillColor:        Colors.lime</pre>
     * <pre>  drawColor:        Colors.black</pre>
     * <pre>  vertexDotsColor:  Colors.red</pre>
     * <pre>  controlDotsColor: Colors.darkorange</pre>
     * <pre>  vertexFrameColor: Colors.transparent</pre>
     * <pre>  bezierFrameColor: Colors.darkorchid</pre>
     * <pre>  thickness:        2</pre>
     *
     * <p>In particular, with these defaults, the vertex frame is
     * not included in the <code>PaintableSequence</code>.</p>
     */
    public final PaintableSequence makeStructurePaintable()
    {
        Color fillColor        = Colors.lime;
        Color drawColor        = Colors.black;
        Color vertexDotsColor  = Colors.red;
        Color controlDotsColor = Colors.darkorange;
        Color vertexFrameColor = Colors.transparent;
        Color bezierFrameColor = Colors.darkorchid;
        int   thickness        = 2;
        
        return makeStructurePaintable
            (fillColor, drawColor, vertexDotsColor, controlDotsColor,
             vertexFrameColor, bezierFrameColor, thickness);
    }
    
    
    /**
     * <p>Removes the current nodes in the path list and sets
     * the nodes to those in the given array of nodes.</p>
     *
     * <p>If the given nodes array is <code>null</code> then
     * does nothing.</p>
     *
     * @param nodes the nodes to use for the path list 
     */
    public final void setPathNodes(PathNode[] nodes) {
        if (nodes == null)
            return;
        
        removeAll();
        append(nodes);
    }
    
    
    /**
     * <p>Returns a <code>PathNode</code> array with the current
     * path nodes in this path list.</p>
     *
     * <p>The <code>PathNode</code> items in the array are live,
     * that is, they are identical to the current items in the
     * path list.  This means that the user of this array may
     * make changes to the path list directly.  Obviously, this
     * facility must be used with care.</p>
     *
     * <p>The length of the array is <code>size()</code>.</p>
     *
     * <p>If the user changes the path list by adding or removing
     * items then this array will be out of synchronization with
     * the data in the path list.</p>
     */
    public final PathNode[] getPathNodes() {
        return (PathNode[]) pathlist.toArray(new PathNode[0]);
    }
    
    
    /**
     * <p>Sets the winding rule as an object.</p>
     *
     * <p>Does nothing if the parameter is <code>null</code>.</p>
     *
     * @param rule the winding rule as an object
     */
    public final void setWindingRule(WindingRule rule) {
        if (rule == null)
            return;
        
        windingrule = rule;
    }
    
    
    /**
     * <p>Sets the winding rule via an int.</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>
     *
     * @param rule the winding rule as an int
     */
    public final void setWindingRule(int rule) {
        if (rule == GeneralPath.WIND_NON_ZERO)
            windingrule = WindingRule.WIND_NON_ZERO;
        else
        if (rule == GeneralPath.WIND_EVEN_ODD)
            windingrule = WindingRule.WIND_EVEN_ODD;
    }
    
    
    /**
     * Get the winding rule as an object.
     *
     * @return the winding rule
     */
    public final WindingRule getWindingRule() {
        return windingrule;
    }
    
    
    /**
     * <p>Sets this path list to be a deep clone of an existing
     * path list.</p>
     *
     * <p>If the given path list is <code>null</code> then does
     * nothing.</p>
     *
     * @param list the path list to clone
     */
    public final void setPathList(PathList list) {
        if (list == null)
            return;
        
        removeAll();
        
        int N = list.size();
        
        for (int i = 0; i < N; i++)
            append(new PathNode(list.get(i)));
        
        setWindingRule(list.getWindingRule());
    }
    
    
    /**
     * <p>Sets the path list using
     * the path nodes and winding rule obtained from the given
     * <code>PathIterator</code>.</p>
     *
     * <p>This method must execute the iteration so when it is
     * complete then <code>iterator.isDone()</code> will be true.</p>
     *
     * <p>If the given iterator is <code>null</code> then does
     * nothing.</p>
     *
     * @param iterator the iterator whose data is cloned to this list
     */
    public final void setPathList(PathIterator iterator) {
        if (iterator != null) {
            removeAll();
            append(PathNode.getPathNodes(iterator));
            setWindingRule(iterator.getWindingRule());
        }
    }
    
    
    /**
     * <p>Sets the path list using
     * the path nodes and winding rule obtained from the given
     * <code>Shape</code>.</p>
     *
     * <p>If the given shape is <code>null</code> then does
     * nothing.</p>
     *
     * @param shape the shape whose data is cloned to this list
     */
    public final void setPathList(Shape shape) {
        if (shape != null)
            setPathList(shape.getPathIterator(null));
    }
    
    
    /**
     * <p>Sets the path list using
     * the path nodes and winding rule obtained from the given
     * <code>Shape</code>
     * after applying the given transform
     * to the shape data.</p>
     *
     * @param shape the shape whose data is used to make this list
     * @param transform the transform to apply to the shape data
     */
    public final void setPathList
        (Shape shape, AffineTransform transform)
    {
        if (shape != null)
            setPathList(shape.getPathIterator(transform));
    }
    
    
    /**
     * <p>Sets the path list using
     * the path nodes and winding rule obtained from the given
     * <code>Shape</code>
     * after applying the given transform and flatness
     * to the shape data.</p>
     *
     * @param shape the shape whose data is used to make this list
     * @param transform the transform to apply to the shape data
     * @param flatness the flatness to require
     */
    public final void setPathList
        (Shape shape, AffineTransform transform, double flatness)
    {
        if (shape != null)
            setPathList(shape.getPathIterator(transform, flatness));
    }
    
    
    /** Returns the size of the list. */
    public final int size() {
        return pathlist.size();
    }
    
    
    /**
     * <p>Sets the <code>PathNode</code> at the given index
     * to a copy of the given node
     * if the given node is non-<code>null</code>
     * and if the given index is in range;
     * otherwise does nothing.</p>
     *
     * @param index the index of a path node in the path list
     * @param node  the path node to copy
     */
    public final void set(int index, PathNode node) {
        if (node == null)
            return;
        
        PathNode internal = get(index);
        
        if (internal != null)
            internal.setPathNode(node);
    }
    
    
    /**
     * <p>Returns the <code>PathNode</code> at the given index
     * if the given index is in range;
     * otherwise returns <code>null</code>.</p>
     *
     * @param index the index of a path node in the path list
     */
    public final PathNode get(int index) {
        if ((index >= 0) && (index < size()))
            return (PathNode) pathlist.get(index);
        else
            return null;
    }
    
    
    /**
     * <p>Sets the contents of the given slot of
     * the <code>PathNode</code> at the given index
     * to the given x, y
     * if the index is in range and the slot is valid;
     * otherwise does nothing.</p>
     *
     * @param index the index of a path node in the path list
     * @param slot  the slot within the path node (1, 2, 3)
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    public final void setSlot(int index, int slot, float x, float y) {
        PathNode internal = get(index);
        
        if (internal != null)
            internal.setSlot(slot, x, y);
    }
    
    
    /**
     * <p>Sets the contents of the given slot of
     * the <code>PathNode</code> at the given index
     * to the coordinates of the given <code>pair</code>
     * if the index is in range and the slot is valid
     * and if <code>pair</code> is non-<code>null</code>
     * and of length 2;
     * otherwise does nothing.</p>
     *
     * @param index the index of a path node in the path list
     * @param slot  the slot within the path node (1, 2, 3)
     * @param pair the float[2] array with x, y
     */
    public final void setSlot(int index, int slot, float[] pair) {
        if ((pair == null) || (pair.length != 2))
            return;
        
        setSlot(index, slot, pair[0], pair[1]);
    }
    
    
    /**
     * <p>Sets the contents of the slot <code>indices[1]</code> of
     * the <code>PathNode</code> at the index <code>indices[0]</code>
     * to the given x, y
     * if <code>indices</code> is non-<code>null</code>
     * and of length 2
     * and if the index is in range and the slot is valid;
     * otherwise does nothing.</p>
     *
     * @param indices the index-slot pair
     * @param x the x-coordinate
     * @param y the y-coordinate
     */
    public final void setSlot(int[] indices, float x, float y) {
        if ((indices == null) || (indices.length != 2))
            return;
        
        setSlot(indices[0], indices[1], x, y);
    }
    
    
    /**
     * <p>Sets the contents of the slot <code>indices[1]</code> of
     * the <code>PathNode</code> at the index <code>indices[0]</code>
     * to the coordinates of the given <code>pair</code>
     * if <code>indices</code> is non-<code>null</code>
     * and of length 2,
     * if the index is in range and the slot is valid,
     * and if <code>pair</code> is non-<code>null</code>
     * and of length 2;
     * otherwise does nothing.</p>
     *
     * @param indices the index-slot pair
     * @param pair the float[2] array with x, y
     */
    public final void setSlot(int[] indices, float[] pair) {
        if ((indices == null) || (indices.length != 2))
            return;
        
        if ((pair == null) || (pair.length != 2))
            return;
        
        setSlot(indices[0], indices[1], pair[0], pair[1]);
    }
    
    
    /**
     * <p>Returns the contents of the given slot of
     * the <code>PathNode</code> at the given index
     * if the index is in range and the slot is valid;
     * otherwise returns <code>null</code>.</p>
     *
     * @param index the index of a path node in the path list
     * @param slot  the slot within the path node (1, 2, 3)
     */
    public final float[] getSlot(int index, int slot) {
        PathNode internal = get(index);
        
        if (internal == null)
            return null;
        
        return internal.getSlot(slot);
    }
    
    
    /**
     * <p>Returns the contents of the slot <code>indices[1]</code> of
     * the <code>PathNode</code> at the index <code>indices[0]</code>
     * if <code>indices</code> is non-<code>null</code>
     * and of length 2
     * and if the index is in range and the slot is valid;
     * otherwise returns <code>null</code>.</p>
     *
     * @param indices the index-slot pair
     */
    public final float[] getSlot(int[] indices) {
        if ((indices == null) || (indices.length != 2))
            return null;
        
        return getSlot(indices[0], indices[1]);
    }
    
    
    /**
     * <p>Searches the nodes in this path list
     * and for each such node
     * searches the slots in the node 
     * to determine if the given (x,y) is within epsilon
     * of the values in the slot;
     * if successful, returns an array int[2]
     * with value 0 equal to the node index
     * and  value 1 equal to the slot index;
     * if unsuccessful, returns <code>null</code>.</p>
     *
     * <p>The metric Metric.MAX is used.</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[] nearSlot(double x, double y, double epsilon) {
        return nearSlot(x, y, epsilon, Metric.MAX);
    }
    
    
    /**
     * <p>Searches the nodes in this path list
     * and for each such node
     * searches the slots in the node 
     * to determine if the given (x,y) is within epsilon
     * of the values in the slot relative to the given metric;
     * if successful, returns an array int[2]
     * with value 0 equal to the node index
     * and  value 1 equal to the slot index;
     * if unsuccessful, returns <code>null</code>.</p>
     *
     * <p>If the given metric is <code>null</code>
     * then <code>Metric.MAX</code> is used.</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[] nearSlot
        (double x, double y, double epsilon, Metric metric)
    {
        if (metric == null)
            metric = Metric.MAX;
        
        int N = size();
        
        for (int index = 0; index < N; index++) {
            PathNode internal = (PathNode) pathlist.get(index);
            
            int slot = internal.nearSlot(x, y, epsilon, metric);
            
            if (slot > 0)
                return new int[] { index, slot };
        }
        
        return null;
    }
    
    
    /**
     * <p>Inserts the specified node at the specified position in
     * this <code>PathList</code> and shifts any elements right as needed.</p>
     *
     * <p>Does not clone the node but adds the node as is.</p>
     *
     * <p>Does nothing if the given node is <code>null</code>. It is
     * therefore impossible to place a <code>null</code> node in the list.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>    0 &lt;= index &lt;= size()</pre>
     *
     * @param index the list position
     * @param node  the node to add to the list
     */
    public final void add(int index, PathNode node) {
        if (node == null)
            return;
        
        if (index < 0)
            index = 0;
        else
        if (index > size())
            index = size();
        
        pathlist.add(index, node);
    }
    
    
    /**
     * <p>Inserts the specified array of nodes into this
     * <code>PathList</code> starting at the specified position in
     * the list and shifts any elements right as needed.</p>
     *
     * <p>Does not clone any nodes but adds them as is.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * <p>Forces the given start index into the range:</p>
     *
     * <pre>    0 &lt;= index &lt;= size()</pre>
     *
     * @param index the list start position
     * @param nodes the nodes to clone and add to the list
     */
    public final void add(int index, PathNode[] nodes) {
        add(index, nodes, false);
    }
    
    
    /**
     * <p>Inserts the specified array of nodes into this
     * <code>PathList</code> starting at the specified position in
     * the list and shifts any elements right as needed.</p>
     *
     * <p>Does not clone any nodes but adds them as is.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * <p>Forces the given start index into the range:</p>
     *
     * <pre>    0 &lt;= index &lt;= size()</pre>
     *
     * <p>If the connect parameter is true and if the insert start index
     * is not zero then if the first inserted node is a MOVE it will be
     * changed to a LINE.  If the connect parameter is false then there
     * will be no change to the first inserted node.</p>
     *
     * @param index the list start position
     * @param nodes the nodes to clone and add to the list
     * @param connect whether or not to modify the first inserted node in
     *        order to connect the inserted nodes to the existing nodes
     */
    public final void add(int index, PathNode[] nodes, boolean connect)
    {
        if (nodes == null)
            return;
        
        int N = nodes.length;
        
        if (N == 0)
            return;
        
        if (index < 0)
            index = 0;
        else
        if (index > size())
            index = size();
        
        int start = index;
        
        for (int i = 0; i < N; i++)
            if (nodes[i] != null) {
                pathlist.add(index, nodes[i]);
                index++;
            }
        
        if (connect)
            if (start > 0)
                if (index > start)
                    get(start).changeMoveToLine();
    }
    
    
    /**
     * <p>Appends the specified node to this <code>PathList</code>.</p>
     *
     * <p>Does not clone the node but appends the node as is.</p>
     *
     * <p>Does nothing if the given node is <code>null</code>.</p>
     *
     * @param node the node to clone and append to the list
     */
    public final void append(PathNode node) {
        if (node == null)
            return;
        
        pathlist.add(node);
    }
    
    
    /**
     * <p>Appends the specified array of nodes to this
     * <code>PathList</code>.</p>
     *
     * <p>Does not clone any nodes but appends them as is.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * @param nodes the nodes to clone and append to the list
     */
    public final void append(PathNode[] nodes) {
        append(nodes, false);
    }
    
    
    /**
     * <p>Appends the specified array of nodes to this
     * <code>PathList</code>.</p>
     *
     * <p>Does not clone any nodes but appends them as is.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * <p>If the connect parameter is true and if the current list is not
     * empty then if the first appended node is a MOVE it will be changed
     * to a LINE.  If the connect parameter is false then there will be no
     * change to the first appended node.</p>
     *
     * @param nodes the nodes to clone and append to the list
     * @param connect whether or not to modify the first appended node in
     *        order to connect the appended nodes to the existing nodes
     */
    public final void append(PathNode[] nodes, boolean connect) {
        if (nodes == null)
            return;
        
        int N = nodes.length;
        
        if (N == 0)
            return;
        
        int start = size();
        int index = start;
        
        for (int i = 0; i < N; i++)
            if (nodes[i] != null) {
                pathlist.add(nodes[i]);
                index++;
            }
        
        if (connect)
            if (start > 0)
                if (index > start)
                    get(start).changeMoveToLine();
    }
    
    
    /**
     * <p>Extracts the path node data from the given iterator and appends
     * the nodes to this path list.</p>
     *
     * <p>The method must execute the iteration so when it is
     * complete then <code>iterator.isDone()</code> will be true.</p>
     *
     * @param iterator the iterator whose data is to be extracted
     */
    public final void append(PathIterator iterator) {
        append(iterator, false);
    }
    
    
    /**
     * <p>Extracts the path node data from the given iterator and appends
     * the nodes to this path list.</p>
     *
     * <p>If the connect parameter is true and if the current list is not
     * empty then if the first appended node is a MOVE it will be changed
     * to a LINE.  If the connect parameter is false then there will be no
     * change to the first appended node.</p>
     *
     * <p>The method must execute the iteration so when it is
     * complete then <code>iterator.isDone()</code> will be true.</p>
     *
     * @param iterator the iterator whose data is to be extracted
     * @param connect whether or not to modify the first appended node in
     *        order to connect the appended nodes to the existing nodes
     */
    public final void append(PathIterator iterator, boolean connect) {
        if (iterator == null)
            return;
        
        append(PathNode.getPathNodes(iterator), connect);
    }
    
    
    /**
     * <p>Extracts the path node data from the given shape and appends
     * the nodes to this path list.</p>
     *
     * @param shape the shape whose data is to be extracted
     */
    public final void append(Shape shape) {
        append(shape, false);
    }
    
    
    /**
     * <p>Extracts the path node data from the given shape and appends
     * the nodes to this path list.</p>
     *
     * <p>If the connect parameter is true and if the current list is not
     * empty then the first appended node will be changed from a MOVE to
     * a LINE.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param connect whether or not to modify the first appended node in
     *        order to connect the appended nodes to the existing nodes
     */
    public final void append(Shape shape, boolean connect) {
        if (shape == null)
            return;
        
        append(shape.getPathIterator(null), connect);
    }
    
    
    /**
     * <p>Extracts the path node data from the given shape and appends
     * the nodes to this path list
     * after applying the given transform.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param transform the transform to apply to the shape data
     */
    public final void append
        (Shape shape, AffineTransform transform)
    {
        append(shape, transform, false);
    }
    
    
    /**
     * <p>Extracts the path node data from the given shape and appends
     * the nodes to this path list
     * after applying the given transform.</p>
     *
     * <p>If the connect parameter is true and if the current list is not
     * empty then the first appended node will be changed from a MOVE to
     * a LINE.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param transform the transform to apply to the shape data
     * @param connect whether or not to modify the first appended node in
     *        order to connect the appended nodes to the existing nodes
     */
    public final void append
        (Shape shape, AffineTransform transform, boolean connect)
    {
        if (shape == null)
            return;
        
        append(shape.getPathIterator(transform), connect);
    }
    
    
    /**
     * <p>Extracts the path node data from the given shape and appends
     * the nodes to this path list
     * after applying the given transform and flatness.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param transform the transform to apply to the shape data
     * @param flatness the flatness to require
     */
    public final void append
        (Shape shape, AffineTransform transform, double flatness)
    {
        append(shape, transform, flatness, false);
    }
    
    
    /**
     * <p>Extracts the path node data from the given shape and appends
     * the nodes to this path list
     * after applying the given transform and flatness.</p>
     *
     * <p>If the connect parameter is true and if the current list is not
     * empty then the first appended node will be changed from a MOVE to
     * a LINE.</p>
     *
     * @param shape the shape whose data is to be extracted
     * @param transform the transform to apply to the shape data
     * @param flatness the flatness to require
     * @param connect whether or not to modify the first appended node in
     *        order to connect the appended nodes to the existing nodes
     */
    public final void append
        (Shape shape, AffineTransform transform, double flatness, boolean connect)
    {
        if (shape == null)
            return;
        
        append(shape.getPathIterator(transform, flatness), connect);
    }
    
    
    /**
     * <p>Inserts a clone of the specified node at the specified position in
     * this <code>PathList</code> and shifts any elements right as needed.</p>
     *
     * <p>Does nothing if the given node is <code>null</code>. It is
     * therefore impossible to place a <code>null</code> node in the list.</p>
     *
     * <p>Forces the given index into the range:</p>
     *
     * <pre>    0 &lt;= index &lt;= size()</pre>
     *
     * @param index the list position
     * @param node  the node to clone and add to the list
     */
    public final void cloneAndAdd(int index, PathNode node) {
        if (node == null)
            return;
        
        if (index < 0)
            index = 0;
        else
        if (index > size())
            index = size();
        
        pathlist.add(index, new PathNode(node));
    }
    
    
    /**
     * <p>Inserts clones of the specified array of nodes to this
     * <code>PathList</code> starting at the specified position in
     * the list and shifts any elements right as needed.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * <p>Forces the given start index into the range:</p>
     *
     * <pre>    0 &lt;= index &lt;= size()</pre>
     *
     * @param index the list start position
     * @param nodes the nodes to clone and add to the list
     */
    public final void cloneAndAdd(int index, PathNode[] nodes) {
        cloneAndAdd(index, nodes, false);
    }
    
    
    /**
     * <p>Inserts clones of the specified array of nodes to this
     * <code>PathList</code> starting at the specified position in
     * the list and shifts any elements right as needed.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * <p>Forces the given start index into the range:</p>
     *
     * <pre>    0 &lt;= index &lt;= size()</pre>
     *
     * <p>If the connect parameter is true and if the insert start index
     * is not zero then if the first inserted node is a MOVE it will be
     * changed to a LINE.  If the connect parameter is false then there
     * will be no change to the first inserted node.</p>
     *
     * @param index the list start position
     * @param nodes the nodes to clone and add to the list
     * @param connect whether or not to modify the first inserted node in
     *        order to connect the inserted nodes to the existing nodes
     */
    public final void cloneAndAdd(int index, PathNode[] nodes, boolean connect)
    {
        if (nodes == null)
            return;
        
        int N = nodes.length;
        
        if (N == 0)
            return;
        
        if (index < 0)
            index = 0;
        else
        if (index > size())
            index = size();
        
        int start = index;
        
        for (int i = 0; i < N; i++)
            if (nodes[i] != null) {
                pathlist.add(index, new PathNode(nodes[i]));
                index++;
            }
        
        if (connect)
            if (start > 0)
                if (index > start)
                    get(start).changeMoveToLine();
    }
    
    
    /**
     * <p>Appends a clone of the specified node to this <code>PathList</code>.</p>
     *
     * <p>Does nothing if the given node is <code>null</code>.</p>
     *
     * @param node the node to clone and append to the list
     */
    public final void cloneAndAppend(PathNode node) {
        if (node == null)
            return;
        
        pathlist.add(new PathNode(node));
    }
    
    
    /**
     * <p>Appends clones of the specified array of nodes to this
     * <code>PathList</code>.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * @param nodes the nodes to clone and append to the list
     */
    public final void cloneAndAppend(PathNode[] nodes) {
        cloneAndAppend(nodes, false);
    }
    
    
    /**
     * <p>Appends clones of the specified array of nodes to this
     * <code>PathList</code>.</p>
     *
     * <p>Does nothing if the given node array is <code>null</code>
     * and ignores any <code>null</code> items in the array.</p>
     *
     * <p>If the connect parameter is true and if the current list is not
     * empty then if the first appended node is a MOVE it will be changed
     * to a LINE.  If the connect parameter is false then there will be no
     * change to the first appended node.</p>
     *
     * @param nodes the nodes to clone and append to the list
     * @param connect whether or not to modify the first appended node in
     *        order to connect the appended nodes to the existing nodes
     */
    public final void cloneAndAppend(PathNode[] nodes, boolean connect) {
        if (nodes == null)
            return;
        
        int N = nodes.length;
        
        if (N == 0)
            return;
        
        int start = size();
        int index = start;
        
        for (int i = 0; i < N; i++)
            if (nodes[i] != null) {
                pathlist.add(new PathNode(nodes[i]));
                index++;
            }
        
        if (connect)
            if (start > 0)
                if (index > start)
                    get(start).changeMoveToLine();
    }
    
    
    /**
     * <p>Removes the <code>PathNode</code> at the given index
     * and returns the removed node.</p>
     *
     * <p>Returns <code>null</code> if the index is not valid.</p>
     *
     * @param index the index of the <code>PathNode</code> to remove
     */
    public final PathNode remove(int index) {
        if ((index >= 0) && (index < size()))
            return (PathNode) pathlist.remove(index);
        else
            return null;
    }
    
    
    /**
     * <p>Removes the <code>PathNode</code>s starting at the given
     * index m inclusive and ending at the given index n exclusive;
     * returns an array with the removed nodes.</p>
     *
     * <p>Forces m and n into range if needed.</p>
     *
     * <p>Returns an array of length 0 if no nodes are removed.</p>
     *
     * @param m the starting index inclusive
     * @param n the ending index exclusive
     */
    public final PathNode[] remove(int m, int n) {
        if (m < 0)
            m = 0;
        
        if (n > size())
            n = size();
        
        int c = n - m;
        
        if (c <= 0)
            return new PathNode[0];
        
        PathNode[] result = new PathNode[c];
        
        while (n > m) {
            c--;
            n--;
            result[c] = (PathNode) pathlist.remove(n);
        }
        
        return result;
    }
    
    
    /**
     * Removes all <code>PathNode</code>s from the path list
     * and returns an array with the removed nodes.
     */
    public final PathNode[] removeAll() {
        return remove(0, size());
    }
    
    
    /**
     * <p>Transforms the internal data points in each path node using the given
     * affine transform.</p>
     *
     * <p>Does nothing if the given affine transform is <code>null</code>.</p>
     *
     * @param T the affine transform
     */
    public final void transform(AffineTransform T) {
        if (T == null)
            return;
        
        int N = size();
        
        for (int i = 0; i < N; i++)
            get(i).transform(T);
    }
    
    
    /**
     * <p>Returns true if this <code>PathList</code> is a valid path list
     * for the construction of a <code>Shape</code>.</p>
     *
     * <p>More precisely, returns true if the list is empty (size is zero)
     * or if the initial <code>PathNode</code> has type MOVE.</p>
     */
    public final boolean isValid() {
        return (size() == 0) || (get(0).getNodeType() == MOVE);
    }
    
    
    /**
     * <p>Returns true if the list has at least 2 nodes, all nodes in the
     * list have one of the following 3 types MOVE, LINE, CLOSE, and the
     * first node is MOVE.</p>
     *
     * <p>This is a "loose" definition of "polygon" since the method does
     * not make assertions about whether the path consists of one open or
     * closed chain of line segments.</p>
     */
    public final boolean isPolygon() {
        int N = size();
        
        if (N < 2)
            return false;
        
        if (get(0).getNodeType() != MOVE)
            return false;
        
        for (int i = 1; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            if ((type == QUAD) || (type == CUBIC))
                return false;
        }
        
        return true;
    }
    
    
    /**
     * <p>Returns true if the list has at least 2 nodes, the initial
     * node has type MOVE, the final node has type LINE or CLOSE, all
     * inner nodes have type LINE, and at least one node has type LINE.</p>
     *
     * <p>Historical note: Java defines <code>java.awt.Polygon</code>.
     * Since this class is awt-based, the coordinates of a point must
     * be integers not floating point numbers.  In addition, this class
     * requires that a polygon be closed and have WIND_EVEN_ODD at its
     * winding rule.  This method intentionally does not test for these
     * constraints since we do believe them to be essential.</p>
     */
    public final boolean isStrictPolygon() {
        int N = size();
        
        if (N < 2)
            return false;
        
        if (get(0).getNodeType() != MOVE)
            return false;
        
        int M = N - 1;
        
        for (int i = 1; i < M; i++)
            if (get(i).getNodeType() != LINE)
                return false;
        
        boolean hasLINE = (M > 1);
        
        PathNode node = get(M);
        int type = node.getNodeType();
            
        if (type == LINE)
            hasLINE = true;
        else
            if (type != CLOSE)
                return false;
        
        return hasLINE;
    }
    
    
    /**
     * <p>Returns a new <code>PathList</code> with the same winding
     * rule as this path list and whose nodes are constructed from
     * the nodes of this path list by the following operations.</p>
     *
     * <p>If the type of a node is MOVE, LINE, or CLOSE, then a copy
     * of the node is inserted in the new list.</p>
     *
     * <p>A node of the form QUAD[x1;y1;x2;y2] is replaced by
     * a node LINE[x2;y2].</p>
     *
     * <p>A node of the form CUBIC[x1;y1;x2;y2;x3;y3] is replaced by
     * a node LINE[x3;y3].</p>
     *
     * <p>If <code>isPolygon()</code> is true then this method just
     * returns a copy of this path list.</p>
     */
    public final PathList makeVertexPathList() {
        PathList list = new PathList();
        
        list.setWindingRule(getWindingRule());
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE:
                case CLOSE:
                    list.append(new PathNode(node));
                    break;
                
                case QUAD: {
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    break;
                }
                
                case CUBIC: {
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    list.append(new PathNode(LINE, x3, y3, 0, 0, 0, 0));
                    break;
                }
                
                default:
                    break;
            }
        }
        
        return list;
    }
    
    
    /**
     * <p>Returns a new <code>PathList</code> with the same winding
     * rule as this path list and whose nodes are constructed from
     * the nodes of this path list by the following operations.</p>
     *
     * <p>If the type of a node is MOVE, LINE, or CLOSE, then a copy
     * of the node is inserted in the new list.</p>
     *
     * <p>A node of the form QUAD[x1;y1;x2;y2] is replaced by
     * 2 nodes LINE[x1;y1], LINE[x2;y2].</p>
     *
     * <p>A node of the form CUBIC[x1;y1;x2;y2;x3;y3] is replaced by
     * 3 nodes LINE[x1;y1], LINE[x2;y2], LINE[x3;y3].</p>
     *
     * <p>If <code>isPolygon()</code> is true then this method just
     * returns a copy of this path list.</p>
     */
    public final PathList makeBezierPathList() {
        PathList list = new PathList();
        
        list.setWindingRule(getWindingRule());
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE:
                case CLOSE:
                    list.append(new PathNode(node));
                    break;
                
                case QUAD: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    break;
                }
                
                case CUBIC: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x3, y3, 0, 0, 0, 0));
                    break;
                }
                
                default:
                    break;
            }
        }
        
        return list;
    }
    
    
    /**
     * <p>Returns a new <code>PathList</code> with the same winding
     * rule as this path list and whose nodes are constructed from
     * the nodes of this path list by the following operations.</p>
     *
     * <p>If the type of a node is CLOSE, then the node is
     * ignored.</p>
     *
     * <p>A node of the form MOVE[x1;y1] or the form LINE[x1;y1] is
     * replaced by 2 nodes MOVE[x1;y1], LINE[x1;y1].</p>
     *
     * <p>A node of the form QUAD[x1;y1;x2;y2] is replaced by
     * 2 nodes MOVE[x2;y2], LINE[x2;y2].</p>
     *
     * <p>A node of the form CUBIC[x1;y1;x2;y2;x3;y3] is replaced by
     * 2 nodes MOVE[x3;y3], LINE[x3;y3].</p>
     */
    public final PathList makeVertexDotsPathList() {
        PathList list = new PathList();
        
        list.setWindingRule(getWindingRule());
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    list.append(new PathNode(MOVE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    break;
                }
                
                case QUAD: {
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.append(new PathNode(MOVE, x2, y2, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    break;
                }
                
                case CUBIC: {
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    list.append(new PathNode(MOVE, x3, y3, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x3, y3, 0, 0, 0, 0));
                    break;
                }
                
                case CLOSE:
                default:
                    break;
            }
        }
        
        return list;
    }
    
    
    /**
     * <p>Returns a new <code>PathList</code> with the same winding
     * rule as this path list and whose nodes are constructed from
     * the nodes of this path list by the following operations.</p>
     *
     * <p>If the type of a node is CLOSE, then the node is
     * ignored.</p>
     *
     * <p>A node of the form MOVE[x1;y1] or the form LINE[x1;y1] is
     * replaced by 2 nodes MOVE[x1;y1], LINE[x1;y1].</p>
     *
     * <p>A node of the form QUAD[x1;y1;x2;y2] is replaced by
     * 4 nodes MOVE[x1;y1], LINE[x1;y1], MOVE[x2;y2], LINE[x2;y2].</p>
     *
     * <p>A node of the form CUBIC[x1;y1;x2;y2;x3;y3] is replaced by
     * 6 nodes MOVE[x1;y1], LINE[x1;y1], MOVE[x2;y2], LINE[x2;y2],
     * MOVE[x3;y3], LINE[x3;y3].</p>
     */
    public final PathList makeBezierDotsPathList() {
        PathList list = new PathList();
        
        list.setWindingRule(getWindingRule());
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    list.append(new PathNode(MOVE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    break;
                }
                
                case QUAD: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.append(new PathNode(MOVE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(MOVE, x2, y2, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    break;
                }
                
                case CUBIC: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    list.append(new PathNode(MOVE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(MOVE, x2, y2, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    list.append(new PathNode(MOVE, x3, y3, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x3, y3, 0, 0, 0, 0));
                    break;
                }
                
                case CLOSE:
                default:
                    break;
            }
        }
        
        return list;
    }
    
    
    /**
     * <p>Returns a new <code>PathList</code> with the same winding
     * rule as this path list and whose nodes are constructed from
     * the nodes of this path list by the following operations.</p>
     *
     * <p>If the type of a node is MOVE, LINE, or CLOSE, then the
     * node is ignored.</p>
     *
     * <p>A node of the form QUAD[x1;y1;x2;y2] is replaced by
     * 2 nodes MOVE[x1;y1], LINE[x1;y1].</p>
     *
     * <p>A node of the form CUBIC[x1;y1;x2;y2;x3;y3] is replaced by
     * 4 nodes MOVE[x1;y1], LINE[x1;y1], MOVE[x2;y2], LINE[x2;y2].</p>
     */
    public final PathList makeControlDotsPathList() {
        PathList list = new PathList();
        
        list.setWindingRule(getWindingRule());
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case QUAD: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    list.append(new PathNode(MOVE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    break;
                }
                
                case CUBIC: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.append(new PathNode(MOVE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x1, y1, 0, 0, 0, 0));
                    list.append(new PathNode(MOVE, x2, y2, 0, 0, 0, 0));
                    list.append(new PathNode(LINE, x2, y2, 0, 0, 0, 0));
                    break;
                }
                
                case MOVE:
                case LINE:
                case CLOSE:
                default:
                    break;
            }
        }
        
        return list;
    }
    
    
    /**
     * Returns a <code>PathListIterator</code> constructed from
     * an instance of <code>makeVertexPathList()</code>.
     */
    public final PathListIterator makeVertexPathListIterator() {
        return makeVertexPathList().makePathListIterator();
    }
    
    
    /**
     * Returns a <code>PathListIterator</code> constructed from
     * an instance of <code>makeBezierPathList()</code>.
     */
    public final PathListIterator makeBezierPathListIterator() {
        return makeBezierPathList().makePathListIterator();
    }
    
    
    /**
     * Returns a <code>PathListIterator</code> constructed from
     * an instance of <code>makeVertexDotsPathList()</code>.
     */
    public final PathListIterator makeVertexDotsPathListIterator() {
        return makeVertexDotsPathList().makePathListIterator();
    }
    
    
    /**
     * Returns a <code>PathListIterator</code> constructed from
     * an instance of <code>makeBezierDotsPathList()</code>.
     */
    public final PathListIterator makeBezierDotsPathListIterator() {
        return makeBezierDotsPathList().makePathListIterator();
    }
    
    
    /**
     * Returns a <code>PathListIterator</code> constructed from
     * an instance of <code>makeControlDotsPathList()</code>.
     */
    public final PathListIterator makeControlDotsPathListIterator() {
        return makeControlDotsPathList().makePathListIterator();
    }
    
    
    /**
     * <p>Returns a polygonal <code>Shape</code> that passes through
     * the vertex points of this pathlist.</p>
     */
    public final Shape makeVertexShape() {
        GeneralPath gp = new GeneralPath(getWindingRule().rule());
        
        if (! isValid())
            return gp;
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                    gp.moveTo(node.getX1(), node.getY1());
                    break;
                
                case LINE:
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case QUAD:
                    gp.lineTo(node.getX2(), node.getY2());
                    break;
                
                case CUBIC:
                    gp.lineTo(node.getX3(), node.getY3());
                    break;
                
                case CLOSE:
                    gp.closePath();
                    break;
                
                default:
                    break;
            }
        }
        
        return gp;
    }
    
    
    /**
     * <p>Returns a polygonal <code>Shape</code> that passes through
     * the bezier points of this pathlist, that is, both the vertex
     * points and the control points.</p>
     */
    public final Shape makeBezierShape() {
        GeneralPath gp = new GeneralPath(getWindingRule().rule());
        
        if (! isValid())
            return gp;
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                    gp.moveTo(node.getX1(), node.getY1());
                    break;
                
                case LINE:
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case QUAD:
                    gp.lineTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX2(), node.getY2());
                    break;
                
                case CUBIC:
                    gp.lineTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX2(), node.getY2());
                    gp.lineTo(node.getX3(), node.getY3());
                    break;
                
                case CLOSE:
                    gp.closePath();
                    break;
                
                default:
                    break;
            }
        }
        
        return gp;
    }
    
    
    /**
     * Returns a <code>Shape</code> that consists of the vertex dots.</p>
     */
    public final Shape makeVertexDotsShape() {
        GeneralPath gp = new GeneralPath(getWindingRule().rule());
        
        if (! isValid())
            return gp;
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case LINE:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case QUAD:
                    gp.moveTo(node.getX2(), node.getY2());
                    gp.lineTo(node.getX2(), node.getY2());
                    break;
                
                case CUBIC:
                    gp.moveTo(node.getX3(), node.getY3());
                    gp.lineTo(node.getX3(), node.getY3());
                    break;
                
                case CLOSE:
                default:
                    break;
            }
        }
        
        return gp;
    }
    
    
    /**
     * Returns a <code>Shape</code> that consists of the bezier dots.</p>
     */
    public final Shape makeBezierDotsShape() {
        GeneralPath gp = new GeneralPath(getWindingRule().rule());
        
        if (! isValid())
            return gp;
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case LINE:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case QUAD:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    gp.moveTo(node.getX2(), node.getY2());
                    gp.lineTo(node.getX2(), node.getY2());
                    break;
                
                case CUBIC:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    gp.moveTo(node.getX2(), node.getY2());
                    gp.lineTo(node.getX2(), node.getY2());
                    gp.moveTo(node.getX3(), node.getY3());
                    gp.lineTo(node.getX3(), node.getY3());
                    break;
                
                case CLOSE:
                default:
                    break;
            }
        }
        
        return gp;
    }
    
    
    /**
     * Returns a <code>Shape</code> that consists of the control dots.</p>
     */
    public final Shape makeControlDotsShape() {
        GeneralPath gp = new GeneralPath(getWindingRule().rule());
        
        if (! isValid())
            return gp;
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            
            int type = node.getNodeType();
            
            switch (type) {
                case QUAD:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    break;
                
                case CUBIC:
                    gp.moveTo(node.getX1(), node.getY1());
                    gp.lineTo(node.getX1(), node.getY1());
                    gp.moveTo(node.getX2(), node.getY2());
                    gp.lineTo(node.getX2(), node.getY2());
                    break;
                
                case MOVE:
                case LINE:
                case CLOSE:
                default:
                    break;
            }
        }
        
        return gp;
    }
    
    
    /**
     * <p>Returns a new <code>Point2D[]</code> whose points are built
     * to include the vertex points of the path list by the following
     * operations.</p>
     *
     * <p>For a node of the form MOVE[x1;y1] or LINE[x1;y1],
     * 1 point [x1;y1] is added to the array.</p> 
     *
     * <p>For a node of the form QUAD[x1;y1;x2;y2],
     * 1 point [x2;y2] is added to the array.</p> 
     *
     * <p>For a node of the form CUBIC[x1;y1;x2;y2;x3;y3],
     * 1 point [x3;y3] is added to the array.</p> 
     */
    public final Point2D[] makeVertexPoints() {
        Vector list = new Vector();
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    list.add(new Point2D.Double(x1, y1));
                    break;
                }
                
                case QUAD: {
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.add(new Point2D.Double(x2, y2));
                    break;
                }
                
                case CUBIC: {
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    list.add(new Point2D.Double(x3, y3));
                    break;
                }
                
                case CLOSE:
                default:
                    break;
            }
        }
        
        return (Point2D[]) list.toArray(new Point2D[0]);
    }
    
    
    /**
     * <p>Returns a new <code>Point2D[]</code> whose points are built
     * to include the bezier points of the path list by the following
     * operations.</p>
     *
     * <p>For a node of the form MOVE[x1;y1] or LINE[x1;y1],
     * 1 point [x1;y1] is added to the array.</p> 
     *
     * <p>For a node of the form QUAD[x1;y1;x2;y2],
     * 2 points [x1;y1], [x2;y2] are added to the array.</p> 
     *
     * <p>For a node of the form CUBIC[x1;y1;x2;y2;x3;y3],
     * 3 points [x1;y1], [x2;y2], [x3;y3] are added to the array.</p> 
     */
    public final Point2D[] makeBezierPoints() {
        Vector list = new Vector();
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    list.add(new Point2D.Double(x1, y1));
                    break;
                }
                
                case QUAD: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.add(new Point2D.Double(x1, y1));
                    list.add(new Point2D.Double(x2, y2));
                    break;
                }
                
                case CUBIC: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    list.add(new Point2D.Double(x1, y1));
                    list.add(new Point2D.Double(x2, y2));
                    list.add(new Point2D.Double(x3, y3));
                    break;
                }
                
                case CLOSE:
                default:
                    break;
            }
        }
        
        return (Point2D[]) list.toArray(new Point2D[0]);
    }
    
    
    /**
     * <p>Returns a new <code>Point2D[]</code> whose points are built
     * to include the control points of the path list by the following
     * operations.</p>
     *
     * <p>For a node of the form QUAD[x1;y1;x2;y2],
     * 1 point [x1;y1] is added to the array.</p> 
     *
     * <p>For a node of the form CUBIC[x1;y1;x2;y2;x3;y3],
     * 2 points [x1;y1], [x2;y2] are added to the array.</p> 
     */
    public final Point2D[] makeControlPoints() {
        Vector list = new Vector();
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            PathNode node = get(i);
            int type = node.getNodeType();
            
            switch (type) {
                case QUAD: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    list.add(new Point2D.Double(x1, y1));
                    break;
                }
                
                case CUBIC: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    list.add(new Point2D.Double(x1, y1));
                    list.add(new Point2D.Double(x2, y2));
                    break;
                }
                
                case MOVE:
                case LINE:
                case CLOSE:
                default:
                    break;
            }
        }
        
        return (Point2D[]) list.toArray(new Point2D[0]);
    }
    
    
    /**
     * <p>Static method that returns the vertex shape corresponding to
     * the given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list and
     * then use the path list to make the associated vertex shape.</p>
     *
     * @param the shape whose vertex shape is desired
     */
    public static Shape makeVertexShape(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeVertexShape();
    }
    
    
    /**
     * <p>Static method that returns the bezier shape corresponding to
     * the given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list and
     * then use the path list to make the associated bezier shape.</p>
     *
     * @param the shape whose bezier shape is desired
     */
    public static Shape makeBezierShape(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeBezierShape();
    }
    
    
    /**
     * <p>Static method that returns the vertex dots shape corresponding to
     * the given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list and
     * then use the path list to make the associated vertex dots shape.</p>
     *
     * @param the shape whose vertex dots shape is desired
     */
    public static Shape makeVertexDotsShape(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeVertexDotsShape();
    }
    
    
    /**
     * <p>Static method that returns the bezier dots shape corresponding to
     * the given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list and
     * then use the path list to make the associated bezier dots shape.</p>
     *
     * @param the shape whose bezier dots shape is desired
     */
    public static Shape makeBezierDotsShape(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeBezierDotsShape();
    }
    
    
    /**
     * <p>Static method that returns the control dots shape corresponding to
     * the given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list and
     * then use the path list to make the associated control dots shape.</p>
     *
     * @param the shape whose control dots shape is desired
     */
    public static Shape makeControlDotsShape(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeControlDotsShape();
    }
    
    
    /**
     * <p>Static method that returns the vertex point array for the
     * given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list
     * and then use the path list to make the associated vertex point
     * array.</p>
     *
     * @param the shape whose vertex point array is desired
     */
    public static Point2D[] makeVertexPoints(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeVertexPoints();
    }
    
    
    /**
     * <p>Static method that returns the bezier point array for the
     * given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list
     * and then use the path list to make the associated bezier point
     * array.</p>
     *
     * @param the shape whose bezier point array is desired
     */
    public static Point2D[] makeBezierPoints(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeBezierPoints();
    }
    
    
    /**
     * <p>Static method that returns the control point array for the
     * given shape.</p>
     *
     * <p>Current implementation: Use the shape to make a path list
     * and then use the path list to make the associated control point
     * array.</p>
     *
     * @param the shape whose control point array is desired
     */
    public static Point2D[] makeControlPoints(Shape shape) {
        PathList pathlist = new PathList(shape);
        return pathlist.makeControlPoints();
    }
    
    
    /**
     * <p>Without actually constructing the shape, this function returns
     * the mathematical point at float parameter t on the shape that
     * would correspond to this path list.</p>
     *
     * <p>Let us explain how the computation is done and what happens in
     * boundary cases.  For brevity, we use the notation <code>[x;y]</code>
     * to stand for an internal data pair or for a return value of the form
     * <code>new&nbsp;float[]&nbsp;{&nbsp;x,&nbsp;y&nbsp;}</code>.</p>
     *
     * <p>Returns <code>[0;0]</code> if the size of this list is 0 or if
     * <code>isValid()</code> is false.</p>
     *
     * <p>Otherwise, for this discussion, let <code>N&nbsp;=&nbsp;size()</code>
     * and <code>M&nbsp;=&nbsp;N&nbsp;-&nbsp;1</code>.</p>
     *
     * <p>First assume that t is an integer with
     * <code>0&nbsp;&lt;=&nbsp;t&nbsp;&lt;=&nbsp;M</code>.
     * What is returned at t depends on the t-th node in the path list.</p>
     *
     * <p>If the t-th node is <code>MOVE[x1;y1]</code> or
     * <code>LINE[x1;y1]</code>, return <code>[x1;y1]</code>.</p>
     *
     * <p>If the t-th node is <code>QUAD[x1;y1;x2;y2]</code>, return
     * <code>[x2;y2]</code>.</p>
     *
     * <p>If the t-th node is <code>CUBIC[x1;y1;x2;y2;x3;y3]</code>, return
     * <code>[x3;y3]</code>.</p>
     *
     * <p>If the t-th node is <code>CLOSE[]</code>, return
     * <code>[xc;yc]</code> where <code>[xc;yc]</code> is the
     * coordinate pair of the last previous MOVE operation in the path list.</p>
     *
     * <p>We next give the out-of-bounds return values.</p>
     *
     * <p>If <code>t&nbsp;&lt;&nbsp;0</code>, return the value for
     * <code>t&nbsp;=&nbsp;0</code>.</p>
     *
     * <p>If <code>t&nbsp;&gt;&nbsp;M</code>, return the value for
     * <code>t&nbsp;=&nbsp;M</code>.</p>
     *
     * <p>Finally assume that <code>t&nbsp;=&nbsp;k&nbsp;+&nbsp;z</code> where
     * k is an integer and <code>0&nbsp;&lt;&nbsp;z&nbsp;&lt;&nbsp;1</code>.
     * Assume also that <code>[x0,y0]</code> is the coordinate pair of the
     * point returned by this function at the integer k.</p>
     *
     * <p>If the node at (k+1) is <code>MOVE[x1;y1]</code>, return
     * <code>[x0;y0]</code> since a move represents a discontinuous jump.</p>
     *
     * <p>If the node at (k+1) is <code>LINE[x1;y1]</code>, return the
     * linear interpolation by z between <code>[x0;y0]</code> and
     * <code>[x1;y1]</code>.  The linear interpolation is defined by:</p>
     *
     * <pre>  (1-z)*[x0;y0] + z*[x1;y1]</pre>
     *
     * <p>If the node at (k+1) is <code>QUAD[x1;y1;x2;y2]</code>, return
     * the quadratic interpolation by z in the quadratic bezier arch
     * <code>[x0;y0;x1;y1;x2;y2]</code>.</p>
     *
     * <p>If the node at (k+1) is <code>CUBIC[x1;y1;x2;y2;x3;y3]</code>,
     * return the cubic interpolation by z in the cubic bezier arch
     * <code>[x0;y0;x1;y1;x2;y2;x3;y3]</code>.</p>
     *
     * <p>If the node at (k+1) is <code>CLOSE[]</code> return the linear
     * interpolation by z between <code>[x0;y0]</code> and 
     * <code>[xc;yc]</code> where <code>[xc;yc]</code> is the coordinate
     * pair of the last previous MOVE operation in the path list.</p>
     *
     * <p>The Bezier polynomials defined in class <code>Bezier</code>
     * are used to compute the x,y coordinates of the linear, quadratic,
     * and cubic interpolations.  See the comments in that class for more
     * mathematical details.</p>
     *
     * <p>Examination of the above algorithm reveals that we assume that
     * the parameter interval for each node in the pathlist is of length 1.
     * It is possible to assign a different parameter interval to each node
     * but we have decided to stick with the simplest case in this method.</p>
     * 
     * @param t the parameter at which to a point on a shape
     */
    public final float[] getShapePoint(float t) {
        int N = size();
        
        if (N == 0)
            return new float[2];
        
        PathNode start = get(0);
        
        if (! (start.getNodeType() == MOVE))
            return new float[2];
        
        // zero or out of bounds below
        if (t <= 0) {
            return new float[] { start.getX1(), start.getY1() };
        }
        
        int M = N - 1;
        
        // out of bounds above
        if (t > M)
            return getShapePoint(M);
        
        // in bounds
        int k = (int) t;
        float z = t - k;
        
        // integer case
        if (z == 0) {
            PathNode node = get(k);
            int type = node.getNodeType();
            
            switch (type) {
                case MOVE:
                case LINE:
                    return new float[] { node.getX1(), node.getY1() };
                
                case QUAD:
                    return new float[] { node.getX2(), node.getY2() };
                
                case CUBIC:
                    return new float[] { node.getX3(), node.getY3() };
                
                case CLOSE:
                    k--;
                    
                    while (k >= 0) {
                        node = get(k);
                        type = node.getNodeType();
                        
                        if (type == MOVE)
                            return new float[] { node.getX1(), node.getY1() };
                        else
                            k--;
                    }
                
                // error situation
                default:
                    return new float[2];
            }
        }
        // non-integer case
        else {
            PathNode node = get(k+1);
            int type = node.getNodeType();
            
            float[] last = getShapePoint(k);
            float x0 = last[0];
            float y0 = last[1];
            
            switch (type) {
                case MOVE: {
                    return last;
                }
                
                case LINE: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    
                    float x = Bezier.bezierF(z, x0, x1);
                    float y = Bezier.bezierF(z, y0, y1);
                    
                    return new float[] { x, y };
                }
                
                case QUAD: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    
                    float x = Bezier.bezierF(z, x0, x1, x2);
                    float y = Bezier.bezierF(z, y0, y1, y2);
                    
                    return new float[] { x, y };
                }
                
                case CUBIC: {
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    
                    float x2 = node.getX2();
                    float y2 = node.getY2();
                    
                    float x3 = node.getX3();
                    float y3 = node.getY3();
                    
                    float x = Bezier.bezierF(z, x0, x1, x2, x3);
                    float y = Bezier.bezierF(z, y0, y1, y2, y3);
                    
                    return new float[] { x, y };
                }
                
                case CLOSE: {
                    while (k >= 0) {
                        node = get(k);
                        type = node.getNodeType();
                        
                        if (type == MOVE)
                            break;
                        else
                            k--;
                    }
                    
                    float x1 = node.getX1();
                    float y1 = node.getY1();
                    
                    float x = Bezier.bezierF(z, x0, x1);
                    float y = Bezier.bezierF(z, y0, y1);
                    
                    return new float[] { x, y };
                }
                
                // error situation
                default:
                    return new float[2];
            }
        }
    }
    
    
    /**
     * <p>Returns the data in this path list as a multi-line
     * <code>String</code>.</p>
     *
     * <p>The first line returned is one of the following two
     * lines:</p>
     *
     * <pre>WIND_NON_ZERO</pre>
     *
     * <pre>WIND_EVEN_ODD</pre>
     *
     * <p>The remaining lines give the <code>PathNode</code>
     * information for each path node in the path list using
     * the <code>toString()</code> method of that class.</p>
     */
    public String toString() {
        StringBuffer buffer = new StringBuffer();
        
        buffer.append(getWindingRuleString());
        buffer.append("\n");
        
        int N = size();
        
        for (int i = 0; i < N; i++) {
            buffer.append(get(i).toString());
            buffer.append("\n");
        }
        
        return buffer.toString();
    }
    
    
    /** Returns the same <code>String</code> as <code>toString()</code>. */
    public String toStringData() {
        return toString();
    }
    
    
    /**
     * <p>Sets the data of this path list using <code>String</code>
     * data in the format produced by <code>toString()</code>.</p>
     *
     * <p>Throws <code>ParseException</code> if the data is not in
     * the correct format.</p>
     *
     * @param data the <code>String</code> with the list information
     * @throws ParseException
     */
    public void fromStringData(String data)
        throws ParseException
    {
        if (data == null) {
            String message = "PathList error: null data\n";
            
            throw new ParseException(message, -1);
        }
        
        data = data.trim();
        
        String[] item = Strings.tokenize(data, "\n", true);
        
        int L = item.length;
        
        if (L < 1) {
            String message = "PathList error: Too few lines of data " + L + "\n"
                + data + "\n\n" + standardMessage;
            
            throw new ParseException(message, -1);
        }
        
        WindingRule rule = getWindingRuleFromStringData(item[0]);
        
        int N = L - 1;
        
        PathNode[] nodes = new PathNode[N];
        
        for (int i = 0; i < N; i++) {
            String info = item[i + 1];
            
            try {
                nodes[i] = new PathNode(info);
            }
            catch (ParseException ex) {
                String message = "PathList error in PathNode data item: " + i + "\n"
                    + info + "\n\n"
                    + ex.getMessage() + "\n"
                    + standardMessage;
                
                throw new ParseException(message, -1);
            }
        }
        
        setWindingRule(rule);
        setPathNodes(nodes);
    }
    
    
    /**
     * <p>Returns a <code>String</code> with the value of the method
     * <code>toStringData</code> applied to each item in the array
     * returned by <code>getPathNodes</code>.</p>
     */
    public final String[] getPathNodeStateArray() {
        int N = size();
        
        String[] state = new String[N];
        
        for (int i = 0; i < N; i++)
            state[i] = get(i).toStringData();
        
        return state;
    }
    
    
    /**
     * <p>Returns a <code>String</code> with the winding rule information.</p>
     *
     * <p>Returns one of:</p>
     *
     * <pre>WIND_NON_ZERO</pre>
     *
     * <pre>WIND_EVEN_ODD</pre>
     */
    public final String getWindingRuleString() {
        if (windingrule == WindingRule.WIND_NON_ZERO)
            return String_WIND_NON_ZERO;
        else
            return String_WIND_EVEN_ODD;
    }
    
    
    /**
     * <p>Returns the <code>WindingRule</code> that corresponds to the
     * given <code>String</code> data.</p>
     *
     * <p>The valid input data is:</p>
     *
     * <pre>WIND_NON_ZERO</pre>
     *
     * <pre>WIND_EVEN_ODD</pre>
     *
     * <p>Throws <code>ParseException</code> if the winding rule data
     * does not match either of the above strings.</p>
     *
     * @param data the <code>String</code> with the winding rule
     * @throws ParseException
     */
    public WindingRule getWindingRuleFromStringData(String data)
        throws ParseException
    {
        if (data == null) {
            String message = "PathList winding rule error: null data\n";
            
            throw new ParseException(message, -1);
        }
        
        data = data.trim().toUpperCase();
        
        if (data.equals(String_WIND_NON_ZERO))
            return WindingRule.WIND_NON_ZERO;
        
        if (data.equals(String_WIND_EVEN_ODD))
            return WindingRule.WIND_EVEN_ODD;
        
        String message =
            "PathList winding rule error:\n" + data + "\n\n" + standardMessage;
        
        throw new ParseException(message, -1);
    }
    
    
    /**
     * <p>Opens a file dialog to get the file name of a text file
     * with path list data and then reads the data.<p>
     *
     * <p>Does nothing to the current list if errors occur.</p>
     *
     * @return true if the operation succeeds
     */
    public final boolean readDataFromFile()
    {
        int result = filechooser.showOpenDialog(null);
        
        if (result == JFileChooser.CANCEL_OPTION)
            return false;
        
        if (result == JFileChooser.ERROR_OPTION) {
            GeneralDialog.showOKDialog
                ("File Dialog Error", "File Dialog Error");
            
            return false;
        }
        
        File source = filechooser.getSelectedFile();
        
        filechooser.setCurrentDirectory(source);
        
        return readDataFromFile(source, true);
    }
    
    
    /**
     * <p>Reads the path list data from the given text file.<p>
     *
     * <p>Does nothing to the current list if errors occur.</p>
     *
     * @param source the data source
     * @param displayErrorDialogs if true display error dialogs
     *
     * @return true if the operation succeeds
     */
    public final boolean readDataFromFile
        (File source, boolean displayErrorDialogs)
    {
        if (source == null) {
             if (displayErrorDialogs) {
                String message =
                    "Error in reading file\n"
                    + "Null source File\n";
                
                GeneralDialog.showOKDialog
                    (message, "File Read Error");
            }
            
           return false;
        }
        
        String data = "";
        
        try {
            data = FileUtilities.readFile(source);
        }
        catch (Exception ex) {
            if (displayErrorDialogs) {
                String message =
                    "Error in reading file\n"
                    + source.getName() + "\n\n"
                    + ex.getMessage() + "\n";
                
                GeneralDialog.showOKDialog
                    (message, "File Read Error");
            }
            
            return false;
        }
        
        PathList list = new PathList();
        
        try {
            list.fromStringData(data);
        }
        catch (ParseException ex) {
            if (displayErrorDialogs) {
                String message =
                    "Error in reading data\n"
                    + source.getName() + "\n\n"
                    + ex.getMessage() + "\n";
                
                GeneralDialog.showOKDialog
                    (message, "File Data Error");
            }
            
            return false;
        }
        
        setPathList(list);
        
        return true;
    }
    
    
    /**
     * <p>Opens a file dialog to get the file name of a text file
     * and then saves the data to the file.<p>
     *
     * <p>The current path list is unchanged.</p>
     *
     * <p>Does nothing if errors occur.</p>
     *
     * @return true if the operation succeeds
     */
    public final boolean saveDataToFile()
    {
        int result = filechooser.showSaveDialog(null);
        
        if (result == JFileChooser.CANCEL_OPTION)
            return false;
        
        if (result == JFileChooser.ERROR_OPTION) {
            GeneralDialog.showOKDialog
                ("File Dialog Error", "File Dialog Error");
            
            return false;
        }
        
        File target = filechooser.getSelectedFile();
        filechooser.setCurrentDirectory(target);
        
        return saveDataToFile(target, true);
    }
    
    
    /**
     * <p>Saves the path list data to the given text file.<p>
     *
     * <p>The current path list is unchanged.</p>
     *
     * <p>Does nothing if errors occur.</p>
     *
     * @param target the data target
     * @param displayErrorDialogs if true display error dialogs
     * @return true if the operation succeeds
     */
    public final boolean saveDataToFile
        (File target, boolean displayErrorDialogs)
    {
        if (target == null) {
             if (displayErrorDialogs) {
                String message =
                    "Error in writing file\n"
                    + "Null target File\n";
                
                GeneralDialog.showOKDialog
                    (message, "File Save Error");
            }
            
           return false;
        }
        
        String data = toString();
        
        try {
            FileUtilities.writeFile(target, data, true);
        }
        catch (Exception ex) {
            if (displayErrorDialogs) {
                String message =
                    "Error in writing file\n"
                    + target.getName() + "\n\n"
                    + ex.getMessage() + "\n";
                
                GeneralDialog.showOKDialog
                    (message, "File Save Error");
            }
            
            return false;
        }
        
        return true;
    }
    
}

