/*
    Finite State Machine Simulator

    Author:     Kyril Faenov
    Version:    1.0
    Date:       12/5/95

    Notes:

    Todo:

    - make sure while dialog are up menu items cannot be selected
    - add audio hints in addition to hint line
    - do better recovery if file save/load fails
*/


/*
    IMPORTS
*/


import java.applet.*;
import java.awt.*;
import java.lang.*;
import java.util.*;
import java.io.*;
import java.net.*;


/*
    INTERFACES
*/


/*
    StateIdResolver interface - classes complying to this interface must
    implement resolveId routine, which finds state object based on specified
    id.
*/


interface StateIdResolver
{
    /*
        return state object with specified id.
    */

    State resolveId (int id);

} /* end StateIdResolver */


/*
    PUBLIC CLASSES
*/


/*
    Simulator class - this is the top-level applet class. It does all of
    the initialization, event processing, re-paint initiation and dialog
    window management.
*/


public class Simulator extends Applet
{
    /*
        CLASS CONSTANTS
    */


    /*
        constants secifying what to do on the next mouse click
    */

    /* regular state */

    final static int    CLICK_NORMAL                  = 0;

    /* adding new state */

    final static int    CLICK_STATE_ADD               = 1;

    /* deleting state */

    final static int    CLICK_STATE_REMOVE            = 2;

    /* marking state as initial */

    final static int    CLICK_STATE_INITIAL           = 3;

    /* marking state as accepting */

    final static int    CLICK_STATE_ACCEPTING         = 4;

    /* marking state as normal */

    final static int    CLICK_STATE_NORMAL            = 5;

    /* selecting first state for new transition */

    final static int    CLICK_TRANSITION_FIRST_STATE  = 6;

    /* selecting last state for new transition */

    final static int    CLICK_TRANSITION_LAST_STATE   = 7;

    /* running simulation */

    final static int    CLICK_RUN_SIMULATION          = 8;

    /* selecting first state of the transition to be removed */

    final static int    CLICK_TRANSITION_REMOVE_FIRST = 9;

    /* selecting last state of the transition to be removed */

    final static int    CLICK_TRANSITION_REMOVE_LAST  = 10;

    /* applet's dimensions */

    final static int    DIMENSION   = 500;

    /* height of the button control area */

    final static int    CONTROL_HEIGHT = 75;


    /*
        INSTANCE VARIABLES
    */


    /* state machine */

    private Machine     machine;

    /* hint line support module */

    private Help        help;

    /* specifies what to do on the next mouse click (see the constants above) */

    private int         clickState;

    /* if set - clear the application panel on the next re-draw */

    private boolean     clearAll;


    /* if set - draggin of a state is in progress */

    private boolean     dragging;


    /*
        sub-dialog classes
    */

    private QuitDialog  quitDialog;
    private AboutBox    aboutBox;
    private TokenSelection tokenSelection;
    private TokenInput  tokenInput;
    private FileInput   fileInput;
    private Panel       buttons;


    /*
        PUBLIC METHODS
    */


    /*
        called when applet is being initialized
    */

    public void init ()
    {
        /* initialize instance variables */

        clickState = 0;
        clearAll   = true;
        dragging   = false;

        /* create help and machine class instances */

        help    = new Help ();
        machine = new Machine ();

        /* set applet size */

        resize (DIMENSION, DIMENSION);

        /* create button bar panel */

        buttons = new Panel ();

        /* set layout within the panel to 5 elements per row */

        buttons . setLayout (new GridLayout (0, 5));

        /* add all buttons */

        buttons . add (new Button ("New"));
        buttons . add (new Button ("Load"));
        buttons . add (new Button ("Save"));
        buttons . add (new Button ("About"));
        buttons . add (new Button ("Quit"));

        buttons . add (new Button ("Add State"));
        buttons . add (new Button ("Del State"));
        buttons . add (new Button ("Mark Initial"));
        buttons . add (new Button ("Mark Accepting"));
        buttons . add (new Button ("Mark Normal"));
        buttons . add (new Button ("Add Transition"));
        buttons . add (new Button ("Del Transition"));

        buttons . add (new Button ("Input String"));
        buttons . add (new Button ("Run"));
        buttons . add (new Button ("Stop"));

        /* add button panel to applet panel */

        add (buttons);

        /* resize bar panel and position it at the top of the applet */

        buttons . resize (DIMENSION, CONTROL_HEIGHT);
        buttons . move (0, 0);
        buttons . show ();

        /* create dialog panels and add them to applet panel 
           note that dialogs are initialized hidden and will not show
           up until show() method is invoked on them. */

        aboutBox       = new AboutBox       (this);
        quitDialog     = new QuitDialog     (this);
        tokenSelection = new TokenSelection (this);
        tokenInput     = new TokenInput     (this);
        fileInput      = new FileInput      (this);

        add (aboutBox);
        add (quitDialog);
        add (tokenSelection);
        add (tokenInput);
        add (fileInput);

        /* let the dialogs know about the machine - they will have
           to communicate user commands and input to it */

        tokenInput     . setMachine (machine, help);
        tokenSelection . setMachine (machine, help);
        fileInput      . setMachine (machine, help);

        /* display initial message on the hint line */

        help . setHelp (help . INITIAL);

        /* make applet show now */

        show ();

    } /* end init */


    /*
        override the layout method with the one that does nothing. we do not
        need layout for the applet panel since all the positioning being done
        manually.
    */

    public synchronized void layout ()
    {

    } /* end layout */


    /*
        this method is called when repaint of the panle is requested
    */

    public void paint (Graphics g)
    {
        Rectangle size = bounds ();

        /* if cleanup was requested - clear the entire panel */

        if (clearAll)
        {
            clearAll = false;

            g . setColor (Color . lightGray);
            g . fillRect (0, 0, size . width, size . height);
        }

        /* repaint the components */

        buttons . paint (g);
        machine . paint (g, CONTROL_HEIGHT + 5);
        help    . paint (g, size . width, size . height);

    } /* end paint */


    /*
        this method handles all of the the events for the applet
    */

