JMU
The State Pattern
An Introduction with Examples in Java


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Motivation
An Example

A Statechart Diagram for a Simplified Garage Door Opener

images/garage-door-opener_states.gif
An Example (cont.)

A "Messy" Implementation

javaexamples/statepattern/alternative/Controller.java
        import java.io.*;


/**
 * The Controller for a garage door opener
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 0.1
 */
public class Controller
{
    private BufferedReader          in;    
    private PrintWriter             out;    
    private int                     state;

    private static final int        AWAITING_COMBINATION = 0;
    private static final int        CLOSED               = 1;
    private static final int        LOCKED               = 2;
    private static final int        OPENED               = 3;
    


    
    /**
     * Explicit Value Constructor
     *
     * @param is  The InputStream used for input and output
     * @param os  The OutputStream used for input and output
     */
    public Controller(InputStream is, OutputStream os) throws IOException
    {
       in  = new BufferedReader(new InputStreamReader(is));
       out = new PrintWriter(os);

       setState(CLOSED);
    }


    

    /**
     * Run this controller (i.e., prompt the user to enter
     * a command and respond accordingly)
     */
    public void run() throws IOException
    {
       String           line;
       
       out.print("Command (close,combination,error,lock,open,unlock): ");  
       out.flush();
       
       while ((line=in.readLine()) != null)
       {
          if (line.equals("close"))
          {
             if (state == OPENED) setState(CLOSED);             
          }
          if (line.equals("combination"))
          {
             if (state == AWAITING_COMBINATION) setState(CLOSED);             
          }
          if (line.equals("error"))
          {
             if (state == AWAITING_COMBINATION) setState(LOCKED);             
          }
          if (line.equals("lock"))
          {
             if (state == CLOSED) setState(LOCKED);             
          }
          if (line.equals("open"))       
          {
             if (state == CLOSED) setState(OPENED);             
          }
          if (line.equals("unlock"))     
          {
             if (state == LOCKED) setState(AWAITING_COMBINATION);             
          }
          
          out.print("Command (close,combination,error,lock,open,unlock): "); 
          out.flush();
       }
    }



    /**
     * Set the state
     *
     * @param state    The new state
     */
    private void setState(int state)
    {
       this.state = state;
       out.println("  State set to: "+state);
       out.flush();       
    }
    
}

        
An Important Example
The State Pattern
The State Pattern (cont.)

In UML:

images/state-pattern.gif
The Example Revisited

Using the State Pattern

images/garage-door-opener_classes.gif
The Example Revisited (cont.)

The Abstract State

javaexamples/statepattern/State.java
        /**
 * An abstract State that the Controller can be in
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public abstract class State
{
    /**
     * Process a close transition
     */
    public void close()
    {
    }


    /**
     * Process a combinationEntered transition
     */
    public void combinationEntered()
    {
    }
    

    /**
     * Enter this State
     *
     * Specifically, construct an instance if necessary and perform any
     * necessary operations
     *
     * @param controller   The Controller to perform operations on
     * @return             The (singleton) instance of this State
     */
    public static State enter(Controller controller)
    {
       return null;
    }
    


    /**
     * Process an errorEntered transition
     */
    public void errorEntered()
    {
    }
    

    /**
     * Process a lock transition
     */
    public void lock()
    {
    }


    /**
     * Process a open transition
     */
    public void open()
    {
    }


    
    /**
     * Process a startUnlock transition
     */
    public void startUnlock()
    {
    }
    


}
        
The Example Revisited (cont.)

The States

javaexamples/statepattern/AwaitingCombination.java
        /**
 * The AwaitingCombination State (for a garage door Controller)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class AwaitingCombination extends State
{
    private        Controller           controller;
    private static AwaitingCombination  instance;
    

    /**
     * Explicit Value Constuctor 
     * [used by the enter() method]
     *
     * @param controller   The Controller to use
     */
    private AwaitingCombination(Controller controller)
    {
       this.controller = controller;
    }
    

    
    /**
     * Process a combinationEntered transition
     */
    public void combinationEntered()
    {
       controller.changeState(Closed.enter(controller));
    }
    


    /**
     * Enter this State
     *
     * Specifically, construct an instance if necessary and perform any
     * necessary operations
     *
     * @param controller   The Controller to perform operations on
     * @return             The (singleton) instance of this State
     */
    public static State enter(Controller controller)
    {
       if (instance == null) instance = new AwaitingCombination(controller);

       controller.reset();       
       return instance;
    }


    /**
     * Process an errorEntered transition
     */
    public void errorEntered()
    {
       controller.changeState(Locked.enter(controller));
    }
    
    
}
        
The Example Revisited (cont.)

The States (cont.)

javaexamples/statepattern/Closed.java
        /**
 * The Closed State (for a garage door Controller)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Closed extends State
{
    private        Controller           controller;
    private static Closed               instance;
    

    /**
     * Explicit Value Constuctor 
     * [used by the enter() method]
     *
     * @param controller   The Controller to use
     */
    private Closed(Controller controller)
    {
       this.controller = controller;
    }
    

    /**
     * Enter this State
     *
     * Specifically, construct an instance if necessary and perform any
     * necessary operations
     *
     * @param controller   The Controller to perform operations on
     * @return             The (singleton) instance of this State
     */
    public static State enter(Controller controller)
    {
       if (instance == null) instance = new Closed(controller);

       return instance;
    }

    
    /**
     * Process a lock transition
     */
    public void lock()
    {
       controller.changeState(Locked.enter(controller));
    }
    


    
    /**
     * Process an open transition
     */
    public void open()
    {
       controller.changeState(Opened.enter(controller));
    }
    





}
        
