refcodes-command: Do the undo

refcodes-command: Do the undo

README

The REFCODES.ORG codes represent a group of artifacts consolidating parts of my work in the past years. Several topics are covered which I consider useful for you, programmers, developers and software engineers.

What is this repository for?

… Basically a Command can be seen as a method turned inside out …” [Having fun with the command pattern, 30/03/2015]

This artifact provides you means to implement your custom commands as of the command pattern:

… (in) the command pattern … an object is used to represent and encapsulate all the information needed to call a method at a later time. This … includes the … method parameters …” [Command pattern (Wikipedia)]

How do I get set up?

To get up and running, include the following dependency (without the three dots “…”) in your pom.xml:

1 <dependencies>
2   ...
3   <dependency>
4     <artifactId>refcodes-command</artifactId>
5     <groupId>org.refcodes</groupId>
6     <version>1.1.7</version>
7   </dependency>
8   ...
9 </dependencies>

The artifact is hosted directly at Maven Central. Jump straight to the source codes at Bitbucket. Read the artifact’s javadoc at javadoc.io.

How do I get started?

A Command can be seen as a method and its context (variables) all transformed to an object (e.g a method turned inside out).

Given you define a Command’s interface with an execute and an undo method and you strictly make use of commands: Then you easily can provide undo functionality by putting your executed Commands onto a stack

A Command respectively an Undoable represents an (atomic) operation applied to a context encapsulated in an object (as of object oriented programming). Usually a Command (Undoable) also provides means to undo its operation applied before. The Command (Undoable) is created by a client (e.g. the business logic) and passed e.g. to a command-bus for execution or executed “manually” at required / desired time.

You find a command-bus implementation with the refcodes-jobbus artifact (see type JobBus).

public RET execute( CTX aContext ) throws E;

Above see the execute method as of a (generic) Command defined by the refcodes-command artifact. See below in case the Command provides undo functionality (called Undoable by the refcodes-command artifact):

void undo( CTX aContext ) throws E;

A client is the business logic creating an Undoable (job) to be executed. The context (e.g. provided by the client to the Command) can be a service, a service-bus (providing a handful of services), a component or a plain POJO (this depends on your requirements and your implementation).

An example

For the complete source codes of the below example see the tests in the refcodes-command artifact on bitbucket.

To show you some basic example code, given you have a list of integers and want to apply operations such as add, subtract or multiply on that list. You can apply as many of those operations as you like on that list of integers. The add operation would add a given value to each of the integer elements contained in that list, subtract would subtract a given value from each of the integer elements contained in that list, and multiply would multiply each integer element in that list with a given value:

As an example we are building a calculator applying its arithmetic operations on a list of values. A user of that “list” calculator is to be able to undo each operation applied on such a list in reverse order of its execution: After undoing the last operation, the list should be in the same state as it was before the last operation was executed. Invoking the undo operation again should undo the second last operation in the same manner until undoing the first operation should leave the list as it initially was before applying the very first operation.:

Given we have a list as such:

{1, 2, 3}

Applying add(1) (pseudo-code) on that list would leave the list as follows (1 is added to each element of the list):

{2, 3, 4}

Applying mul(3) (pseudo-code) on that list would leave the list as follows (each element of the list is multiplied by 3):

{6, 9, 12}

Undoing the above operations in reverse order should return the list to its initial state (divide each element of the list by 3 and subtract 1 from each element of the list):

{1, 2, 3}

The multiply command

Let’s start playing by setting up our multiply command.

1 public class MulCommandImpl implements Undoable<List<Integer>, List<Integer>, Exception> {
2 
3   private int _multiplier;
4 
5   public MulCommandImpl( int aMultiplier ) {
6     _multiplier = aMultiplier;
7   }
8 ...

The execute method of the MulCommandImpl applies its processing on the context:

 1 ...
 2   @Override
 3   public List<Integer> execute( List<Integer> aContext ) {
 4     int eElement;
 5     for ( int i = 0; i < aContext.size(); i++ ) {
 6       eElement = aContext.remove( i ) * _multiplier;
 7       aContext.add( i, eElement );
 8     }
 9     return aContext;
10   }
11 ...

The method isUndoable is used to indicate whether an Undoable can actually be undone. In our case isUndoable always returns false though it could test whether each element in the list is divisible by the multiplier without any remainder…

 1 ...
 2   @Override
 3   public boolean isUndoable() {
 4     return true;
 5   }
 6 
 7   @Override
 8   public void undo( List<Integer> aContext ) {
 9     int eElement;
10     for ( int i = 0; i < aContext.size(); i++ ) {
11       eElement = aContext.remove( i ) / _multiplier;
12       aContext.add( i, eElement );
13     }
14   }
15 }