    public boolean handleEvent (Event evt)
    {
        switch (evt . id)
        {

            case Event . MOUSE_DOWN:

                switch (clickState)
                {
                    case CLICK_STATE_ADD:

                        /* tell machine to create a new state at the location
                           of the mouse click */

                        if (! machine . addState (evt . x, evt . y))
                            help . setHelp (help . MAX_STATES);
                        else
                            help . setHelp (help . EMPTY);

                        break;

                    case CLICK_STATE_REMOVE:

                        /* tell machine to remove a state at the location
                           of the mouse click */

                        if (! machine . removeState (evt . x, evt . y))
                            help . setHelp (help . NO_STATE);

                        break;

                    case CLICK_STATE_INITIAL:

                        /* tell machine to mark a state at the location
                           of the mouse click as initial */

                        if (! machine . makeStateInitial (evt . x, evt . y))
                            help . setHelp (help . INITIAL_STATE_PRESENT);
                        else
                            help . setHelp (help . EMPTY);

                        break;

                    case CLICK_STATE_ACCEPTING:

                        /* tell machine to mark a state at the location
                           of the mouse click as accepting */

                        if (! machine . makeStateAccepting (evt . x, evt . y))
                            help . setHelp (help . NO_STATE);
                        else
                            help . setHelp (help . EMPTY);

                        break;

                    case CLICK_STATE_NORMAL:

                        /* tell machine to mark a state at the location
                           of the mouse click as normal */

                        if (! machine . makeStateNormal (evt . x, evt . y))
                            help . setHelp (help . NO_STATE);
                        else
                            help . setHelp (help . EMPTY);

                        break;

                    case CLICK_TRANSITION_FIRST_STATE:

                        /* tell machine to mark a state at the location
                           of the mouse click as first state for the upcoming
                           transition */

                        if (! machine . selectFirstState (evt . x, evt . y))
                        {
                            help . setHelp (help . NO_STATE);
                            break;
                        }

                        help . setHelp (help . LAST_STATE);

                        /* go on to selecting last state */

                        clickState = CLICK_TRANSITION_LAST_STATE;

                        repaint ();

                        return true;

                    case CLICK_TRANSITION_LAST_STATE:

                        /* tell machine to mark a state at the location
                           of the mouse click as last state for the upcoming
                           transition */

                        if (! machine . selectLastState (evt . x, evt . y))
                        {
                            help . setHelp (help . NO_STATE);
                            break;
                        }

                        help . setHelp (help . EMPTY);

                        /* popup token selection dialog */

                        tokenSelection . show();

                        break;

                    case CLICK_TRANSITION_REMOVE_FIRST:

                        /* tell machine to mark a state at the location
                           of the mouse click as first state for the upcoming
                           transition removal */

                        if (! machine . selectFirstState (evt . x, evt . y))
                        {
                            help . setHelp (help . NO_STATE);
                            break;
                        }

                        help . setHelp (help . LAST_STATE);

                        /* go on to selecting last state */

                        clickState = CLICK_TRANSITION_REMOVE_LAST;

                        repaint ();

                        return true;

                    case CLICK_TRANSITION_REMOVE_LAST:

                        /* tell machine to mark a state at the location
                           of the mouse click as first state for the upcoming
                           transition removal */

                        if (! machine . selectLastState (evt . x, evt . y))
                        {
                            help . setHelp (help . NO_STATE);
                            break;
                        }

                        help . setHelp (help . EMPTY);

                        /* remove the transition from first state to last state */

                        machine . removeTransition ();

                        break;

                    case CLICK_RUN_SIMULATION:

                        /* invoke runSimulation method on machine until it returns
                           false */

                        switch (machine . runSimulation ())
                        {
                            case Machine . STATUS_ACCEPTED:
                                help . setHelp (help . STRING_ACCEPTED);
                                break;

                            case Machine . STATUS_NOT_ACCEPTED:
                                help . setHelp (help . STRING_NOT_ACCEPTED);
                                break;

                            case Machine . STATUS_BAD_TOKEN:
                                help . setHelp (help . BAD_TOKEN);
                                break;

                            case Machine . STATUS_BAD_GRAMMAR:
                                help . setHelp (help . BAD_MACHINE);
                                break;

                            case Machine . STATUS_NORMAL:
                            default:
                                repaint ();
                                return true;
                        }

                        break;

                    case CLICK_NORMAL:
                    default:

                        /* see if there is a state to drag */

                        dragging = machine . selectDragState (evt . x, evt . y);

                        break;
                }

                clickState = CLICK_NORMAL;

                break;

            case Event . MOUSE_UP:

                /* if we are dragging something - stop it */

                if (dragging)
                {
                    machine . deselectDragState (evt . x, evt . y);
                    dragging = false;
                }

                break;

            case Event . MOUSE_DRAG:

                /* if we are dragging something - keep dragging */

                if (dragging)
                    machine . dragState (evt . x, evt . y);

                break;

            /* handle action events (button presses) */

            case Event . ACTION_EVENT:

                /* only respond to button presses */

                if (evt . target instanceof Button)
                {
                    /* find out the name of the button pressed */

                    String label = ((Button)(evt . target)) . getLabel ();

                    /* pressing a button destroys previous click state */

                    clickState = CLICK_NORMAL;

                    if (label . equals ("Quit"))
                    {
                        /* popup quit confirmation dialog */

                        quitDialog . show ();
                    }
                    else if (label . equals ("About"))
                    {
                        /* popup about window */

                        aboutBox . show();

                        /* play (getCodeBase (), "welcome.au"); */
                    }
                    else if (label . equals ("New"))
                    {
                        clearAll = true;

                        /* create new machine instance - the old one will get
                           garbage collected */

                        machine = new Machine ();

                        /* link dialogs with the new machine */

                        tokenInput     . setMachine (machine, help);
                        fileInput      . setMachine (machine, help);
                        tokenSelection . setMachine (machine, help);

                        help . setHelp (help . INITIAL);
                    }
                    else if (label . equals ("Load"))
                    {
                        /* 'load' does implicit 'new' */

                        clearAll = true;

                        /* create new machine instance - the old one will get
                           garbage collected */

                        machine = new Machine ();

                        /* link dialogs with the new machine */

                        tokenInput     . setMachine (machine, help);
                        tokenSelection . setMachine (machine, help);
                        fileInput      . setMachine (machine, help);

                        help . setHelp (help . INITIAL);

                        /* tell file dialog to perform load and pop it up */

                        fileInput . setMode (FileInput . LOAD);
                        fileInput . show();
                    }
                    else if (label . equals ("Save"))
                    {
                        /* tell file dialog to perform save and pop it up */

                        fileInput . setMode (FileInput . SAVE);
                        fileInput . show();
                    }
                    else if (label . equals ("Input String"))
                    {
                        /* popup input string dialog */

                        tokenInput . show();
                    }
                    else if (label . equals ("Run"))
                    {
                        /* tell machine to start simulation */

                        switch (machine . startSimulation ())
                        {
                            case Machine . STATUS_NORMAL:
                                help . setHelp (help . RUN_INSTRUCTIONS);
                                clickState = CLICK_RUN_SIMULATION;
                                break;

                            case Machine . STATUS_NO_INPUT:
                                help . setHelp (help . NO_INPUT_STRING);
                                break;

                            case Machine . STATUS_NO_INITIAL:
                                help . setHelp (help . NO_INITIAL_STATE);
                                break;

                            case Machine . STATUS_NO_ACCEPTING:
                                help . setHelp (help . NO_ACCEPTING_STATE);
                                break;

                            default:
                                break;
                        }

                    }
                    else if (label . equals ("Stop"))
                    {
                        /* tell machine to stop simulation */

                        machine . stopSimulation ();
                        help . setHelp (help . EMPTY);
                        clickState = CLICK_NORMAL;
                    }

                    /* for the following buttons just set the appropriate
                       click state and hint line text. all the work will be
                       done after the mouse is clicked */

                    else if (label . equals ("Add State"))
                    {
                        clickState = CLICK_STATE_ADD;
                        help . setHelp (help . SELECT_LOCATION);
                    }
                    else if (label . equals ("Del State"))
                    {
                        clickState = CLICK_STATE_REMOVE;
                        help . setHelp (help . TARGET_STATE);
                    }
                    else if (label . equals ("Mark Initial"))
                    {
                        clickState = CLICK_STATE_INITIAL;
                        help . setHelp (help . TARGET_STATE);
                    }
                    else if (label . equals ("Mark Accepting"))
                    {
                        clickState = CLICK_STATE_ACCEPTING;
                        help . setHelp (help . TARGET_STATE);
                    }
                    else if (label . equals ("Mark Normal"))
                    {
                        clickState = CLICK_STATE_NORMAL;
                        help . setHelp (help . TARGET_STATE);
                    }
                    else if (label . equals ("Add Transition"))
                    {
                        clickState = CLICK_TRANSITION_FIRST_STATE;
                        help . setHelp (help . FIRST_STATE);
                    }
                    else if (label . equals ("Del Transition"))
                    {
                        clickState = CLICK_TRANSITION_REMOVE_FIRST;
                        help . setHelp (help . FIRST_STATE);
                    }
                }

                break;

            /* for all other events run the default Applet class handler */

            default:


                return super . handleEvent (evt);
        }

        /* force applet repaint as something could have changed */

        repaint ();

        /* return true - means we have handled the event and there is no
           no need to pass it along to other components */

        return true;

    } /* end handleEvent */


} /* end Simulator */


/*
    PRIVATE CLASSES
*/


/*
    Input class - an abstract class for text input dialog. Allows user to
    enter some textual data and pass it to the state machine. Subclasses
    must implement passInput method.
*/

abstract class Input extends Panel
{
    /*
        INSTANCE VARIABLES
    */


    /* text field where user will be entering parameters */

    private TextField   input;

    /* parent object */

    private Panel       parent;

    /* state machine object that will be given entered parameters */

    protected Machine   machine;


    /* hint line object */

    protected Help      help;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    public Input (Panel par, String label)
    {
        /* initialize instance variables */

        parent  = par;
        machine = (Machine) null;
        help    = (Help) null;

        setBackground (new Color (150, 150, 150));

        resize (400, 120);

        Rectangle bounds = par . bounds ();
        move ((bounds . width  - 400) / 2, (bounds . height - 120) / 2);

        /* create and add to dialog command label */

        Label l = new Label (label);
        l . setAlignment (Label . CENTER);
        add ("North", l);

        /* create and add to dialog text field */

        input = new TextField (50);
        add ("Center", input);

        /* create and add to dialog a panel holding two buttons */

        Panel p = new Panel ();
        p . add (new Button ("OK"));
        p . add (new Button ("Cancel"));
        add ("South", p);

        /* make sure dialog does not display until specifically told to */

        hide ();

    } /* end Input */


    /*
        set machine object that will receive the entered entered parameters
    */

    public void setMachine (Machine m, Help h)
    {
        machine = m;
        help = h;

    } /* end setMachine */


    /*
        during repaint draw a 3d rectangle around the dialog for cooler look
    */

    public void paint (Graphics g)
    {
        Rectangle bounds = bounds ();

        g . setColor (getBackground ());
        g . draw3DRect (0, 0, bounds . width - 1, bounds . height - 1, true);

    } /* end paint */


    /*
        leave some space around the edges
    */

    public Insets insets()
    {
        return new Insets (5, 5, 5, 5);

    } /* end Insets */


    /*
        pass data to the state machine - must be implemented by subclass
    */

    abstract public void passInput (String i);


    /*
        handle action events (button presses)
    */

    public boolean action (Event e, Object obj)
    {
        String label = (String) obj;

        /* only handle button presses */

        if (e . target instanceof Button)
        {
            if (label . equals ("OK"))
            {
                /* pass entered text to the machine */

                passInput (input . getText ());

                /* hide our dialog and force repaint of the parent */

                hide();
                parent . repaint ();

                return true;
            }
            else if (label . equals ("Cancel"))
            {
                /* clear the text field */

                input . setText ("");

                /* hide our dialog and force repaint of the parent */

                hide();
                parent . repaint ();

                return true;
            }
        }

        return true;

    } /* action */

} /* end Input */


/*
    FileInput class - allows user to enter the file name for load/save operations.
*/

class FileInput extends Input
{
    /*
        CLASS CONSTANTS
    */


    /* specifies which action (load or save) is to be performed when OK button
       is pressed. */

    final static int    LOAD = 1;
    final static int    SAVE = 2;


    /*
        INSTANCE VARIABLES
    */


    /* current mode (load or save) */

    private int         mode;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    public FileInput (Panel par)
    {
        super (par, "Type file name:");

        /* initialize instance variables */

        mode = SAVE;

    } /* end FileInput */


