// FiniteStateMachine.java import java.awt.*; import java.awt.event.*; import java.util.*; /** A graphical component representing a state diagram of a finite automaton, pushdown automaton, or Turing Machine. */ public abstract class FiniteStateMachine extends Component implements KeyListener, MouseListener, MouseMotionListener { // CONSTANTS /** graphical size of a state, in pixels */ public static final int STATE_SIZE = 40; /** graphical pixel offset used in certain places */ public static final int OFFSET = STATE_SIZE / 2; /** graphical size of the triangle marking the start state, in pixels */ public static final int TRI_SIZE = 8; /** graphical size of the canvas, in pixels */ public static final int CANVAS_SIZE = 1000; /** character representing an epsilon transition */ public static final char EPSILON = 1; /** character used to display the epsilon symbol (or close approximation) */ public static final char EPSILON_CHAR = 163; /** assumed height of transition text */ public static final int TEXT_HEIGHT = 15; // STATE VARIABLES (no pun intended) /** Vector of states in this finite state machine */ protected Vector states; /** Number of states in this finite state machine */ protected int numStates; /** start state for this finite state machine */ protected State start; /** transition function */ protected TransitionFunction function; /** word for the current computation */ protected String word; /** current position in the computation */ protected int step; /** the solution to the current computation, or null if it is not yet finished */ protected Boolean answer; /** current transition destination state */ private State trans; /** currently selected state */ private State sel; // STATIC GRAPHICS UTILITY METHODS /** computes the offset vector p3 (from p1) of length r that lies on the line from p1 to p2 */ private static Point getPointOnLine(Point p1, Point p2, int r) { Point p3 = new Point(); int t = p2.x - p1.x; if (t == 0) { p3.x = 0; p3.y = r; } else { double slope = (double) (p2.y - p1.y) / (p2.x - p1.x); r *= r; double div = slope * slope + 1; double d = r / div; p3.x = (int) Math.sqrt(d); p3.y = (int) Math.sqrt(r - d); } if (p1.x > p2.x) p3.x = -p3.x; if (p1.y > p2.y) p3.y = -p3.y; return p3; } /** computes the offset vector p3 (from p1) of length r such that (p2 - p1) is perpendicular to p3 */ private static Point getPerpendicular(Point p1, Point p2, int r) { Point p3 = new Point(); int x21 = p2.x - p1.x; int y21 = p2.y - p1.y; double denom = Math.sqrt(x21 * x21 + y21 * y21); if (denom == 0) { p3.x = 0; p3.y = 0; } else { p3.x = (int) -(r * y21 / denom); p3.y = (int) (r * x21 / denom); } return p3; } /** draws an arrow head at the tip of the line (p2 - p1) using the given Graphics context */ private static void drawArrow(Point p1, Point p2, Graphics g) { // compute arrow points Point p3 = getPointOnLine(p1, p2, 20); p3.x = p2.x - p3.x; p3.y = p2.y - p3.y; Point t = getPerpendicular(p3, p2, 5); Point t1 = new Point(); Point t2 = new Point(); t1.x = p3.x + t.x; t1.y = p3.y + t.y; t2.x = p3.x - t.x; t2.y = p3.y - t.y; // draw arrow head g.drawLine(p2.x, p2.y, t1.x, t1.y); g.drawLine(p2.x, p2.y, t2.x, t2.y); } /** draws the specified text at location (x, y) using the given Graphics context */ private static void drawText(String[] text, int x, int y, Graphics g) { // draw text for (int i=0; i w) w = wi; } int h = TEXT_HEIGHT * text.length; int p21x = p2.x - p1.x; int p21y = p2.y - p1.y; int halfway = (int) (Math.sqrt(p21x * p21x + p21y * p21y) / 2); Point p4 = getPointOnLine(p1, p2, halfway); p4.x = p2.x - p4.x; p4.y = p2.y - p4.y; Point tt = getPerpendicular(p4, p2, 5); tt.x += p4.x; tt.y += p4.y; if (p1.x < p2.x) tt.y += h; if (p1.y < p2.y) tt.x -= w; // draw text drawText(text, tt.x, tt.y, g); } /** draws the state diagram onscreen */ public void paint(Graphics g) { // draw the finite automaton to the canvas FontMetrics fm = getParent().getFontMetrics(g.getFont()); synchronized (states) { int len = states.size(); for (int i=0; i 0) { sel.setName(name.substring(0, len - 1)); repaint(); } } else { // add the typed character to the selected state's name if (key >= 32 && key < 256 && key != 127) { sel.setName(name + key); repaint(); } } } else { // user is typing in transition information; subclass handles this transitionKeyPressed(sel, trans, key); repaint(); } } /** event fired when a key is released */ public void keyReleased(KeyEvent e) { } /** event fired when a key is pressed then released */ public void keyTyped(KeyEvent e) { } /** event fired when mouse button is pressed then released */ public void mouseClicked(MouseEvent e) { } /** event fired when mouse enters a bounding area */ public void mouseEntered(MouseEvent e) { } /** event fired when mouse exits a bounding area */ public void mouseExited(MouseEvent e) { } /** event fired when mouse button is pressed */ public void mousePressed(MouseEvent e) { requestFocus(); Point p = e.getPoint(); int mod = e.getModifiers(); boolean shift = e.isShiftDown(); boolean left = ((mod & 0x10) != 0); boolean middle = ((mod & 0x0a) != 0); boolean right = ((mod & 0x04) != 0); trans = null; State state = getStateByPosition(p); if (shift) { if (left) { // shift + left press if (state == null) { // create a new state at the given location p.x -= OFFSET; p.y -= OFFSET; State s = new State("", false, p); addState(s); selectState(s); repaint(); } else { // toggle this state's accept flag state.setAccept(!state.isAccept()); repaint(); } } else if (right) { // shift + right press: delete this state if (state != start) { // don't allow deletion of the start state! removeState(state); repaint(); } } } else if (state != null) { if (left) { // left press: select this state selectState(state); repaint(); } else if (right) { // right press: add transitions from current state to this state if (sel != null) { trans = state; repaint(); } } } else { // mouse press occurred in cytoplasm; unselect all states selectState(null); repaint(); } } /** event fired when mouse button is released */ public void mouseReleased(MouseEvent e) { } /** event fired when mouse moves */ public void mouseMoved(MouseEvent e) { } /** event fired when mouse moves while button is pressed */ public void mouseDragged(MouseEvent e) { Point p = e.getPoint(); int mod = e.getModifiers(); boolean shift = e.isShiftDown(); boolean left = ((mod & 0x10) != 0); boolean middle = ((mod & 0x0a) != 0); boolean right = ((mod & 0x04) != 0); // mouse drag occurred in the finite automaton canvas if (shift) { if (left) { // shift + left drag } else if (right) { // shift + right drag } } else { if (left) { // left drag: move selected state if (sel != null) { p.x -= OFFSET; p.y -= OFFSET; sel.setPosition(p); repaint(); } } else if (right) { // right drag } } } // HELPER METHODS /** returns the first state that occupies the given position */ private State getStateByPosition(Point p) { synchronized (states) { int len = states.size(); for (int i=0; i