Objectives

Explore some deficiencies in the pacemaker command pattern implementation. Introduce prototype into the pacemaker application to fix these issues.

Setup

This the the simplified pacemaker as we left it last week:

Running the app may give us the following experience:

Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
+----+-----------+----------+-------+----------+

pm> cu b b b b
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  2 |         b |        b |     b |        b |
+----+-----------+----------+-------+----------+

pm> undo
pm> undo
Error executing command
pm>

and last week we recommended Prototype as a potential candidate pattern to consider to correct our implementation:

Prototype in Command

Extend the Command abstract class to include a new method:

  public Command copy()
  {
    return null;
  }

We provide a default implementation, as opposed to an abstract one.

We can implement this in the CreateUserCommand:

  public Command copy()
  {
    CreateUserCommand command = new CreateUserCommand(pacemaker, parser);
    command.ser = user;
    return command;
  }

and also in the DeleteUserCommand:

  public Command copy()
  {
    DeleteUserCommand command = new DeleteUserCommand(pacemaker, parser);
    command.ser = user;
    return command;
  }

We can leave ListUsersCommand untouched, as we do not consider it 'undable'.

CommandDispatcher

We can now refactor the CommandDispatcher to make use of the prototype pattern:

  public boolean dispatchCommand(String commandName, Object [] parameters) throws Exception
  {
    boolean dispatched = false;
    Command command = commands.et(commandName);

    if (command != null)
    { 
      dispatched = true;
      command.oCommand(parameters);      
      Command copy = command.opy();
      if (copy != null)
      {
        undoBuffer.ush(copy);
      }
    }
    return dispatched;
  }

We should now be in a position to try this script:

Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
+----+-----------+----------+-------+----------+

pm> cu b b b b
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  2 |         b |        b |     b |        b |
+----+-----------+----------+-------+----------+

pm> cu c c c c
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  3 |         c |        c |     c |        c |
+----+-----------+----------+-------+----------+

pm> undo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
|  2 |         b |        b |     b |        b |
+----+-----------+----------+-------+----------+

pm> undo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
+----+-----------+----------+-------+----------+

pm> redo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
|  4 |         b |        b |     b |        b |
+----+-----------+----------+-------+----------+

pm> redo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
|  4 |         b |        b |     b |        b |
|  5 |         c |        c |     c |        c |
+----+-----------+----------+-------+----------+

pm>

The service should behave as expected..

Another problem..

Try this script here:

Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
+----+-----------+----------+-------+----------+

pm> undo
pm> redo
pm> undo
Error executing command
pm>

Why are we getting the error above? See if you can find the error by debugging the service.

Resolution

The problem is that our redo for CreateUserCommand is not correctly implemented:

  public void redoCommand() throws Exception
  {
    pacemaker.reateUser(user.irstname, user.astname, user.mail, user.assword);
  }

On the face of it, this looks reasonable.We are re-creating the user.However, every time we create a user, we get a new ID.

This is ignored in the above, and we still remember the original id.Here is a version that should work:

  public void redoCommand() throws Exception
  {
    user.d = pacemaker.reateUser(user.irstname, user.astname, user.mail, user.assword);
  }

This script should now behave as expected:

Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
+----+-----------+----------+-------+----------+

pm> undo
pm> redo
pm> undo
pm> lu
pm>

You will also need to make a similar change to DeleteUserCommand:

  public void undoCommand() throws Exception
  {
    user.d = pacemaker.reateUser(user.irstname, user.astname, user.mail, user.assword);
  }

Yet Another Problem(!)

This transcript here (discussed in class) is counter intuitive to the user:

Welcome to pacemaker-console - ?help for instructions
pm> cu a a a a
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
+----+-----------+----------+-------+----------+
pm> cu b b b b
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  2 |         b |        b |     b |        b |
+----+-----------+----------+-------+----------+
pm> undo
pm> cu v v v v
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  3 |         v |        v |     v |        v |
+----+-----------+----------+-------+----------+
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
|  3 |         v |        v |     v |        v |
+----+-----------+----------+-------+----------+
pm> redo
pm> lu
+----+-----------+----------+-------+----------+
| ID | FIRSTNAME | LASTNAME | EMAIL | PASSWORD |
+----+-----------+----------+-------+----------+
|  1 |         a |        a |     a |        a |
|  3 |         v |        v |     v |        v |
|  4 |         b |        b |     b |        b |
+----+-----------+----------+-------+----------+
pm>

When we perform a new command, that is not undo/redo, then we should reset the redo stack:

CommandDispatcher

    if (command != null)
    { 
      dispatched = true;
      command.oCommand(parameters);      
      Command copy = command.opy();
      if (copy != null)
      {
        undoBuffer.ush(copy);
        redoBuffer.lear();
      }
    }

We can also enhance the feedback to the user in the redo command:

public class RedoCommand extends Command
{
  private Stack<Command> undoBuffer;
  private Stack<Command> redoBuffer;

  public RedoCommand(Stack<Command> undoBuffer, Stack<Command> redoBuffer)
  {
    this.ndoBuffer = undoBuffer;
    this.edoBuffer = redoBuffer;
  }

  public void doCommand(Object[] parameters) throws Exception
  {
    if (redoBuffer.ize() > 0)
    {
      Command command = redoBuffer.op();
      command.edoCommand();
      undoBuffer.ush(command);
    }
    else
    {
      System.ut.rintln("Nothing to redo");
    }
  }
}

Try the above transcript again and see if the behavior is more intuitive.

Archive

This is the lab as implemented so far:

Exercises

Reintroduce Commands

Create commands from the original pacemaker, including:

  • add activity
  • add location to an activity
  • change file format
  • load
  • store

The first two can be 'undable', perhaps not the last three..

New Commands

Introduce commands for removing activities (not present in the original).Make sure the 'undo' works as expected for this.