    /*
        set dialog to specified mode (load or save)
    */

    public void setMode (int m)
    {
        mode = m;

    } /* setMode */


    /*
        pass input to the state machine
    */

    public void passInput (String i)
    {
        if (mode == LOAD)
            switch (machine . loadFile (i))
            {
                case Machine . STATUS_FILE_NOT_FOUND:
                    help . setHelp (help . FILE_NOT_FOUND);
                    break;

                case Machine . STATUS_BAD_INPUT_FILE:
                    help . setHelp (help . FILE_BAD_INPUT);
                    break;

                case Machine . STATUS_READ_ERROR:
                    help . setHelp (help . FILE_READ_ERROR);
                    break;

                case Machine . STATUS_NORMAL:
                default:
                    help . setHelp (help . FILE_LOADED_OK);
                    break;
            }

        else
            switch (machine . saveFile (i))
            {
                case Machine . STATUS_FILE_CREATE_ERROR:
                    help . setHelp (help . FILE_CREATE_ERROR);
                    break;

                case Machine . STATUS_WRITE_ERROR:
                    help . setHelp (help . FILE_WRITE_ERROR);
                    break;

                case Machine . STATUS_NORMAL:
                default:
                    help . setHelp (help . FILE_SAVED_OK);
                    break;
            }

    } /* end setMachine */

} /* end FileInput */


/*
    TokenInput class - allows user to enter the token string that will be used
    as the input for the state machine during simulation.
*/


class TokenInput extends Input
{
    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    public TokenInput (Panel par)
    {
        super (par, "Type in input tokens:");

    } /* end TokenSelection */


    /*
        pass input to the state machine
    */

    public void passInput (String i)
    {
        if (i . length () > 0)
            machine . setInputString (i);
        else
            help . setHelp (Help . NO_TOKEN_STRING);

    } /* end setMachine */

} /* end TokenInput */


/*
    TokenSelection class - presents user with a set of checkboxes, one for each
    possible token. The selected tokens are encoded into a boolean array and
    transition is created between the start and finish states.
*/


class TokenSelection extends Panel
{
    /*
        INSTANCE VARIABLES
    */


    /* panel containing checkboxes */

    private Panel       checkPanel;

    /* machine object associated with this dialog */

    private Machine     machine;

    /* hint line object */

    private Help        help;

    /* parent object */

    private Panel       parent;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    public TokenSelection (Panel par)
    {
        /* initialize instance variables */

        parent = par;

        setBackground (new Color (150, 150, 150));

        resize (450, 150);

        Rectangle bounds = par . bounds ();
        move ((bounds . width  - 450) / 2, (bounds . height - 150) / 2);

        /* create and add to dialog command label */

        Label l = new Label ("Select tokens for this transition:", Label . CENTER);
        add ("North", l);

        /* create panel for holding checkboxes and set its layout to display
           10 elements in a row */

        checkPanel = new Panel ();
        checkPanel . setLayout(new GridLayout (0, 10));

        /* add a checkbox for each token */

        for (int i = 0; i < InputString . TOKENS; i ++)
        {
            Character ch = new Character ((char) (InputString . LABEL_SHIFT + i));
            checkPanel . add (new Checkbox (ch . toString ()));
        }

        /* add a checkbox for all tokens */

        checkPanel . add (new Checkbox ("All"));

        /* add the panel with checkboxes to dialog */

        add ("Center", checkPanel);

        /* create and add to dialog a button */

        add ("South", new Button ("OK"));

        /* make sure dialog does not display until specifically told to */

        hide ();

    } /* end TokenSelection */


    /*
        set machine object that will receive the entered entered parameters
    */

    public void setMachine (Machine m, Help h)
    {
        machine = m;
        help = h;

    } /* end setMachine */


    /*
        during repaint draw a 3d rectangle around the dialog for cooler look
    */

    public void paint (Graphics g)
    {
        Rectangle bounds = bounds ();

        g . setColor (getBackground ());

        g . draw3DRect (0, 0, bounds . width - 1, bounds . height - 1, true);

    } /* end paint */


    /*
        leave some space around the edges
    */

    public Insets insets()
    {
        return new Insets (5, 5, 5, 5);

    } /* end Insets */


    /*
        handle action events (button presses)
    */

    public boolean action (Event e, Object obj)
    {
        /* only handle button presses */

        if (e . target instanceof Button)
        {
            /* allocate array that will hold true flags for each token
               selected. the last entry is used as All making the transition
               respond to all tokenss */

            boolean tokens [] = new boolean [InputString . TOKENS + 1];

            /* this boolean will make sure at least one token is selected. it
               will hold the OR of all the booleans in the above array */

            boolean     exists = false;

            /* go through the checkboxes and extract their values into the
               array */

            for (int i = 0; i <= InputString . TOKENS; i++)
            {
                Checkbox cb = (Checkbox) checkPanel . getComponent (i);

                tokens [i] = cb . getState ();

                exists = exists || tokens [i];

                /* reset the checkbox state to clear */

                if (tokens [i])
                    cb . setState (false);
            }

            /* if at least one token was selected - create the transition */

            if (exists)
                machine . addTransition (tokens);
            else
                help . setHelp (Help . NO_TOKENS);

            /* hide our dialog and force repaint of the parent */

            hide();
            parent . repaint ();

            return true;
        }

        return true;

    } /* action */

} /* end TokenSelection */


/*
    About class - displays the program and author names.
*/


class AboutBox extends Panel
{
    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    public AboutBox (Panel par)
    {
        setBackground (new Color (150, 150, 150));

        resize (220, 100);

        Rectangle bounds = par . bounds ();
        move ((bounds . width  - 220) / 2, (bounds . height - 100) / 2);

        /* create and add to dialog labels */

        Label l = new Label ("State Machine Simulator", Label . CENTER);
        l . setForeground (Color . red);
        l . setFont (new Font ("Helvetica", Font . BOLD, 14));
        add ("North", l);

        l = new Label ("Developed by Kyril Faenov, 1995", Label . CENTER);
        l . setForeground (Color . blue);
        l . setFont (new Font ("Helvetica", Font . ITALIC, 10));
        add ("Center", l);

        /* create and add to dialog a button */

        add ("South", new Button ("OK"));

        /* make sure dialog does not display until specifically told to */

        hide ();

    } /* end AboutBox */


    /*
        during repaint draw a 3d rectangle around the dialog for cooler look
    */


    public void paint (Graphics g)
    {
        Rectangle bounds = bounds ();

        g . setColor (getBackground ());
        g . draw3DRect (0, 0, bounds . width - 1, bounds . height - 1, true);

    } /* end paint */


    /*
        leave some space around the edges
    */


    public Insets insets()
    {
        return new Insets (5, 5, 5, 5);

    } /* end Insets */


    /*
        handle action events (button presses)
    */

    public boolean action (Event e, Object obj)
    {
        /* just make the dialog go away */

        hide();
        return true;

    } /* action */

} /* end AboutBox */


/*
    QuitDialog class - gives user a chance to confirm the exit
*/


class QuitDialog extends Panel
{
    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    public QuitDialog (Panel par)
    {
        setBackground (new Color (150, 150, 150));

        resize (100, 75);

        Rectangle bounds = par . bounds ();
        move ((bounds . width  - 100) / 2, (bounds . height - 75) / 2);

        /* create and add to dialog labels */

        Label l = new Label ("Really quit?:", Label . CENTER);
        add ("North", l);

        /* create and add to dialog a panel holding two buttons */

        Panel p = new Panel ();
        p . add (new Button ("Yes"));
        p . add (new Button ("No"));
        add ("South", p);

        /* make sure dialog does not display until specifically told to */

        hide ();

    } /* end QuitDialog */


    /*
        during repaint draw a 3d rectangle around the dialog for cooler look
    */

    public void paint (Graphics g)
    {
        Rectangle bounds = bounds ();

        g . setColor (getBackground ());
        g . draw3DRect (0, 0, bounds . width - 1, bounds . height - 1, true);

    } /* end paint */


    /*
        handle action events (button presses)
    */

    public boolean action (Event e, Object arg)
    {
        String label = (String) arg;

        if (label . equals ("Yes"))
        {
            /* perform system exit */

            System . exit (0);
        }
        else if (label . equals ("No"))
        {
            /* make dialog go away */

            hide ();
        }

        return true;

    } /* end action */

} /* end QuitDialog */


/*
    Machine class - implements state machine abstraction - a collection of
    state objects and operations on them.
*/


class Machine implements StateIdResolver
{
    /*
        CLASS CONSTANTS
    */


    /*
        return values from some calls
    */


    /* normal status */

    final static int    STATUS_NORMAL            = 1;

    /* cannot start simulation - no input string */

    final static int    STATUS_NO_INPUT          = 2;

    /* cannot start simulation - no initial state */

    final static int    STATUS_NO_INITIAL        = 3;

    /* cannot start simulation - no accepting state */

    final static int    STATUS_NO_ACCEPTING      = 4;

    /* string was accepted by the machine */

    final static int    STATUS_ACCEPTED          = 5;

    /* string was NOT accepted by the machine */

    final static int    STATUS_NOT_ACCEPTED      = 6;

    /* bad token was encountered */

    final static int    STATUS_BAD_TOKEN         = 7;

    /* bad grammar was encountered */