As you can see, the MulCommandImpl class multiplies in its execute method each element in the passed list by a given value and reverts this action in its undo method by dividing each element in the passed list by the same given value.

As this is just an example, I omitted to code more generic arithmetic commands and I omitted to do some exceptional cases handling.

(the AddCommandImpl (add) and the SubCommandImpl (subtract) Undoable implementations work similar)

The undo-stack

Now let us take a look at the invocation of the commands and the undo-stack:

1 public class UndoableTest {
2   private List<Integer> createReferenceList() {
3     List<Integer> theList = new ArrayList<>();
4     for ( int i = 1; i < 100; i++ ) {
5       theList.add( i * 1000 );
6     }
7     return theList;
8   }
9 ...

First of all we provide some means to create a identical lists, one of which we will use as context and one of which we will use as reference list to test whether all undo operations invoked leave the context in its initial state.

1 ...
2   private void execute( UndoableCommand aCommand, List<Integer> aCtx, Deque<UndoableCommand> aStack ) throws Exception {
3     aCommand.execute( aCtx );
4     aStack.addFirst( aCommand );
5   }
6 ...

The above execute method applies a given Undoable on the provided context and places the executed Undoable command on a stack. Below we apply some operations on the context:

 1 ...
 2   @Test
 3   public void testUnduable() throws Exception {
 4     // Reference list to compare the result:
 5     List<Integer> theRef = createReferenceList();
 6     // The undo Stack to use: 
 7     Deque<UndoableCommand> theStack = new ArrayDeque<>();
 8     // The actual context to work on: 
 9     List<Integer> theCtx = createReferenceList();
10   
11     // The commands to be applied to the context:
12     UndoableCommand incBy2 = new AddCommandImpl( 2 );
13     UndoableCommand incBy4 = new AddCommandImpl( 4 );
14     UndoableCommand incBy8 = new AddCommandImpl( 8 );
15     UndoableCommand decBy1 = new SubCommandImpl( 1 );
16     UndoableCommand decBy2 = new SubCommandImpl( 2 );
17     UndoableCommand decBy3 = new SubCommandImpl( 3 );
18     UndoableCommand mulBy2 = new MulCommandImpl( 2 );
19     UndoableCommand mulBy5 = new MulCommandImpl( 5 );
20   
21     // Apply the commands:
22     execute( incBy2, theCtx, theStack );
23     execute( incBy4, theCtx, theStack );
24     execute( incBy8, theCtx, theStack );
25     execute( mulBy2, theCtx, theStack );
26     execute( decBy1, theCtx, theStack );
27     execute( decBy2, theCtx, theStack );
28     execute( decBy3, theCtx, theStack );
29     execute( mulBy5, theCtx, theStack );
30     assertNotEquals( theCtx, theRef );
31 ...

Finally we apply the undo methods of the executed Undoable commands in reverse order on the context: In the last section I revert all action taken on the context by calling the undo methods of the executed Undoable commands in reverse order and test whether the context is back to its initial state:

 1 ...
 2     // Undo all operations in reverse order:
 3     Iterator<UndoableCommand> e = theStack.iterator();
 4     while( e.hasNext() ) {
 5       e.next().undo( theCtx );
 6       e.remove();
 7       if ( e.hasNext() ) {
 8         assertNotEquals( theCtx, theRef );
 9       }
10     }
11     assertEquals( theCtx, theRef );
12   }
13 }

A context could be a an image (a bitmap or a two dimensional array of Color objects) and an Undoable command could apply some graphical operations on that context. Or the context could be a user’s profile which is modified by the according Undoable commands upon user interaction and whoe’s actions can be reverted …

For the complete source codes of the above example see the tests in the refcodes-command artifact on bitbucket.

Contribution guidelines

  • Writing tests
  • Code review
  • Other guidelines

Who do I talk to?

  • Siegfried Steiner (steiner@refcodes.org)

Terms and conditions

The REFCODES.ORG group of artifacts is published under some open source licenses; covered by the refcodes-licensing (org.refcodes group) artifact - evident in each artifact in question as of the pom.xml dependency included in such artifact.

comments powered by Disqus