The Example Revisited (cont.)

The States (cont.)

javaexamples/statepattern/Locked.java
        /**
 * The Locked State (for a garage door Controller)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Locked extends State
{
    private        Controller           controller;
    private static Locked               instance;
    

    /**
     * Explicit Value Constuctor 
     * [used by the enter() method]
     *
     * @param controller   The Controller to use
     */
    private Locked(Controller controller)
    {
       this.controller = controller;
    }
    

    /**
     * Enter this State
     *
     * Specifically, construct an instance if necessary and perform any
     * necessary operations
     *
     * @param controller   The Controller to perform operations on
     * @return             The (singleton) instance of this State
     */
    public static State enter(Controller controller)
    {
       if (instance == null) instance = new Locked(controller);

       return instance;
    }

    
    /**
     * Process a startUnlock transition
     */
    public void startUnlock()
    {
       controller.changeState(AwaitingCombination.enter(controller));
    }
    


}
        
The Example Revisited (cont.)

The States (cont.)

javaexamples/statepattern/Opened.java
        /**
 * The Opened State (for a garage door Controller)
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Opened extends State
{
    private        Controller           controller;
    private static Opened               instance;
    

    /**
     * Explicit Value Constuctor 
     * [used by the enter() method]
     *
     * @param controller   The Controller to use
     */
    private Opened(Controller controller)
    {
       this.controller = controller;
    }
    

    
    /**
     * Process a close transition
     */
    public void close()
    {
       controller.changeState(Closed.enter(controller));
    }
    


    /**
     * Enter this State
     *
     * Specifically, construct an instance if necessary and perform any
     * necessary operations
     *
     * @param controller   The Controller to perform operations on
     * @return             The (singleton) instance of this State
     */
    public static State enter(Controller controller)
    {
       if (instance == null) instance = new Opened(controller);

       return instance;
    }
}
        
The Example Revisited (cont.)

The Context

javaexamples/statepattern/Controller.java
        import java.awt.*;
import java.awt.event.*;
import javax.swing.*;


/**
 * The Controller for a garage door opener
 *
 * @author  Prof. David Bernstein, James Madison University
 * @version 1.0
 */
public class Controller extends JFrame implements ActionListener
{
    private JLabel                  display;    
    private State                   state;
    private String                  entry;
    
    /**
     * Default Constructot
     */
    public Controller()
    {
       super();       
       reset();
       state = Closed.enter(this);
       performLayout();
    }

    /**
     * Handle actionPerformed messages (required by ActionListener)
     *
     * @param evt  The ActionEvent that generated the message
     */
    public void actionPerformed(ActionEvent evt)
    {
       String       ac;
       
       ac = evt.getActionCommand();
       if      (ac.equals("Close"))  state.close();
       else if (ac.equals("Lock"))   state.lock();
       else if (ac.equals("Open"))   state.open();
       else if (ac.equals("Unlock")) state.startUnlock();
       else
       {
          entry += ac;
          System.out.println(entry);
          
          if (entry.length() == 4)
          {
             if (entry.equals("1337")) state.combinationEntered();
             else                      state.errorEntered();

             reset();             
          }
       }
    }

    /**
     * Change the State of the system
     *
     * @param state   The new State
     */
    public void changeState(State state)
    {
       this.state = state;
       setMessage(state.toString());
    }

    /**
     * Reset the controller
     */
    public void reset()
    {
       entry = "";
    }

    /**
     * Layout this Controller
     */
    private void performLayout()
    {
       JButton      close, open, lock, unlock;       
       JButton[]    number;
       JPanel       center, contentPane;
       

       number = new JButton[10];       
       
       contentPane = (JPanel)getContentPane();       
       contentPane.setLayout(new BorderLayout());       

       display = new JLabel("xDoor 1337", SwingConstants.CENTER);
       contentPane.add(display, BorderLayout.NORTH);

       center = new JPanel();
       center.setLayout(new GridLayout(5,3));
       contentPane.add(center, BorderLayout.CENTER);
       for (int i=1; i<=9; i++)
       {
          number[i] = new JButton(""+i);
          center.add(number[i]);          
       }
       open = new JButton("Open");
       center.add(open);
       number[0] = new JButton("0");
       center.add(number[0]);
       close = new JButton("Close");
       center.add(close);

       unlock = new JButton("Unlock");
       center.add(unlock);
       center.add(new JLabel(" "));
       lock = new JButton("Lock");
       center.add(lock);

       for (int i=0; i<=9; i++) number[i].addActionListener(this);
       close.addActionListener(this);
       lock.addActionListener(this);
       open.addActionListener(this);
       unlock.addActionListener(this);
    }

    /**
     * Set the message on the display
     *
     * @param message   The message
     */
    public void setMessage(String message)
    {
       display.setText(message);       
    }
    
}