    final static int    STATUS_BAD_GRAMMAR       = 8;

    /* file specified for loading was not found */

    final static int    STATUS_FILE_NOT_FOUND    = 9;

    /* file specified for savinging could not be created */

    final static int    STATUS_FILE_CREATE_ERROR = 10;

    /* file read error */

    final static int    STATUS_READ_ERROR        = 11;

    /* file write error */

    final static int    STATUS_WRITE_ERROR       = 12;

    /* input file improperly constructed error */

    final static int    STATUS_BAD_INPUT_FILE    = 13;


    /* maximum number of states */

    final static int    STATES = 10;

    /*
        code characters used to mark the file as created by this program
    */

    final static int    CODE1 = (int) 'F';
    final static int    CODE2 = (int) 'S';
    final static int    CODE3 = (int) 'M';
    final static int    CODE4 = (int) 'S';

    /*
        codes characters used to delimit fields in the files
    */

    /* beginning of input string data */

    final static int    BOI = 0x7D;

    /* beginning of state data */

    final static int    BOS = 0x7E;

    /* end of machine data (end of file) */

    final static int    EOM = 0x7F;


    /*
        INSTANCE VARIABLES
    */


    /* array of state objects */

    private State       states [];

    /* number of states currently present */

    private int         numStates;

    /* state we are currently removing */

    private State       removing;

    /* initial state */

    private State       initialState;

    /* first state of a new transition*/

    private State       firstState;

    /* last state of a new transition */

    private State       lastState;

    /* state being dragged */

    private State       dragState;

    /* current state during simulation */

    private State       currState;

    /* number of accepting states */

    private int         acceptingStates;

    /* current input string object */

    private InputString currInputString;

    /* new input string */

    private InputString newInputString;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method
    */

    Machine ()
    {
        /* initialize instance variables */

        removing = initialState = currState = firstState = lastState =
        dragState = (State) null;
        currInputString = newInputString  = (InputString) null;

        acceptingStates = 0;
        numStates       = 0;

        states = new State [STATES];

        for (int i = 0; i < STATES; i ++)
            states [i] = (State) null;

    } /* end Machine */


    /*
        this routine implements StateIdResolver interface. returns the state
        corresponding to the specified id.
    */

    public State resolveId (int id)
    {
        if (id < 0 || id >= STATES)
            return (State) null;

        return states [id];

    } /* end resolveId */


    /*
        display state machine on the specified graphics context.
    */

    public synchronized void paint (Graphics g, int offset)
    {
        /* if a state was marked for removal - call its remove method to delete
           its image */

        if (removing != (State) null)
        {
            removing . remove (g);
            removing = (State) null;
        }

        /* if new input string was specified - call the current string's remove
           method to delete its image and then set the current string to the
           new instance. */

        if (newInputString != (InputString) null)
        {
            currInputString . remove (g, offset);
            currInputString = newInputString;
            newInputString = (InputString) null;
        }

        /* paint all existing states on the screen. notice that we traverse
           the array in the reverse order, so that if states overlap on the
           screen, the ones with the lowest ids will end up on top and will
           correspond to the ones that get selected when user clicks on them */

        for (int i = STATES - 1; i >= 0; i --)
            if (states [i] != (State) null)
                states [i] . paint (g);

        /* if input string was set - draw it */

        if (currInputString != (InputString) null)
            currInputString . paint (g, offset);

    } /* end paint */


    /*
        add a new state at specified coordinates.
    */

    public boolean addState (int x, int y)
    {
        /* if maximum number of states already exists - exit */

        if (numStates == STATES)
            return false;

        numStates ++;

        int i;

        /* look for the first empty slot in the states array */

        for (i = 0; i < STATES; i ++)
            if (states [i] == (State) null)
                break;

        /* create new state */

        states [i] = new State (i, x, y);

        return true;

    } /* end addState */


    /*
        remove state at specified coordinates. if more than one state exist
        at that location, the one with the lowest id will be removed.
    */

    public boolean removeState (int x, int y)
    {
        /* make sure we have something to remove */

        if (numStates == 0)
            return false;

        numStates --;

        int i;

        /* find the first state that acknowledges that the point lays inside
           of its space */

        for (i = 0; i < STATES; i ++)
            if (states [i] != (State) null && states [i] . inside (x, y))
                break;

        if (i == STATES)
            return false;

        /* we cannot completely get rid of it yet, as we will have to remove
           the states drawings. this will be done at the next call to paint,
           so just set the removing to states object. */

        removing = states [i];

        /* strip the state from its initial and accepting parameters and
           invalidate it. invalidation is needed to remove the transitions
           whose destination this state is. state knows only of transitions
           starting with it, but it cannot invalidate the ones ending at it.
           transitions will check if their traget state is valid during update
           calls and invalidate themselves if necessary. */

        makeNormal (removing);
        removing . makeInvalid ();

        states [i] = (State) null;

        return true;

    } /* end removeState */


    /*
        mark the state at specified coordinates as initial. if more than one state
        exist at that location, the one with the lowest id will be marked.
    */

    public boolean makeStateInitial (int x, int y)
    {
        /* cannot have more than one initial state */

        if (initialState != (State) null)
            return false;

        initialState = locateState (x, y);

        if (initialState == (State) null)
            return false;

        initialState . makeInitial ();

        return true;

    } /* end makeStateInitial */


    /*
        mark the state at specified coordinates as accepting. if more than one
        state exist at that location, the one with the lowest id will be marked.
    */

    public boolean makeStateAccepting (int x, int y)
    {
        State state = locateState (x, y);

        if (state == (State) null)
            return false;

        state . makeAccepting ();
        acceptingStates ++;

        return true;

    } /* end makeStateAccepting */


    /*
        mark the state at specified coordinates as normal. if more than one state
        exist at that location, the one with the lowest id will be marked.
    */

    public boolean makeStateNormal (int x, int y)
    {
        State state = locateState (x, y);

        if (state == (State) null)
            return false;

        makeNormal (state);

        return true;

    } /* end makeStateNormal */


    /*
        select the first state for adding the transition. if more than one state
        exist at that location, the one with the lowest id will be marked.
    */


    public boolean selectFirstState (int x, int y)
    {
        firstState = locateState (x, y);

        if (firstState == (State) null)
            return false;

        /* highlight the state and update the hint line */

        firstState . highlightOn ();

        return true;

    } /* end selectFirstState */


    /*
        select the last state for adding the transition. if more than one state
        exist at that location, the one with the lowest id will be marked.
    */


    public boolean selectLastState (int x, int y)
    {
        lastState = locateState (x, y);

        if (lastState == (State) null)
            return false;

        if (firstState != (State) null)
            firstState . highlightOff ();

        return true;

    } /* end selectLastState */


    /*
        add transition between the first and the last states.
    */

    public void addTransition (boolean [] tokens)
    {
        if (firstState != (State) null && lastState != (State) null)
        {
            firstState . addTransition (lastState, tokens);
            firstState = lastState = (State) null;
        }

    } /* end addTransition */


    /*
        remove transition between the first and the last states.
    */

    public void removeTransition ()
    {
        if (firstState != (State) null && lastState != (State) null)
        {
            firstState . removeTransition (lastState);
            firstState = lastState = (State) null;
        }

    } /* end removeTransition */


    /*
        select the state for dragging. if more than one state have these
        coordinates, the one with the lowest id will be selected.
    */

    public boolean selectDragState (int x, int y)
    {
        dragState = locateState (x, y);

        if (dragState == (State) null)
            return false;

        dragState . dragStart (x, y);

        return true;

    } /* end selectDragState */


    /*
        stop dragging the current state.
    */

    public void deselectDragState (int x, int y)
    {
        if (dragState != (State) null)
            dragState . dragStop (x, y);

        dragState = (State) null;

    } /* deselectDragState */


    /*
        drag the current drag state.
    */

    public void dragState (int x, int y)
    {
        if (dragState != (State) null)
            dragState . drag (x, y);

    } /* end dragState */


    /*
        set the input string of tokens from the specified character string.
    */

    public void setInputString (String s)
    {
        /* if one already exists cannot replace it now as it will have to be
           removed from the screen in paint() call. just set the newInputString,
           current one will be set to that value in paint() */

        if (currInputString != (InputString) null)
        {
            newInputString = new InputString (s);
            return;
        }

        currInputString = new InputString (s);

    } /* end setInputString */


    /*
        make sure simulation can be started (necessary conditions exist), stop
        the previous one if necessary.
    */

    public int startSimulation ()
    {
        /* make sure we have input string, initial state and at least one
           accepting state */

        if (currInputString == (InputString) null)
            return STATUS_NO_INPUT;

        if (initialState == (State) null)
            return STATUS_NO_INITIAL;

        if (acceptingStates <= 0)
            return STATUS_NO_ACCEPTING;

        /* stop previous simulation */

        stopSimulation ();

        return STATUS_NORMAL;

    } /* end startSimulation */


    /*
        stop simulation in progress.
    */

    public void stopSimulation ()
    {
        /* rewind token string */

        currInputString . rewind ();

        /* if some state holds token - make it give up the token and turn off
           the highlight */

        if (currState != (State) null)
        {
            currState . giveToken ();
            currState . highlightOff ();
            currState = (State) null;
        }

    } /* end stopSimulation */


    /*
        run one round of simulation.
    */


