/*
 * @(#)Path.java    2.3  15 December 2003
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.util.*;

import java.awt.*;
import java.awt.geom.*;

/**
 * <P>Class <CODE>Path</CODE> encapsulates an interface <CODE>Strategy</CODE>
 * that defines the requirement for a strategy that can automatically build a
 * <CODE>GeneralPath</CODE> given vertex and tangent data.</P>
 *
 * <P>Specifically, the strategy requires:</P>
 * 
 * <UL>
 *   <LI>a vertex array,</LI>
 *   <LI>a tangent array,</LI>
 *   <LI>a choice of <CODE>ClosureMode</CODE>, and</LI>
 *   <LI>a choice of <CODE>WindingRule</CODE>.</LI>
 * </UL>
 *
 * <P>Class <CODE>Path</CODE> also provides some examples of Strategy objects
 * and some useful static methods.</P>
 *
 * <P>Class <CODE>Path</CODE> cannot be instantiated.</P>
 *
 * @see java.awt.Shape
 * @see java.awt.geom.GeneralPath
 * @author  Richard Rasala
 * @version 2.3
 * @since   2.3
 */
public class Path {

    /** Private constructor to prevent instantiation. */
    private Path() {}
    
    
    /**
     * The <CODE>Strategy</CODE> interface requires one method that returns a
     * <CODE>GeneralPath</CODE> given its input parameters. 
     */
    public interface Strategy {
        /**
         * <P>Returns a <CODE>GeneralPath</CODE> given
         * a vertex array,
         * a tangent array,
         * a choice of <CODE>ClosureMode</CODE>,
         * and a choice of <CODE>WindingRule</CODE>.</P>
         *
         * <P>The vertex array must have the form float[N][2] or
         * else an empty <CODE>GeneralPath</CODE> will be returned.</P>
         *
         * <P>If the method uses the tangent array then the tangent array must
         * have the form float[N][2] for the same N as the vertex array or
         * else an empty <CODE>GeneralPath</CODE> will be returned.</P>
         *
         * <P>If the method does not use the tangent array, this fact must be
         * documented.  In this case, it must be acceptable to pass
         * <CODE>null</CODE> as the tangent parameter.</P>
         *
         * @param vertex      the array of vertex data
         * @param tangent     the array of corresponding tangent data
         * @param closuremode the closure mode
         * @param windingrule the winding rule
         */
        public GeneralPath makePath
            (float[][]   vertex,
             float[][]   tangent,
             ClosureMode mode,
             WindingRule windingrule);
    }
    
    
    /** 
     * <P><CODE>POLYGON</CODE> is a <CODE>Strategy</CODE> that constructs a
     * <CODE>GeneralPath</CODE> as the polygon defined by the given vertex
     * array.</P>
     *
     * <P>The given tangent array is not used.</P>
     */
    public static final Path.Strategy POLYGON
        = new Path.Strategy() {
            public GeneralPath makePath
                (float[][]   vertex,
                 float[][]   tangent,
                 ClosureMode closuremode,
                 WindingRule windingrule)
            {
                GeneralPath path = new GeneralPath(windingrule.rule());
                
                if (FloatArray.checkArray(vertex, 2)) {
                    int N = vertex.length;
                    
                    if (N > 0) {
                        path.moveTo(vertex[0][0], vertex[0][1]);
                        
                        int limit = closuremode.limit(N);
                        
                        for (int i = 1; i <= limit; i++) {
                            int k = (i < N) ? i : 0;
                            path.lineTo(vertex[k][0], vertex[k][1]);
                        }
                        
                        if (closuremode == ClosureMode.CLOSED)
                            path.closePath();
                    }
                }
                
                return path;
            }
        };
    
    
    /** 
     * <P><CODE>POLYGON_DOTS</CODE> is a <CODE>Strategy</CODE> that constructs
     * a <CODE>GeneralPath</CODE> using a sequence of <CODE>moveTo</CODE>,
     * <CODE>lineTo</CODE> pairs, one for each vertex in the vertex array.</P>
     *
     * <P>The given tangent array is not used.</P>
     *
     * <P>The given closuremode is not used.  The path is OPEN.</P>
     */
    public static final Path.Strategy POLYGON_DOTS
        = new Path.Strategy() {
            public GeneralPath makePath
                (float[][]   vertex,
                 float[][]   tangent,
                 ClosureMode closuremode,
                 WindingRule windingrule)
            {
                GeneralPath path = new GeneralPath(windingrule.rule());
                
                if (FloatArray.checkArray(vertex, 2)) {
                    int N = vertex.length;
                    
                    for (int i = 0; i < N; i++) {
                        path.moveTo(vertex[i][0], vertex[i][1]);
                        path.lineTo(vertex[i][0], vertex[i][1]);
                    }
                }
                
                return path;
            }
        };
    
    
    /** 
     * <P><CODE>BEZIER_CUBIC</CODE> is a <CODE>Strategy</CODE> that constructs a
     * <CODE>GeneralPath</CODE> as the Bezier cubic defined by the given vertex
     * and tangent arrays.</P>
     */
    public static final Path.Strategy BEZIER_CUBIC
        = new Path.Strategy() {
            public GeneralPath makePath
                (float[][]   vertex,
                 float[][]   tangent,
                 ClosureMode closuremode,
                 WindingRule windingrule)
            {
                GeneralPath path = new GeneralPath(windingrule.rule());
                
                if (FloatArray.checkArrayPair(vertex, tangent, 2)) {
                    int N = vertex.length;
                    
                    if (N > 0) {
                        path.moveTo(vertex[0][0], vertex[0][1]);
                        
                        int limit = closuremode.limit(N);
                        
                        for (int i = 1; i <= limit; i++) {
                            int j = i - 1;
                            int k = (i < N) ? i : 0;
                            
                            float x1 = vertex[j][0] + tangent[j][0];
                            float y1 = vertex[j][1] + tangent[j][1];
                            
                            float x2 = vertex[k][0] - tangent[k][0];
                            float y2 = vertex[k][1] - tangent[k][1];
                            
                            float x3 = vertex[k][0];
                            float y3 = vertex[k][1];
                            
                            path.curveTo(x1, y1, x2, y2, x3, y3);
                        }
                        
                        if (closuremode == ClosureMode.CLOSED)
                            path.closePath();
                    }
                }
                
                return path;
            }
        };
    
    
    /** 
     * <P><CODE>BEZIER_FRAME</CODE> is a <CODE>Strategy</CODE> that constructs a
     * <CODE>GeneralPath</CODE> as the Bezier polygonal frame associated with the
     * given vertex and tangent arrays.</P>
     *
     * <P>The closuremode is used to determine whether the Bezier frame is closed
     * or open.</P>
     */
    public static final Path.Strategy BEZIER_FRAME
        = new Path.Strategy() {
            public GeneralPath makePath
                (float[][]   vertex,
                 float[][]   tangent,
                 ClosureMode closuremode,
                 WindingRule windingrule)
            {
                if (closuremode == ClosureMode.CLOSED)
                    return POLYGON.makePath(
                        closedBezierFrame(vertex, tangent),
                        null,
                        closuremode,
                        windingrule);
                else
                    return POLYGON.makePath(
                        openBezierFrame(vertex, tangent),
                        null,
                        closuremode,
                        windingrule);
            }
        };
    
    
    /** 
     * <P><CODE>BEZIER_TANGENT_SEGMENTS</CODE> is a <CODE>Strategy</CODE> that
     * constructs a <CODE>GeneralPath</CODE> as a disjoint sequence of tangent
     * segments.</P>
     *
     * <P>The tangent segments have the form:</P>
     *
     * <UL>
     *   <LI><CODE>vertex[i] - tangent[i]</CODE></LI>
     *   <LI><CODE>vertex[i] + tangent[i]</CODE></LI>
     * </UL>
     *
     * <P>using the given vertex and tangent arrays.</P>
     *
     * <P>The given closuremode is not used.  The path is OPEN.</P>
     */
    public static final Path.Strategy BEZIER_TANGENT_SEGMENTS
        = new Path.Strategy() {
            public GeneralPath makePath
                (float[][]   vertex,
                 float[][]   tangent,
                 ClosureMode closuremode,
                 WindingRule windingrule)
            {
                GeneralPath path = new GeneralPath(windingrule.rule());
                
                if (FloatArray.checkArrayPair(vertex, tangent, 2)) {
                    int N = vertex.length;
                    
                    if (N > 0) {
                        float[][] segments = bezierTangentSegments(vertex, tangent);
                        
                        for (int i = 0; i < N; i++) {
                            int r = 2 * i;
                            int s = r + 1;
                            
                            path.moveTo(segments[r][0], segments[r][1]);
                            path.lineTo(segments[s][0], segments[s][1]);
                        }
                    }
                }
                
                return path;
            }
        };
    
    
    /**
     * <P>Returns a float array that contains the points on the closed Bezier
     * frame for the given vertex and tangent arrays.</P>
     *
     * <P>Precondition: For some integer N:</P>
     * <UL>
     *   <LI>vertex  is float[N][2]</LI>
     *   <LI>tangent is float[N][2].</LI>
     * </UL>
     * 
     * <P>Postcondition: For M = 3 * N, the array returned is float[M][2].
     * For each index i less than N, with j = (i + 1) (mod N),
     * the sequence of three points</P>
     * <UL>
     *   <LI><CODE>vertex[i]</CODE></LI>
     *   <LI><CODE>vertex[i] + tangent[i]</CODE></LI>
     *   <LI><CODE>vertex[j] - tangent[j]</CODE></LI>
     * </UL>
     * <P>is placed in the next available set of three array slots.</P>
     *
     * <P>If the precondition fails, then float[0][2] is returned.</P>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the Bezier frame points
     */
    public static float[][] closedBezierFrame(float[][] vertex, float[][] tangent) {
        if (! FloatArray.checkArrayPair(vertex, tangent, 2))
            return new float[0][2];
           
        int N = vertex.length;
        int M = 3 * N;
        int L = N - 1;
        
        float[][] result = new float[M][2];
        
        for (int i = 0; i < N; i++) {
            int j = (i < L) ? i : 0;
            
            int r = 3 * i;
            int s = r + 1;
            int t = r + 2;
            
            result[r][0] = vertex[i][0];
            result[r][1] = vertex[i][1];
            
            result[s][0] = vertex[i][0] + tangent[i][0];
            result[s][1] = vertex[i][1] + tangent[i][1];
            
            result[t][0] = vertex[j][0] - tangent[j][0];
            result[t][1] = vertex[j][1] - tangent[j][1];
        }
        
        return result;
    }
    
    
    /**
     * <P>Returns a float array that contains the points on the open Bezier
     * frame for the given vertex and tangent arrays.</P>
     *
     * <P>Precondition: For some integer N:</P>
     * <UL>
     *   <LI>vertex  is float[N][2]</LI>
     *   <LI>tangent is float[N][2].</LI>
     * </UL>
     * 
     * <P>Postcondition: If N is non-zero, then for M = 3 * N - 2,
     * the array returned is float[M][2].
     * For each index i less than (N - 1), with j = i + 1,
     * the sequence of three points</P>
     * <UL>
     *   <LI><CODE>vertex[i]</CODE></LI>
     *   <LI><CODE>vertex[i] + tangent[i]</CODE></LI>
     *   <LI><CODE>vertex[j] - tangent[j]</CODE></LI>
     * </UL>
     * <P>is placed in the next available set of three array slots.
     * Then the final vertex is placed in the final array slot.</P>
     *
     * <P>If the precondition fails or N is 0, then float[0][2] is returned.</P>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the Bezier frame points
     */
    public static float[][] openBezierFrame(float[][] vertex, float[][] tangent) {
        if (! FloatArray.checkArrayPair(vertex, tangent, 2))
            return new float[0][2];
           
        int N = vertex.length;
        
        if (N == 0)
            return new float[0][2];
        
        int M = 3 * N - 2;
        int L = N - 1;
        
        float[][] result = new float[M][2];
        
        for (int i = 0; i < L; i++) {
            int j = i + 1;
            
            int r = 3 * i;
            int s = r + 1;
            int t = r + 2;
            
            result[r][0] = vertex[i][0];
            result[r][1] = vertex[i][1];
            
            result[s][0] = vertex[i][0] + tangent[i][0];
            result[s][1] = vertex[i][1] + tangent[i][1];
            
            result[t][0] = vertex[j][0] - tangent[j][0];
            result[t][1] = vertex[j][1] - tangent[j][1];
        }
        
        result[M-1][0] = vertex[L][0];
        result[M-1][1] = vertex[L][1];
        
        return result;
    }
    
    
    /**
     * <P>Returns a float array that contains the Bezier tangent segments
     * for the given vertex and tangent arrays.</P>
     *
     * <P>Precondition: For some integer N:</P>
     * <UL>
     *   <LI>vertex  is float[N][2]</LI>
     *   <LI>tangent is float[N][2].</LI>
     * </UL>
     * 
     * <P>Postcondition: For M = 2 * N, the array returned is float[M][2].
     * For each index i, the sequence of two points</P>
     * <UL>
     *   <LI><CODE>vertex[i] - tangent[i]</CODE></LI>
     *   <LI><CODE>vertex[i] + tangent[i]</CODE></LI>
     * </UL>
     * <p>is placed in the next available set of two array slots.</P>
     *
     * <P>If the precondition fails, then float[0][2] is returned.</P>
     *
     * @param  vertex  the array of vertex  information
     * @param  tangent the array of tangent information
     * @return the array with the Bezier tangent segments
     */
    public static float[][] bezierTangentSegments
        (float[][] vertex, float[][] tangent)
    {
        if (! FloatArray.checkArrayPair(vertex, tangent, 2))
            return new float[0][2];
           
        int N = vertex.length;
        int M = 2 * N;
        
        float[][] result = new float[M][2];
        
        for (int i = 0; i < N; i++) {
            int r = 2 * i;
            int s = r + 1;
            
            result[r][0] = vertex[i][0] - tangent[i][0];
            result[r][1] = vertex[i][1] - tangent[i][1];
            
            result[s][0] = vertex[i][0] + tangent[i][0];
            result[s][1] = vertex[i][1] + tangent[i][1];
        }
        
        return result;
    }
    
    
    /**
     * <P>Returns the <CODE>GeneralPath</CODE> obtained by appending the
     * given sequence of <CODE>Shape</CODE> objects to the given
     * <CODE>GeneralPath</CODE> using the given sequence of
     * <CODE>boolean</CODE> values to determine the connection at each
     * stage.</P>
     *
     * <P>There is one more <CODE>boolean</CODE> value than there is a
     * <CODE>Shape</CODE> object.  The final <CODE>boolean</CODE> value
     * determines whether or not the <CODE>GeneralPath</CODE> will be
     * terminated by a <CODE>closePath</CODE> operation.
     *
     * <P>If the path parameter is <CODE>null</CODE> then a new
     * <CODE>GeneralPath</CODE> will be created, modified, and returned.</P>
     *
     * <P>Precondition: For some integer <CODE>N</CODE>:</P>
     * <UL>
     *   <LI>shape   is <CODE>Shape[N]</CODE>
     *   <LI>connect is <CODE>boolean[N+1]</CODE> or is <CODE>null</CODE>.
     * </UL>
     *
     * <P>If connect is <CODE>null</CODE>
     * then it is replaced by <CODE>new boolean[N+1]</CODE>.</P>
     *
     * <P>If the precondition fails, no append actions will be taken.</P>
     *
     * @param path    the path to extend or <CODE>null</CODE>
     * @param shape   the array of shapes to append
     * @param connect the array of connection specifications or <CODE>null</CODE>
     * @return the path as modified
     * @see java.awt.geom.GeneralPath#append(java.awt.Shape, boolean)
     */
    public static GeneralPath append(
        GeneralPath path,
        Shape[]     shape,
        boolean[]   connect)
    {
        if (path == null)
            path = new GeneralPath();
        
        if (shape == null)
            return path;
        
        int N = shape.length;
        
        if (connect == null) {
            connect = new boolean[N+1];
        }
        else {
            if (connect.length != (N+1))
                return path;
        }
        
        for (int i = 0; i < N; i++)
            if (shape[i] != null)
                path.append(shape[i], connect[i]);
        
        if (connect[N])
            path.closePath();
        
        return path;
    }
    
    
    /**
     * <P>Returns the <CODE>GeneralPath</CODE> obtained by appending the
     * given sequence of <CODE>Shape</CODE> objects to the given
     * <CODE>GeneralPath</CODE> using the given <CODE>boolean</CODE>
     * value to determine the connection at every stage.</P>
     *
     * <P>If the path parameter is <CODE>null</CODE> then a new
     * <CODE>GeneralPath</CODE> will be created, modified, and returned.</P>
     *
     * <P>Precondition: For some integer N, shape is Shape[N].</P>
     *
     * <P>If the precondition fails, no append actions will be taken.</P>
     *
     * @param path    the path to extend or <CODE>null</CODE>
     * @param shape   the array of shapes to append
     * @param connect the array of connection specifications or <CODE>null</CODE>
     * @return the path as modified
     * @see java.awt.geom.GeneralPath#append(java.awt.Shape, boolean)
     */
    public static GeneralPath append(
        GeneralPath path,
        Shape[]     shape,
        boolean     connect)
    {
        if (shape == null)
            return path;
        
        int N = shape.length;
        
        boolean[] array = new boolean[N+1];
        
        for (int i = 0; i <= N; i++)
            array[i] = connect;
        
        return append(path, shape, array);
    }
    
    
    /**
     * <P>Paints a dot of size 4 at the given position (x, y), in the given color,
     * in the given graphics context.</P>
     *
     * <P>If the graphics context is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>If the color is <CODE>null</CODE>,
     * it is set to <CODE>Color.red</CODE>.</P>
     *
     * @param g the graphics context
     * @param x the x-position
     * @param y the y-position
     * @param color the color for painting
     */
    public static void dot
        (Graphics g, double x, double y, Color color)
    {
        if (g == null)
            return;
        
        if (color == null)
            color = Color.red;
        
        Shape shape = new Rectangle2D.Double(x - 2, y - 2, 4, 4);
        
        PaintMode.fill(g, shape, color);
    }
    
    
    /**
     * <P>Paints a line of thickness 2 from (x1, y1) to (x2, y2), in the given color,
     * in the given graphics context.</P>
     *
     * <P>If the graphics context is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>If the color is <CODE>null</CODE>,
     * it is set to <CODE>Color.green</CODE>.</P>
     *
     * @param g the graphics context
     * @param x1 the start x-position
     * @param y1 the start y-position
     * @param x2 the final x-position
     * @param y2 the final y-position
     * @param color the color for painting
     */
    public static void line
        (Graphics g, double x1, double y1, double x2, double y2, Color color)
    {
        if (g == null)
            return;
        
        if (color == null)
            color = Color.green;
        
        Shape shape = new Line2D.Double(x1, y1, x2, y2);
        
        PaintMode.draw(g, shape, color, new BasicStroke(2));
    }
    
    
    /**
     * <P>Shows the shape of the frame of the given shape, in the given frame color,
     * in the given graphics context.</P>
     * 
     * <P>If the graphics context or the shape is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>If the frame color is <CODE>null</CODE>,
     * it is set to <CODE>Color.green</CODE>.</P>
     *
     * @param g the graphics context
     * @param shape the shape to frame
     * @param frameColor the color of the frame
     */
    public static void showShapeFrame
        (Graphics g, Shape shape, Color frameColor)
    {
        if ((g == null) || (shape == null))
            return;
        
        if (frameColor == null)
            frameColor = Color.green;
        
        PathIterator iterator = shape.getPathIterator(null);
        double[] data = new double[6];
        
        boolean drawline = false;
        
        // last x, y, pair
        double x  = 0;
        double y  = 0;
        
        // most recent x, y pair for SEG_MOVETO
        double x0 = 0;
        double y0 = 0;
        
        while (! iterator.isDone()) {
            int kind = iterator.currentSegment(data);
            
            // SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE
            
            switch(kind) {
                case PathIterator.SEG_MOVETO:
                    x = data[0];
                    y = data[1];
                    
                    x0 = x;
                    y0 = y;
                    
                    drawline = true;
                    break;
                
                case PathIterator.SEG_LINETO:
                    if (drawline)
                        line(g, x, y, data[0], data[1], frameColor);
                    
                    x = data[0];
                    y = data[1];
                    
                    drawline = true;
                    break;
                
                
                case PathIterator.SEG_QUADTO:
                    if (drawline)
                        line(g, x, y, data[0], data[1], frameColor);
                    
                    line(g, data[0], data[1], data[2], data[3], frameColor);
                    
                    x = data[2];
                    y = data[3];
                    
                    drawline = true;
                    break;
                
                
                case PathIterator.SEG_CUBICTO:
                    if (drawline)
                        line(g, x, y, data[0], data[1], frameColor);
                    
                    line(g, data[0], data[1], data[2], data[3], frameColor);
                    
                    line(g, data[2], data[3], data[4], data[5], frameColor);
                    
                    x = data[4];
                    y = data[5];
                    
                    drawline = true;
                    break;
                
                
                case PathIterator.SEG_CLOSE:
                    if (drawline)
                        line(g, x, y, x0, y0, frameColor);
                    
                    x = x0;
                    y = y0;
                    break;
                
                
                default:
                    break;
            }
            
            iterator.next();
        }
    }
    
    
    /**
     * <P>Shows the dots of the given shape, in the given dot color,
     * in the given graphics context.</P>
     * 
     * <P>If the graphics context or the shape is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>If the dot color is <CODE>null</CODE>,
     * it is set to <CODE>Color.red</CODE>.</P>
     *
     * @param g the graphics context
     * @param shape the shape to show dots
     * @param dotColor the color of the dots
     */
    public static void showShapeDots
        (Graphics g, Shape shape, Color dotColor)
    {
        if ((g == null) || (shape == null))
            return;
        
        if (dotColor == null)
            dotColor = Color.red;
        
        PathIterator iterator = shape.getPathIterator(null);
        double[] data = new double[6];
        
        while (! iterator.isDone()) {
            int kind = iterator.currentSegment(data);
            
            // SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE
            
            switch(kind) {
                case PathIterator.SEG_MOVETO:
                    dot(g, data[0], data[1], dotColor);
                    break;
                
                case PathIterator.SEG_LINETO:
                    dot(g, data[0], data[1], dotColor);
                    break;
                
                
                case PathIterator.SEG_QUADTO:
                    dot(g, data[2], data[3], dotColor);
                    break;
                
                
                case PathIterator.SEG_CUBICTO:
                    dot(g, data[4], data[5], dotColor);
                    break;
                
                
                case PathIterator.SEG_CLOSE:
                    break;
                
                
                default:
                    break;
            }
            
            iterator.next();
        }
    }
    
    
    /**
     * <P>Shows the dots of the frame of the given shape, in the given dot color,
     * in the given graphics context.</P>
     * 
     * <P>If the graphics context or the shape is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>If the dot color is <CODE>null</CODE>,
     * it is set to <CODE>Color.orange</CODE>.</P>
     *
     * @param g the graphics context
     * @param shape the shape to show frame dots
     * @param dotColor the color of the frame dots
     */
    public static void showShapeFrameDots
        (Graphics g, Shape shape, Color dotColor)
    {
        if ((g == null) || (shape == null))
            return;
        
        if (dotColor == null)
            dotColor = Color.orange;
        
        PathIterator iterator = shape.getPathIterator(null);
        double[] data = new double[6];
        
        while (! iterator.isDone()) {
            int kind = iterator.currentSegment(data);
            
            // SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE
            
            switch(kind) {
                case PathIterator.SEG_MOVETO:
                    dot(g, data[0], data[1], dotColor);
                    break;
                
                case PathIterator.SEG_LINETO:
                    dot(g, data[0], data[1], dotColor);
                    break;
                
                
                case PathIterator.SEG_QUADTO:
                    dot(g, data[0], data[1], dotColor);
                    dot(g, data[2], data[3], dotColor);
                    break;
                
                
                case PathIterator.SEG_CUBICTO:
                    dot(g, data[0], data[1], dotColor);
                    dot(g, data[2], data[3], dotColor);
                    dot(g, data[4], data[5], dotColor);
                    break;
                
                
                case PathIterator.SEG_CLOSE:
                    break;
                
                
                default:
                    break;
            }
            
            iterator.next();
        }
    }
    
    
    /**
     * <P>Shows the shape, its frame, its frame dots, and its own dots,
     * in the given colors or their defaults,
     * in the given graphics context.</P>
     * 
     * <P>If the graphics context or the shape is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>Defaults for <CODE>null</CODE> color values:</P>
     *
     * <UL>
     *   <LI><CODE>shapeColor</CODE>:    <CODE>Color.black</CODE></LI>
     *   <LI><CODE>frameColor</CODE>:    <CODE>Color.green</CODE></LI>
     *   <LI><CODE>shapeDotColor</CODE>: <CODE>Color.red</CODE></LI>
     *   <LI><CODE>frameDotColor</CODE>: <CODE>Color.orange</CODE></LI>
     * </UL>
     *
     * @param g the graphics context
     * @param shape the shape to show frame dots
     * @param shapeColor the color of the shape
     * @param frameColor the color of the frame
     * @param shapeDotColor the color of the shape dots
     * @param frameDotColor the color of the frame dots
     */
    public static void showShapeStructure(
        Graphics g,
        Shape shape,
        Color shapeColor,
        Color frameColor,
        Color shapeDotColor,
        Color frameDotColor)
    {
        if ((g == null) || (shape == null))
            return;
        
        if (shapeColor == null)
            shapeColor = Color.black;
        
        if (frameColor == null)
            frameColor = Color.green;
        
        if (shapeDotColor == null)
            shapeDotColor = Color.red;
        
        if (frameDotColor == null)
            frameDotColor = Color.orange;
        
        PaintMode.fill    (g, shape, shapeColor);
        
        showShapeFrame    (g, shape, frameColor);
        showShapeFrameDots(g, shape, frameDotColor);
        showShapeDots     (g, shape, shapeDotColor);
        
        PaintMode.draw    (g, shape, shapeDotColor, new BasicStroke(2));
    }
    
    
    /**
     * <P>Shows the shape, its frame, its frame dots, and its own dots,
     * in the default colors,
     * in the given graphics context.</P>
     * 
     * <P>If the graphics context or the shape is <CODE>null</CODE>,
     * then does nothing.</P>
     *
     * <P>Defaults for color values:</P>
     *
     * <UL>
     *   <LI><CODE>shapeColor</CODE>:    <CODE>Color.black</CODE></LI>
     *   <LI><CODE>frameColor</CODE>:    <CODE>Color.green</CODE></LI>
     *   <LI><CODE>shapeDotColor</CODE>: <CODE>Color.red</CODE></LI>
     *   <LI><CODE>frameDotColor</CODE>: <CODE>Color.orange</CODE></LI>
     * </UL>
     *
     * @param g the graphics context
     * @param shape the shape to show frame dots
     */
    public static void showShapeStructure(Graphics g, Shape shape) {
        showShapeStructure(g, shape, null, null, null, null);
    }
    
    
    /**
     * <P>Returns the structural information about a <CODE>Shape</CODE>
     * as a large, multi-line <CODE>String</CODE> that contains all of
     * the information in the <CODE>PathIterator</CODE> for the shape.</P>
     *
     * @return the shape information as a string
     */
    public static String shapeToString(Shape shape) {
        if (shape == null)
            return "Null shape\n\n";
        
        StringBuffer buffer = new StringBuffer();
        
        PathIterator iterator = shape.getPathIterator(null);
        double[] data = new double[6];
        
        while (! iterator.isDone()) {
            int kind = iterator.currentSegment(data);
            
            // SEG_MOVETO, SEG_LINETO, SEG_QUADTO, SEG_CUBICTO, or SEG_CLOSE
            
            switch(kind) {
                case PathIterator.SEG_MOVETO:
                    buffer.append("SEG_MOVETO\n");
                    buffer.append("x: " + data[0] + " y: " + data[1] + "\n");
                    buffer.append("\n");
                    break;
                
                case PathIterator.SEG_LINETO:
                    buffer.append("SEG_LINETO\n");
                    buffer.append("x: " + data[0] + " y: " + data[1] + "\n");
                    buffer.append("\n");
                    break;
                
                
                case PathIterator.SEG_QUADTO:
                    buffer.append("SEG_QUADTO\n");
                    buffer.append("x: " + data[0] + " y: " + data[1] + "\n");
                    buffer.append("x: " + data[2] + " y: " + data[3] + "\n");
                    buffer.append("\n");
                    break;
                
                
                case PathIterator.SEG_CUBICTO:
                    buffer.append("SEG_CUBICTO\n");
                    buffer.append("x: " + data[0] + " y: " + data[1] + "\n");
                    buffer.append("x: " + data[2] + " y: " + data[3] + "\n");
                    buffer.append("x: " + data[4] + " y: " + data[5] + "\n");
                    buffer.append("\n");
                    break;
                
                
                case PathIterator.SEG_CLOSE:
                    buffer.append("SEG_CLOSE\n");
                    buffer.append("\n");
                    break;
                
                
                default:
                    buffer.append("Error: Unknown path type\n");
                    buffer.append("\n");
                    break;
            }
            
            iterator.next();
        }
        
        return buffer.toString();
    }
    
}