    public int runSimulation ()
    {
        /* if no state is marked as current - make the initial state current */

        if (currState == (State) null)
            currState = initialState;

        /* else make the current state give up the token, and by doing so it will
           tell us what the next state is. also advance to the next token in
           the input string. */

        else
        {
            currState = currState . giveToken ();
            currInputString . advanceIndex ();
        }

        /* get next token out of the input string */

        int token = currInputString . currentToken ();

        /* if reached end of input string */

        if (token == InputString . EOS)
        {
            /* if current state is accepting - the machine accepted the string.
               highlight the state to signify success. */

            if (currState . isAccepting ())
            {
                currState . highlightOn ();
                return STATUS_ACCEPTED;
            }
            else
                return STATUS_NOT_ACCEPTED;
        }

        /* if bad token - print the message and halt the simulation */

        if (token == InputString . BAD_TOKEN)
            return STATUS_BAD_TOKEN;

        /* give the new token to current state. if it refuses to take it there
           is a problem with the grammar - either there was zero or more than
           one transitions that fired for this token. */

        if (! currState . takeToken (token))
            return STATUS_BAD_GRAMMAR;

        return STATUS_NORMAL;

    } /* end runSimulation */


    /*
        load new state machine from the specified file.
    */

    public synchronized int loadFile (String fileName)
    {
        FileInputStream file;

        /* open the file, catching the file not found exception */

        try
        {
            file = new FileInputStream (fileName);
        }
        catch (FileNotFoundException e)
        {
            return STATUS_FILE_NOT_FOUND;
        }

        /* read from the file catching the I/O error exception */

        try
        {
            /* make sure the file was created by this program */

            if (file . read () != CODE1 || file . read () != CODE2 ||
                file . read () != CODE3 || file . read () != CODE4)
            {
                return STATUS_BAD_INPUT_FILE;
            }

            int val = 0;

            /* while delimiter is not End Of Machine - keep reading */

            while (val != EOM)
            {
                /* read the next delimiter */

                val = file . read ();

                /* if the delimiter marks Beginning Of State information */

                if (val == BOS)
                {
                    /* read the byte encoding state's id */

                    val = file . read ();

                    if (val < 0 || val >= STATES)
                        return STATUS_BAD_INPUT_FILE;

                    /* create new state with this id. call the constructor
                       that will initialize the state with the information
                       from the file. */

                    states [val] = new State (val, file);
                }

                /* if the delimiter marks Beginning Of Input string */

                else if (val == BOI)
                {
                    /* create new input string and call the constructor that
                       will initialize the string with the information from the
                       file. */

                    currInputString = new InputString (file);
                }

                /* any delimiter other than EOM means garbage */

                else if (val != EOM)
                    return STATUS_BAD_INPUT_FILE;
            }

            /* resolve state IDs in transitions. transitions got initialized
               with state IDs, not objects since some of them were read from the
               file before the corresponding states. this step will resovle
               that. */

            acceptingStates = 0;
            numStates       = 0;

            for (int i = 0; i < STATES; i ++)
            {
                if (states [i] != (State) null)
                {
                    numStates ++;

                    /* initialize the initialState and acceptingStates variables
                        according to state information */

                    if (states [i] . isInitial ())
                        initialState = states [i];

                    if (states [i] . isAccepting ())
                        acceptingStates ++;

                    states [i] . resolveId ((StateIdResolver) this);
                }
            }
        }
        catch (IOException e)
        {
            return STATUS_READ_ERROR;
        }

        return STATUS_NORMAL;

    } /* end loadFile */


    /*
        save current state machine data into specified file.
    */

    public int saveFile (String fileName)
    {
        FileOutputStream file;

        /* open the output file catching the I/O error exception. */

        try
        {
            file = new FileOutputStream (fileName);
        }
        catch (IOException e)
        {
            return STATUS_FILE_CREATE_ERROR;
        }

        /* write to file catching the I/O error exception. */

        try
        {
            /* write out file signature */

            file . write (CODE1);
            file . write (CODE2);
            file . write (CODE3);
            file . write (CODE4);

            /* write out state information */

            for (int i = 0; i < STATES; i ++)
            {
                if (states [i] == (State) null)
                    continue;

                /* write Beginning Of State delimiter and state id */

                file . write (BOS);
                file . write (i);

                /* make state write its own information, throw I/O exception
                   if the operation failed */

                if (! states [i] . saveFile (file))
                    throw new IOException ();
            }

            /* write out input string */

            if (currInputString != (InputString) null)
            {
                /* write Beginning Of State delimiter */

                file . write (BOI);

                /* make input string write its own information, throw I/O
                   exception if the operation failed */

                if (! currInputString . saveFile (file))
                    throw new IOException ();
            }

            /* write End Of Machine delimiter */

            file . write (EOM);
        }
        catch (IOException e)
        {
            return STATUS_WRITE_ERROR;
        }

        return STATUS_NORMAL;

    } /* end saveFile */


    /*
        PRIVATE METHODS
    */


    /*
        return state object at the specified location. if more that one state
        are located in the same space, return the one with the lowest id.
    */

    private State locateState (int x, int y)
    {
        for (int i = 0; i < STATES; i ++)
            if (states [i] != (State) null && states [i] . inside (x, y))

                return states [i];

        return (State) null;

    } /* end locateState */


    /*
        remove accepting and initial attributes from the state. update our
        counters as necessary.
    */

    private void makeNormal (State state)
    {
        if (state . isAccepting ())
            acceptingStates --;

        if (state . isInitial ())
            initialState = (State) null;

        state . makeNormal ();

    } /* end makeNormal */

} /* end Machine */


/*
    State class - implements all the logic for the state. It knows how to draw
    itself on the screen, how to read and write itself to/from a file. Also
    maintains all the transitions initiating from it.
*/


class State
{
    /*
        CLASS CONSTANTS
    */

    /*
        current state of the State
    */

    final static int    NORMAL      = 0;
    final static int    MOVING      = 1;
    final static int    ACTIVE      = 2;
    final static int    HIGHLIGHTED = 3;

    /* dimensions on the screen in pixels */

    final static int    SIZE = 50;

    /* delimiters used to distinguish records in files */

    final static int    BOT = 0x70;
    final static int    EOS = 0x71;


    /*
        INSTANCE VARIABLES
    */

    /* state origin coordinates on the screen (top left corner of the bounding
       rectangle). */

    private int         originX;
    private int         originY;

    /* origin positions used to redraw state during dragging and moving */

    private int         oldX;
    private int         oldY;
    private int         dragX;
    private int         dragY;

    /* colors corresponding to different states */

    private Color       colorNormal;
    private Color       colorSelected;
    private Color       colorActive;
    private Color       colorHighlight;

    /* a growing array of transitions */

    private Vector      transitions;

    /* initial, accepting, valid flags */

    private boolean     initial;
    private boolean     accepting;
    private boolean     valid;

    /* current transition curresponding to the token */

    private Transition  currTransition;

    /* current and old states */

    private int         state;
    private int         oldState;

    /* transition being removed */

    private Transition  removing;

    /* state id */

    private int         id;

    /* state id label */

    private String      label;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method for adding state from user click
    */

    public State (int i, int x, int y)
    {
        /* initialize instance variable */

        id    = i;
        label = new Integer (id) . toString ();

        oldX = originX = x;
        oldY = originY = y;

        colorNormal    = new Color (0, 100, 0);
        colorSelected  = new Color (100, 100, 100);
        colorActive    = new Color (100, 0, 0);
        colorHighlight = new Color (0, 0, 100);

        state = NORMAL;

        currTransition = removing = (Transition) null;

        initial = accepting = false;
        valid = true;

        transitions = new Vector (5, 1);

    } /* end State */


    /*
        constructor method for adding state from file
    */

    public State (int i, FileInputStream file)
    {
        /* read data catching I/O error exception */

        try
        {
            /* initialize instance variable */

            id    = i;
            label = new Integer (id) . toString ();

            colorNormal    = new Color (0, 100, 0);
            colorSelected  = new Color (100, 100, 100);
            colorActive    = new Color (100, 0, 0);
            colorHighlight = new Color (0, 0, 100);

            state = NORMAL;

            currTransition = removing = (Transition) null;

            initial = accepting = false;
            valid = true;

            transitions = new Vector (5, 1);

            /* read in origin coordinates and initial and accepting states */

            oldX = originX = file . read () | (file . read () << 8);
            oldY = originY = file . read () | (file . read () << 8);
            initial        = file . read () == 1;
            accepting      = file . read () == 1;

            int val = 0;

            /* while delimiter is not End Of State information */

            while (val != EOS)
            {
                /* read next delimiter */

                val = file . read ();

                /* if delimiter is Beginning Of Transition information */

                if (val == BOT)
                {
                    /* create a new transition, letting its constructor
                       initialize itself from the file */

                    Transition transition = new Transition (this, file);

                    transitions . addElement ((Object) transition);
                }

                /* any delimiter value other than EOS is error */

                else if (val != EOS)
                    return;
            }
        }
        catch (IOException e)
        {
        }

    } /* end State */





    /*
        called when user starts dragging the state. sample the coordinates
        and set state to MOVING.
    */

    public void dragStart (int x, int y)
    {
        dragX    = x;
        dragY    = y;
        oldState = state;
        state    = MOVING;

    } /* end dragStart */


    /*
        called when user stops dragging the state. update the position by the
        same amount that user moved the mouse. restore previous state.
    */

    public void dragStop (int x, int y)
    {
        move (originX + (x - dragX), originY + (y - dragY));
        dragX = x;
        dragY = y;
        state = oldState;

    } /* end dragStop */


    /*
        called while user stops drags the state. update the position by the
        same amount that user moved the mouse.
    */

    public void drag (int x, int y)
    {
        move (originX + (x - dragX), originY + (y - dragY));
        dragX = x;
        dragY = y;

    } /* end drag */


    /*
        draw itself on the specified graphics context.
    */

    public void paint (Graphics g)
    {
        /* remove previous image */

        remove (g);

        /* do not paint the image if state is not valid */

        if (! valid)
            return;

        /* set color depending on the current state */

        Color   color;

        switch (state)
        {
            case MOVING:
                color = colorSelected;
                break;

            case ACTIVE:
                color = colorActive;
                break;

            case HIGHLIGHTED:
                color = colorHighlight;
                break;

            case NORMAL:
            default:
                color = colorNormal;
                break;
        }

        /* set color and draw the circle */

        g . setColor (color);
        g . drawOval (originX, originY, SIZE, SIZE);


        /* draw bounding rectangle to denote initial state */

        if (initial)
            g . drawRect (originX, originY, SIZE - 1, SIZE - 1);

        /* draw smaller internal circle to denote accepting state */

        if (accepting)
            g . drawOval (originX + 5, originY + 5, SIZE - 10, SIZE - 10);

        /* draw state label */

        g . drawString (label, originX + SIZE / 2, originY + SIZE / 2);

        /* draw all the transitions that belong to us */

        Transition tmp;

        for (int i = 0; i < transitions . size (); i ++)
        {
            tmp = (Transition) (transitions . elementAt (i));

            /* depending on the state pick the color (could be different
                than the state color) */

            switch (state)
            {
                case MOVING:
                    color = colorSelected;
                    break;

                case ACTIVE:
                    /* only draw the transition having the token as active */

                    if (currTransition == tmp)
                    {
                        color = colorActive;
                        break;
                    }

                case HIGHLIGHTED:
                case NORMAL:
                default:
                    color = colorNormal;
                    break;
            }

            g . setColor (color);

            /* paint transition */

            tmp . paint (g);
        }

    } /* end paint */


    /*
        remove out drawing from the specified graphics context.
    */

    public void remove (Graphics g)
    {
        /* fill the area occupied by the state with background color */

        g . setColor (Color . lightGray);
        g . fillRect (oldX, oldY, SIZE, SIZE);

        /* remove transition drawings */

        for (int i = 0; i < transitions . size (); i ++)
        {
            Transition tmp = (Transition) (transitions . elementAt (i));

            /* clear the transition */

            tmp . remove (g);

            /* if this transition is being removed or the state on the other
               side of the transition is removed - delete it */

            if (tmp == removing || ! tmp . stateValid ())
            {
                transitions . removeElementAt (i);
                i --;
                removing = (Transition) null;
            }
        }

        /* update previous coordinates */

        oldX = originX;
        oldY = originY;

    } /* end remove */


    /*
        move state's origin to specified location.
    */

    public void move (int x, int y)
    {
        originX = x;
        originY = y;

    } /* end move */


    /*
        return the bounding rectangle of the state image.
    */

    public Rectangle bounds ()
    {
        return new Rectangle (originX, originY, SIZE, SIZE);

    } /* end bounds */


    /*
        check if the specified coordinate is withing the state image.
    */

    public boolean inside (int x, int y)
    {
        return (x >= originX && x <= originX + SIZE &&
                y >= originY && y <= originY + SIZE);

    } /* end inside */


    /*
        mark state as initial.
    */

    public void makeInitial ()
    {
        initial = true;

    } /* end makeInitial */


    /*
        mark state as accepting.
    */

    public void makeAccepting ()
    {
        accepting = true;

    } /* end makeAccepting */


    /*
        mark state as normal (clear the initial and accepting flags)
    */

    public void makeNormal ()
    {
        initial = accepting = false;

    } /* end makeNormal */


    /*
        mark state as invalid
    */

    public void makeInvalid ()
    {
        valid = false;

    } /* end makeInvalid */


    /*
        return the initial flag.
    */

    public boolean isInitial ()
    {
        return initial;

    } /* end isInitial */


    /*
        return the accepting flag.
    */

    public boolean isAccepting ()
    {
        return accepting;

    } /* end isAccepting */


    /*
        return the valid flag.
    */

    public boolean valid ()
    {
        return valid;

    } /* end valid */


    /*
        return state id.
    */

    public int stateId ()
    {
        return id;

    } /* end stateId */


    /*
        set the state to highlighted.
    */

    public void highlightOn ()
    {
        state = HIGHLIGHTED;

    } /* end highlighOn */


    /*
        clear the highlighted state (set the state to normal).
    */

    public void highlightOff ()
    {
        state = NORMAL;

    } /* end highlighOn */


    /*
        add new transition from this state to specified state.
    */

    public void addTransition (State last, boolean [] tokens)
    {
        /* see if we already have transition between these two states */

        for (int i = 0; i < transitions . size (); i ++)
        {
            Transition tmp = (Transition) (transitions . elementAt (i));

            /* if one exists - just upgrade its token set with the new ones.
               do not create a new transition. */

            if (tmp . destination () == last)
            {
                tmp . addTokens (tokens);
                return;
            }
        }

        /* create a new transition */

        Transition transition = new Transition (this, last, tokens);

        transitions . addElement ((Object) transition);

    } /* end addTransition */


    /*
        remove transition from this state to specified state.
    */

    public void removeTransition (State last)
    {
        /* find the transition with the specified destination state and mark
           it as being removed. it will be actually removed in the paint()
           call after its image had been removed from the screen. */

        for (int i = 0; i < transitions . size (); i ++)
        {
            Transition tmp = (Transition) (transitions . elementAt (i));

            if (tmp . destination () == last)
            {
                removing = tmp;
                return;
            }
        }

    } /* removeTransition */


    /*
        take the specified token and see if it can be passed along. if there is
        zero or more transitions corresponding to the token - it is an error.
        otherwise set our state to active and mark that transition as current.
        token is thought to be moving from this state along that transition.
    */

    public boolean takeToken (int t)
    {
        Transition tmp;

        /* find all transitions that take this token */

        for (int i = 0; i < transitions . size (); i ++)
        {
            tmp = (Transition) (transitions . elementAt (i));

            if (tmp . belongs (t))
            {
                /* if more than one transition match - error */

                if (currTransition != (Transition) null)
                {
                    currTransition = (Transition) null;
                    return false;
                }

                currTransition = tmp;
            }
        }

        /* if not transitions match - error */

        if (currTransition == (Transition) null)
            return false;

        /* mark state as active */

        state = ACTIVE;

        return true;

    } /* end takeToken */


    /*
        logically pass the token and return the state which it was passed to
        (destination state of the current transition).
    */

    public State giveToken ()
    {
        /* cannot pass token if we do not have one (not active) */

        if (state != ACTIVE)
            return (State) null;

        /* get the state at the end of the transition */

        State next = currTransition . destination ();

        /* clear the active state */

        currTransition = (Transition) null;
        state = NORMAL;

        return next;

    } /* end giveToken */


    /*
        write state data into a file.
    */

    public boolean saveFile (FileOutputStream file)
    {
        /* write data catching I/O error exception */

        try
        {
            /* write origin coordinates */

            file . write (originX & 0xff);
            file . write (originX >>> 8);
            file . write (originY & 0xff);
            file . write (originY >>> 8);

            /* write initial and accepting flag values */

            if (initial)
                file . write (1);
            else
                file . write (0);

            if (accepting)
                file . write (1);
            else
                file . write (0);

            /* make all of out transitions write their own data */

            for (int i = 0; i < transitions . size (); i ++)
            {
                /* write Beginning Of Transition delimiter */

                file . write (BOT);

                /* make transitions write its data */

                if (! ((Transition) (transitions . elementAt (i))) . saveFile (file))
                    return false;
            }

            /* write End Of State delimiter */

            file . write (EOS);
        }
        catch (IOException e)
        {
            return false;
        }

        return true;

    } /* end saveFile */


    /*
        make all transitions resolve state ids into state objects with the
        specified resolver object.
    */

    public void resolveId (StateIdResolver res)
    {
        Transition tmp;

        for (int i = 0; i < transitions . size (); i ++)
        {
            tmp = (Transition) (transitions . elementAt (i));

            tmp . resolveId (res);
        }

    } /* end resolveId */


} /* end State */


/*
    Transition class - implements the transition login. It knows how to draw
    itself on the screen, how to read and write itself to/from a file. It knows
    which tokens it is suppose to fire for and what state is at the end of the
    transition.
*/


class Transition
{
    /*
        INSTANCE VARIABLES
    */

    /* source and destination states of the transition */

    private State           srcState;
    private State           dstState;

    /* destination state id, when destination state object is not known */

    private int             dstId;

    /* contains flags specifying which tokens the transition fires for */

    private boolean         tokens [];

    /* transition label */

    private StringBuffer    label;

    /*
        coordinates for drawing transition image on the screen
    */

    /* start and end coordinates */

    private int             x1, x2, y1, y2;

    /* label coordinates */

    private int             xs, ys;

    /* arrow top, first and second lines coordinates */

    private int             xat, xa1, xa2, yat, ya1, ya2;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method for adding transition from user click
    */

    public Transition (State first, State last, boolean [] tok)
    {
        /* initialize instance variables */

        srcState = first;
        dstState = last;
        tokens   = tok;

        if (last != (State) null)
            dstId    = last . stateId ();

        x1 = x2 = y1 = y2 = xs = ys = xat = xa1 = xa2 = yat = ya1 = ya2 = 0;

        /* construct new label for this transition */

        makeLabel ();

    } /* end Transition */


    /*
        constructor method for adding transition from file
    */

    public Transition (State first, FileInputStream file)
    {
        /* read data catching I/O error exception */

        try
        {
            /* initialize instance variables */

            srcState = first;
            dstState = (State) null;

            x1 = x2 = y1 = y2 = xs = ys = xat = xa1 = xa2 = yat = ya1 = ya2 = 0;

            /* read destination state id */

            dstId = file . read ();

            /* create new token flags array and initialize it from file */

            tokens = new boolean [InputString . TOKENS + 1];

            for (int i = 0; i <= InputString . TOKENS; i ++)
                tokens [i] = file . read () == 1;

            /* construct new label for this transition */

            makeLabel ();
        }
        catch (IOException e)
        {
        }

    } /* end Transition */


    /*
        checks if the destination state is valid.
    */

    public boolean stateValid ()
    {
        return (dstState != (State) null && dstState . valid ());

    } /* end stateValid */


    /*
        draw this transition image on the specified graphics context.
        actually just calculate the coordinates and call draw() method to
        do the actual drawing.
    */

    public void paint (Graphics g)
    {
        /* get source state bounding rectangle */

        Rectangle start = srcState . bounds ();

        /* if the source and destination states are the same */

        if (dstState == srcState)
        {
            /* draw a 3/4 circle from the top to the right side of the state
               image */

            x1 = start . x + start . width / 2;
            y1 = start . y - start . height / 2;
            x2 = x1 + start . width;
            y2 = y1 + start . height;

            xs = x2 - 10;
            ys = y1 + 10;

            xat = x1 + (x2 - x1) / 2;
            yat = y2;
            xa1 = x1 + (x2 - x1) / 2 + 3;
            ya1 = y2 - 7;
            xa2 = x1 + (x2 - x1) / 2 + 7;
            ya2 = y2 + 3;

        }

        /* if destination and source states are different figure out the
           coordinates depending on their relative position */

        else
        {
            /* get destination state bounding rectangle */

            Rectangle end   = dstState . bounds ();

            /* figure out the distances between the two states */

            int dx = Math . abs (end . x - start . x);
            int dy = Math . abs (end . y - start . y);


            /* destination is between -45 and 45 degrees of arc, if source
               is considered the center of the coordinate system.

                  /
                S -> D
                  \
            */

            if (end . x >= start . x + start . width && dx >= dy)
            {
                x1 = start . x + start . width;
                y1 = start . y + start . height / 4 * 3;

                x2 = end . x;
                y2 = end . y + end . height / 4 * 3;

                xa1 = x2 - 5;
                ya1 = y2 - 5;

                xa2 = x2 - 5;
                ya2 = y2 + 5;

                xs = x1 + (x2 - x1) / 4;
                ys = y1 + (y2 - y1) / 4;
            }

            /* destination is between 45 and 135 degrees of arc, if source
               is considered the center of the coordinate system.

                  D
                  ^
                 \|/
                  S
            */

            else if (start . y >= end . y + end . height && dy >= dx)
            {
                x1 = start . x + start . width / 4 * 3;
                y1 = start . y;

                x2 = end . x + end . width / 4 * 3;
                y2 = end . y + end . height;

                xa1 = x2 + 5;
                ya1 = y2 + 5;

                xa2 = x2 - 5;
                ya2 = y2 + 5;

                xs = x1 + (x2 - x1) / 2;
                ys = y1 + (y2 - y1) / 2;
            }

            /* destination is between 135 and 225 degrees of arc, if source
               is considered the center of the coordinate system.

                  \
               D <- S
                  /
            */

            else if (start . x > end . x + end . width && dx > dy)
            {
                x1 = start . x;
                y1 = start . y + start . height / 4;

                x2 = end . x + end . width;
                y2 = end . y + end . height / 4;

                xa1 = x2 + 5;
                ya1 = y2 + 5;

                xa2 = x2 + 5;
                ya2 = y2 - 5;

                xs = x1 + (x2 - x1) / 4 * 3;
                ys = y1 + (y2 - y1) / 4 * 3;
            }

            /* destination is between 225 and 315 degrees of arc, if source
               is considered the center of the coordinate system.

                  S
                 /|\
                  v
                  D
            */

            else if (end . y > start . y + start . height && dy > dx)
            {
                x1 = start . x + start . width / 4;
                y1 = start . y + start . height;

                x2 = end . x + end . width / 4;
                y2 = end . y;

                xa1 = x2 - 5;
                ya1 = y2 - 5;

                xa2 = x2 + 5;
                ya2 = y2 - 5;

                xs = x1 + (x2 - x1) / 2;
                ys = y1 + (y2 - y1) / 2;

            }

            /* they must be overlapping - do not draw the transition */

            else
                x1 = x2 = y1 = y2 = xa1 = xa2 = ya1 = ya2 = 0;

            /* arrow head top is by the destination coordinate */

            xat = x2;
            yat = y2;

        }

        /* now draw the transition */

        draw (g);

    } /* end paint */


    /*
        draw the transition at its old coordinates, without recalculating.
        the color is preset in state's paint method.
    */

    public void remove (Graphics g)
    {
        draw (g);

    } /* end remove */


    /*
        draw the transition image with the current coordinates.
    */

    public void draw (Graphics g)
    {
        /* draw the main transition line */

        if (srcState == dstState)
            g . drawArc (x1, y1, x2 - x1, y2 - y1, -90, 270);
        else
            g . drawLine (x1, y1, x2, y2);

        /* draw the arrow head */

        g . drawLine (xat, yat, xa1, ya1);
        g . drawLine (xat, yat, xa2, ya2);

        /* draw the label */

        g . drawString (label . toString (), xs, ys);

    } /* draw */


    /*
        check if the specified token belongs to this transition. either the
        flag for the token or the all-encompassing flag will be set.
    */

    public boolean belongs (int t)
    {
        return (tokens [InputString . TOKENS] || tokens [t]);

    } /* end belongs */


    /*
        return the destination state for this transition.
    */

    public State destination ()
    {
        return dstState;

    } /* end destination */


    /*
        merge the old and new token flag arrays, thus updating the tokens for
        this transition.
    */

    public void addTokens (boolean [] tok)
    {
        /* merge */

        for (int i = 0; i <= InputString . TOKENS; i ++)
            tokens [i] = tokens [i] || tok [i];

        /* redo the label since the tokens have changed */

        makeLabel ();

    } /* end addTokens */


    /*
        write the transition data to file.
    */

    public boolean saveFile (FileOutputStream file)
    {
        /* write the data catching the I/O error exception */

        try
        {
            /* write destination state id */

            file . write (dstState . stateId ());

            /* write token flag array */

            for (int i = 0; i <= InputString . TOKENS; i ++)
                if (tokens [i])
                    file . write (1);
                else
                    file . write (0);
        }
        catch (IOException e)
        {
            return false;
        }

        return true;

    } /* end saveFile */


    /*
        resolve the destination state id into the actual object. this is done
        after reading transitions in from files. call the specified resolver
        object with the id.
    */

    public void resolveId (StateIdResolver res)
    {
        if (dstState == (State) null)
            dstState = res . resolveId (dstId);

    } /* end resolveId */


    /*
        PRIVATE METHODS
    */


    /*
        construct the label out of the token characters
    */

    private void makeLabel ()
    {
        /* if all-encompassing flag is set - just write 'All' */

        if (tokens [InputString . TOKENS])
            label = new StringBuffer (" All");

        /* get the letter representations of enabled tokens by adding the
           token value to TOKEN_SHIFT constant to obtain the ascii value */

        else
        {
            label = new StringBuffer (" ");

            for (int i = 0; i < InputString . TOKENS; i ++)
            {
                if (tokens [i])
                {
                    label . append ((char) (InputString . LABEL_SHIFT + i));
                    label . append (' ');
                }
            }
        }

    } /* end makeLabel */

} /* end Transition */

/*
    InputString class - implements a string of tokens used to feed into the state
    machine. Maintains position within the string, knows how to draw itself on
    the screen and read/write itself from/into file.
*/


class InputString
{
    /*
        CLASS CONSTANTS
    */

    /* number of different tokens recognized */

    final static int    TOKENS = 26;

    /* dictates how to make an ascii character out of the token. tokens are
       value from 0 to TOKENS - 1. adding LABLE_SHIFT will give you the
       ascii character representation */

    final static int    LABEL_SHIFT = 97;

    /* token standing for End Of String */

    final static int    EOS = -1;

    /* token standing for Bad Token */

    final static int    BAD_TOKEN = -2;


    /*
        INSTANCE VARIABLES
    */

    /* the string of character representations of tokens */

    private String      inputString;

    /* index within the input string of the current token */

    private int         index;

    /* color for drawing normal tokens */

    private Color       normalColor;

    /* color for drawing current token */

    private Color       currColor;

    /* 'Input string:' label drawn before token string */

    private String      title;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method for initialization from user input
    */

    public InputString (String str)
    {
        /* initialize instance variables */

        inputString = str;
        index = 0;

        normalColor = new Color (0, 0, 100);
        currColor = new Color (100, 0, 0);

        title = new String ("Input string: ");

    } /* end InputString */


    /*
        constructor method for initialization from file
    */

    public InputString (FileInputStream file)
    {
        /* read data catching I/O error exception */
        try
        {
            /* initialize instance variables */

            index = 0;

            normalColor = new Color (0, 0, 100);
            currColor = new Color (100, 0, 0);

            title = new String ("Input string: ");

            /* read number of tokens in the following string */

            int cnt = file . read () | (file . read () << 8);

            /* construct the array of bytes of that */

            byte [] bytes = new byte [cnt];

            /* fill array from file */

            file . read (bytes);

            /* create new string based on the data in the array of bytes */

            inputString = new String (bytes, 0);
        }
        catch (IOException e)
        {
        }

    } /* end InputString */


    /*
        returns current token or EOS on end of string or BAD_TOKEN if bad
        token encountered.
    */

    public int currentToken ()
    {
        if (index < 0 || index >= inputString . length ())
            return EOS;

        int curr = ((int) (inputString . charAt (index)) - LABEL_SHIFT);

        if (curr < 0 || curr >= TOKENS)
            return BAD_TOKEN;

        return curr;

    } /* end currentToken */


    /*
        advance current token index by 1.
    */

    public void advanceIndex ()
    {
        index ++;

    } /* end advanceIndex */


    /*
        set the current token index to the beginning of string
    */

    public void rewind ()
    {
        index = 0;

    } /* end rewind */


    /*
        draw itself on the specified graphics context offset pixels from the
        top. current token is drawn in different color.
    */

    public void paint (Graphics g, int offset)
    {
        /* remove previous string image */

        remove (g, offset);

        /* get font height and title string width */

        int height = g . getFontMetrics () . getHeight () + offset;
        int titleWidth = g. getFontMetrics () . stringWidth (title);

        /* set normal color and draw the title */

        g . setColor (normalColor);
        g . drawString (title, 0, height);

        /* if index is bad - draw the whole string in normal color */

        if (index < 0 || index >= inputString . length ())
        {
            g . drawString (inputString, titleWidth, height);
        }

        /* draw the string in three parts, so that the current token is drawn
           in different color */

        else
        {
            g . drawString (inputString . substring (0, index), titleWidth, height);

            g . setColor (currColor);
            g . drawString (inputString . substring (index, index + 1),
                            g. getFontMetrics () . stringWidth (
                            inputString . substring (0, index)) + titleWidth,
                            height);

            g . setColor (normalColor);
            g . drawString (inputString . substring (index + 1,
                            inputString . length ()),
                            g. getFontMetrics () . stringWidth (
                            inputString . substring (0, index + 1)) + titleWidth,
                            height);
        }

    } /* end paint */


    /*
        clear the rectangle of entire screen width and input string height on
        the specified context.
    */

    public void remove (Graphics g, int offset)
    {
        /* get font height and title string width */

        int height = g . getFontMetrics () . getHeight () + offset;
        int titleWidth = g. getFontMetrics () . stringWidth (title);

        /* fill the rectangle with background color */

        g . setColor (Color . lightGray);
        g . fillRect (0, 0, g . getFontMetrics () . stringWidth (inputString) +
                      titleWidth, height);

    } /* end remove */


    /*
        write the input string to file.
    */

    public boolean saveFile (FileOutputStream file)
    {
        /* write data catching the I/O error exception */

        try
        {
            /* write string length */

            file . write (inputString . length () & 0xff);
            file . write (inputString . length () >>> 8);

            /* allocate array of bytes to fit the string */

            byte [] bytes = new byte [inputString . length ()];

            /* extract tokens into the byte array */

            inputString . getBytes (0, inputString . length (), bytes, 0);

            /* write entire byte array to file */

            file . write (bytes);
        }
        catch (IOException e)
        {
            return false;
        }

        return true;

    } /* end saveFile */

} /* end InputString */


/*
    Help class - draws hints and messages at the bottom of the applet.
*/


class Help
{
    /*
        CLASS CONSTANTS
    */

    final static int    EMPTY                           = 0;
    final static int    INITIAL                         = 1;
    final static int    NO_STATE                        = 2;
    final static int    MAX_STATES                      = 3;
    final static int    INITIAL_STATE_PRESENT           = 4;
    final static int    NO_INPUT_STRING                 = 5;
    final static int    NO_INITIAL_STATE                = 6;
    final static int    NO_ACCEPTING_STATE              = 7;
    final static int    TARGET_STATE                    = 8;
    final static int    FIRST_STATE                     = 9;
    final static int    LAST_STATE                      = 10;
    final static int    SELECT_LOCATION                 = 11;
    final static int    STRING_ACCEPTED                 = 12;
    final static int    STRING_NOT_ACCEPTED             = 13;
    final static int    BAD_TOKEN                       = 14;
    final static int    BAD_MACHINE                     = 15;
    final static int    RUN_INSTRUCTIONS                = 16;
    final static int    FILE_NOT_FOUND                  = 17;
    final static int    FILE_CREATE_ERROR               = 18;
    final static int    FILE_WRITE_ERROR                = 19;
    final static int    FILE_READ_ERROR                 = 20;
    final static int    FILE_BAD_INPUT                  = 21;
    final static int    FILE_LOADED_OK                  = 22;
    final static int    FILE_SAVED_OK                   = 23;
    final static int    NO_TOKENS                       = 24;
    final static int    NO_TOKEN_STRING                 = 25;


    /*
        INSTANCE VARIABLES
    */


    /* current message string */

    private String      currHelp;


    /*
        PUBLIC METHODS
    */


    /*
        constructor method - set current message to empty
    */

    public Help ()
    {
        currHelp = new String ("");

    } /* end Help */


    /*
        sets current message string depending on the type of message wanted
    */

    public void setHelp (int num)
    {
        switch (num)
        {
            case INITIAL:
                currHelp = new String ("State Machine Simulator V1.0. Written by Kyril Faenov, Fall 1995.");
                break;

            case NO_STATE:
                currHelp = new String ("There is no state there");
                break;

            case MAX_STATES:
                currHelp = new String ("Maximum number of states is reached");
                break;

            case INITIAL_STATE_PRESENT:
                currHelp = new String ("No state there or already have one initial state");
                break;

            case NO_INPUT_STRING:
                currHelp = new String ("Cannot run simulation - no input string");
                break;

            case NO_INITIAL_STATE:
                currHelp = new String ("Cannot run simulation - no initial state");
                break;

            case NO_ACCEPTING_STATE:
                currHelp = new String ("Cannot run simulation - no accepting state(s)");
                break;

            case TARGET_STATE:
                currHelp = new String ("Click on the target state");
                break;

            case FIRST_STATE:
                currHelp = new String ("Click on the source state");
                break;

            case LAST_STATE:
                currHelp = new String ("Click on the destination state");
                break;

            case SELECT_LOCATION:
                currHelp = new String ("Click on desired location");
                break;

            case STRING_ACCEPTED:
                currHelp = new String ("Input string accepted by the machine");
                break;

            case STRING_NOT_ACCEPTED:
                currHelp = new String ("Input string NOT accepted by the machine");
                break;

            case BAD_TOKEN:
                currHelp = new String ("Input string contains bad token - halting");
                break;

            case BAD_MACHINE:
                currHelp = new String ("No or more than one transitions for this token - halting");
                break;

            case RUN_INSTRUCTIONS:
                currHelp = new String ("Click to step through tokens");
                break;

            case FILE_NOT_FOUND:
                currHelp = new String ("Specified file not found");
                break;

            case FILE_CREATE_ERROR:
                currHelp = new String ("Specified file could not be created");
                break;

            case FILE_WRITE_ERROR:
                currHelp = new String ("Error writing file");
                break;

            case FILE_READ_ERROR:
                currHelp = new String ("Error reading file");
                break;

            case FILE_BAD_INPUT:
                currHelp = new String ("Bad input file");
                break;

            case FILE_LOADED_OK:
                currHelp = new String ("File loaded successfully");
                break;

            case FILE_SAVED_OK:
                currHelp = new String ("File saved successfully");
                break;

            case NO_TOKENS:
                currHelp = new String ("No tokens was selected - transition not added");
                break;

            case NO_TOKEN_STRING:
                currHelp = new String ("Empty input string was specified - not setting");
                break;

            case EMPTY:
            default:
                currHelp = new String ("");
                break;
        }

    } /* end setHelp */


    /*
        draw the current message string at the very bottom of the screen.
    */

    public void paint (Graphics g, int height, int width)
    {
        /* remove previous message */

        remove (g, height, width);

        /* draw new one */

        g . setColor (Color . red);
        g . drawString (currHelp, 0, height - 1);

    } /* end paint */


    /*
        draw a filled rectangle with the width of the screen and the height
        of the font.
    */

    public void remove (Graphics g, int height, int width)
    {
        g . setColor (Color . lightGray);
        g . fillRect (0, height - g . getFontMetrics () . getHeight () - 1,
                      width, g . getFontMetrics () . getHeight () + 1);

    } /* end remove */

} /* end Help */
