Table of contents

  1. Overview
  2. Introduction
  3. A short example
  4. State machine
  5. States
  6. Transitions and validations

Overview

The jStates library helps developers to design and easily implement and use simple state machines. The machines created using this library are intended for general use, not restricted to a certain framework or Java platform (J2SE, J2EE).

This library version requires J2SE 1.4.2 or above.

 This documentation shortly describes the jStates library and API. See the jstates-utils and jstates-examples sourceforge packages documentation and javadoc for more information about Jakarta Commons Validator integration and library usage examples.

Introduction

State machines are used in many scientific and technological fields: mathematics, computer science, engineering and many more.

There are several types of state machines, each one suited for different uses. This library does *not* offer a complete implementation of any of them, but a simplified and generic approach to some of them. This simplified approach makes learning jStates easy. Using and integrating jStates machines is easier than using other more specific state machines. You can use jStates to easily define application flow, GUI (desktop and HTML) complex form navigation, form data validation, serialize state machines, generate localized error messages, use your own validators or scripting languages to check state values and more.

A jStates state machine can be divided into the following parts:

  • The machine itself, has a name that identifies it and a XML file that defines it. Its main functions are to act as a container of states and to act as an interface between the developer and these states. A machine is always in a well defined state ("current state"). A machine can change its current state by "forwarding" to another state through a "transition".
  • A state is a set of properties and transitions. Properties store the current value of a state, while transitions show what other states can be reached from a state.
  • A transition is a little more complex concept. Think of it as a condition check to change the current state of a machine. A transition contains a set of validator rules that state properties must conform, for a machine to be able to reach another state from the current one. When we ask a machine to follow a transition ("forward"), the machine tests all of the validator rules of the transition and, if all of the tests are correct, the machine changes its current state to the one pointed by the transition. Transitions of a state can only be followed if that state is the current one.
  • A validator rule is a set of conditions that a state machine must verify before a transition can be followed. A validator rule can be written as a custom class ("validator") or a script ("script"). More on this in the following sections.

To create and use a jStates state machine, we should determine the possible states of a machine, determine the transitions from each state to the other states and determine what validator rules must conform a state to allow transitions to another states. The following sections describe these steps in more detail.

A short example

This section shows an example of how to use jStates. The presented example is just a couple of commented code blocks, for a more detailed explanation of this code, please refer to the next sections.

Machine definition files are XML files where a developer can define a state machine and all of its elements: states, transitions, validators and so on. Currently, the DTD for XML validation of machine definition files is statemachine_0_9.dtd. Take a look at this DTD file for detailed documentation about XML tags and attributes. This is a simple but complete example of a XML definition file, mytestmachine.xml: *** TODO I DON'T LIKE THIS CODE

<!DOCTYPEstatemachine PUBLIC "-//jStates State Machines//DTD Machine Definition//EN"
       "http://www.jstates.com/dtds/statemachine_0_9.dtd">
       
 <!-- State machine -->      
<statemachine name="mytestmachine" initialStateName="myfirststate" info="a test machine">

 	<!-- State -->
	<state name="myfirststate" info="some info about this state">
		
		<!-- Property: always string type -->
		<property name="fooproperty" value="some value" />

		<!-- Transition: change state to "mysecondstate" -->
		<transition name="trans1" to="mysecondstate" info="transition to second state">
				
			<!-- Validation: contains this transition's validators -->
			<validation>

				<!-- Custom validator: Jakarta Commons Validator binding, 
					see jstates-utils sourceforge package -->			
				<validator type="net.sf.jstates.commons.validator.CommonsValidator" 
					parameters="messages, validator-rules.xml, validation.xml"/>
						
				<!-- Script validator -->
				<script name="ascript" info="a named script" language="javascript">
					<![CDATA[
					
				   /* 
					* Entry point for validation, "state" is a reference to 
					* this validator's parent state. Must return a boolean value. 
					*/
						
					function validate(state) {

						if(state.get('fooproperty') != 'bar') {
							// Implicit TransitionErrors object.
							errors.addError('fooproperty', 
								'fooproperty should be "bar"');
							return false;
						}
					
						return true;
					}
					
					]]>
				</script>
					
			</validation>
		</transition>

		<!-- Empty transition: change state to "athirdstate" with no validation -->
		<transition name="trans2" to="athirdstate" info="an empty transition" />

	</state>
		
	<!-- A final state: once reached, a machine cannot go forward to other states, 
			just return to previous states -->
	<state name="mysecondstate" info="this state has no transitions, it's a final state">
	
		<!-- Property: must exist at least one, always has a string value-->	
		<property name="someotherproperty" value="25.44" />
	</state>
	
	<!-- Another final state -->
	<state name="athirdstate" info="another final state">
	
		<!-- A property value can be empty -->
		<property name="someotherproperty" value="" />
	</state>
	
</statemachine>
				

After machine definition comes machine use. This is a Java source code snippet, demonstrating how to create and use the previously defined machine:

// Load machine definition. Currently, XML machine definition file must be in the classpath.
StateMachineFactory.addMachineDefinition("machines/mytestmachine.xml");

// Create a machine based on a loaded machine definition. Current state is "myfirststate"
StateMachine sm = StateMachineFactory.createMachine("mytestmachine");

// Try to reach the state pointed by transition "trans1"
if(!sm.forward("trans1")) {
	// Validation failed, show validation errors.
	TransitionErrors errors = sm.getTransitionErrors();
	[... iterate over error messages ...]
	
	// Change current state property values and try forward again
	State st = sm.getCurrentState();
	// At least, the script validator should succeed.
	st.set("fooproperty", "bar");
	
	if(!sm.forward("trans1")) {
		System.out.println("failed again!");
		System.exit(0);
	}
} else {
	// Validation OK, show current state and property values
	State st = sm.getCurrentState();
	System.out.println( st.get("someotherproperty") );
	[... print each property ...]
}


				

See jstates-examples package in sourceforge.net package downloads for more Java source code and XML machine definition examples.

State machine

As previously said in the "Introduction" section, a state machine is both a states container and an interface to interact with these states. The first step to use a machine is to define it. The second should be to use Java code to instantiate it and use it.

The XML tag to define a machine is the <statemachine> tag. This tag has three attributes:

  • name: contains the unique name of the machine. It's used for machine instance creation, more on this later in this section.
  • initialStateName: contains the name of the initial state, the first "current state" of the machine just created.
  • info: contains extra information about the machine, think of this as a place to include metainformation - a descriptive test of machine usage, a URL, version information, ...

Once defined, we should load the machine definition into the StateMachineFactory, using the method addMachineDefinition. Once loaded, we can create instances by calling the createMachine method, with the machine name as argument. Any machine instance is independent of the other instances, so we can create any number of machines of the same type:

	// Add a new machine prototype to the factory
	StateMachineFactory.addMachineDefinition("machinedefinition.xml");
	
	// Instantiate the same machine several times
	StateMachine machine1 = StateMachineFactory.createMachine("mymachine");
	StateMachine machine2 = StateMachineFactory.createMachine("mymachine");
	StateMachine machine3 = StateMachineFactory.createMachine("mymachine");
	[...]
				

These are the most common operations that we will perform on a machine:

  • Examinate the current state: call the getCurrentState method. It will return a State object. More on State objects in the next section.
  • Change the current state to another: call the forward overloaded method, see the StateMachine javadoc for more information about these methods. These methods return a boolean value, indicating whether the machine changed its current state ("true") or not ("false"). If the state change failed, there could be stored error messages about the validation that aborted this change. Call the getTransitionErrors method to obtain these errors. More on this in the "Transitions" section.
  • Change the internal value of the current state: we can do this in two ways. First way is to get the current state by calling getCurrentState and then calling the set method on this state (more on this in the next section). Second way is by calling the updateCurrentState method, and passing a JavaBean object that has properties with the same name as the state properties. The properties with the same name will be copied to the current state and the rest will be ignored. This last method is useful if you want to populate the current state with form field values contained in a bean, for example.

A StateMachine object can perform other interesting operations, including going back to previous states, serialization and more. See the javadoc documentation for more about this.

States

The State objects represent the status of a StateMachine object and the possible states a machine can reach from its current state. To accomplish these goals, a State contains two types of objects:

  • Properties: they have the same functionality as JavaBean properties or classic variables, storing the internal value of a state. In a machine definition XML, a property can have a default value or no value (empty String), asigned by the value attribute in the <property> tag. However, this value can be changed by calling the set method of a State object or by calling the updateXXXState methods of StateMachine. In the same way, property values can be read by using the get methods getXXX of a State object. Internally, properties are stored as String objects.

    A machine can be in a well defined state ("current state") and its status will not change if the internal properties of the current state do change their values. Properties are used to decide if its possible to change to another state from the current one, so they are related to transitions, not to the machine status. See the "form validation" (TODO: IMPLEMENT) example in the jstates-examples package to see a practical use of this feature.
  • Transitions: there is a transition for each state that can be reached from the state this transition is contained into. When a machine tries to change its current state by calling "forward" methods, it must call a transition of the current state to do so. See the next section, "Transitions", for a more complete explanation about this process.

    There can be any number of transitions contained into a State, even none of them. If a state has no transitions, it is called a "final" state.

A XML example:

	<state name="firststate" info="this is the first state">
	
		<property name="foo1" value="some string" />
		<property name="foo2" value="25" />	
		<property name="foo3" value="true" />
		
		<transition name="trans" to="secondstate" info="transition to second state" />		
		
	</state>
	
	<state name="secondstate" info="a second state">
		<property name="bar1" value="property bar in the second state" />
	</state>
				

And this is a Java code snippet, demonstrating state and property handling:

	// State machine is in the first state
	State firstState = machine.getCurrentState();
	
	// Print current state properties
	System.out.println("foo1=" + firstState.getStr("foo1");
	System.out.println("foo2=" + firstState.getInt("foo2");
	System.out.println("foo3=" + firstState.getBool("foo3");
	
	// Change some of the property values: use Strings
	firstState.set("foo1", "changed value");
	firstState.set("foo2", "0");
	firstState.set("foo3", "false");
	
	// Use a transition to change the current state from "firststate" to "secondstate"
	machine.forward("trans");
	
	// Get the current state
	State secondState = machine.getCurrentState();
	
	// Print current state property
	System.out.println("bar1=" + secondState.getStr("bar1"));
				

Transitions

As previously mentioned, a state machine can be only in a well-known state, that we called the "current state". When first created, the machine sets its current state to the state referenced by the initialStateName attribute of the <statemachine> tag. From there, a machine is supposed to change its current state to another state when some significative event happens, like changing from a form web page to another form page, for example.

Transitions are the way to change the current state. Each state contains zero or more transitions, each one pointing to a destination state. When a machine wishes to change its current state, it "talks" to its current state and asks it for a transition to the destination state. From this point on, two things can happen: the current state properties have correct values and the transition is successful, or the current state properties do not have correct values and the transition fails. If the transition is successful, the current state of the machine changes to the destination state; if not, the machine's current state does not change.

So, a successful transition really depends on two conditions: state property values and "correctness" of their values. How to check that values are correct? The answer is by using "validator rules". A validator rule is an executable code that gets a State object as argument (always the current state) and returns a boolean "true" or "false" after verification of certain conditions about that state's parameters values.

This is a diagram of a transition with a validation rule:

	
  (A)
         firststate                                                 secondstate
     +---------------------+                                   +---------------------+
     |                     |               trans               |                     |
     | foo1="some string"  |      [condition: foo2 == "25"]    |  bar1="property bar"|
     | foo2="25"           |------------- OK ----------------->|                     |
     | foo3="true"         |                                   |                     |
     |                     |                                   |                     |
     +---------------------+                                   +---------------------+
          INITIAL STATE                                              FINAL STATE





  (B)
         firststate                                                 secondstate
     +---------------------+                                   +---------------------+
     |                     |               trans               |                     |
     | foo1="some string"  |      [condition: foo2 == "25"]    |  bar1="property bar"|
     | foo2="0"            |------------- FAILS -------------->|                     |
     | foo3="true"         |                                   |                     |
     |                     |                                   |                     |
     +---------------------+                                   +---------------------+
          INITIAL STATE
           FINAL STATE

				

An this is the XML code for the previous diagram:

	<state name="firststate" info="this is the first state">
	
		<property name="foo1" value="some string" />
		<property name="foo2" value="25" />	
		<property name="foo3" value="true" />
		
		<transition name="trans" to="secondstate" info="transition to second state" >
			<!-- Validation: contains this transition's validators -->
			<validation>
				<!-- Script validation rule -->
				<script name="rulescript" 
					info="a validation rule script" language="javascript">
				<![CDATA[
					
					// Entry point for the validation rule
					function validate(state) {
					
						// Property value check
						if(state.get('foo2') == '25')
							return true;
						else
							return false;
					}
				]]>
				</script>
			</validation>
		</transition>		
		
	</state>
	
	<state name="secondstate" info="a second state">
		<property name="bar1" value="property bar in the second state" />
	</state>
				

The following source code demonstrates what happens in both the diagram cases, (A) and (B), using a different machine for each case:

		// State variable
		State state = null;
		// Result variable
		boolean res = false;
		
		// Both machines are instances of the same definition
		StateMachine machineA = StateMachineFactory.createMachine("mymachine");
		StateMachine machineB = StateMachineFactory.createMachine("mymachine");
		
		/* First case (A): correct transition */
		
		// res = true
		res = machineA.forward("trans");
		
		// Get current state
		state = machineA.getCurrentState();
		
		// Prints "secondstate"
		System.println(state.getName());
		
		/* Second case (B): incorrect transition */

		// Get current state and change property value
		state = machineB.getCurrentState();
		state.set("foo2", "0");
		
		// res = false
		res = machineB.forward("trans");
		
		// Get current state
		state = machineB.getCurrentState();
		
		// Prints "firststate"
		System.println(state.getName());
		
				

This example demonstrates the use of a validator rule to check state property conditions. This is a script validator rule, but there is a "validator" type rule too (more on this later).

Instead of using just one simple rule, we could have used a more complicated one or many of them to perform the transition check. If we used more than one rule, the machine would have executed each of them, no matter if some or all of them failed. This "rule execution" process is called "validation". All of the validator rules must be contained into a <validation> tag.

There are two types of validator rules, "script" rules and "validator" rules. They are called "Scripts" and "Validators" for short. Besides returning "true" or "false", both rules can store text messages into an "errors" object, so these messages can be recovered after a failed validation and shown to the final user in a GUI.

  • Scripts

    This type of validator rule was the one used in the previous example. A script is a code that gets executed when we call "forward" on the machine. As you can see, the language used is JavaScript (ECMAScript, in fact). The script engine jStates uses is Rhino, the Mozilla scripting engine. Take a look at the Rhino documentation if you want to get the most from your scripts.

    The entry point for the rule is the "validate" function, though you can define another functions that get called from this one. There is an implicit "errors" object too, placed into the script global scope, that allows you to place error messages related to failed validation checks. This errors object has the type TransitionErrors, see the javadoc for usage instructions.

  • Validators

    For more complex validations, where a script is cumbersome to use or we need a better Java integration, the best option is to use "validators". A validator is a Java class that implements the net.sf.jstates.validation.StateValidator interface and has a constructor with just a String argument, see the javadoc for details.

    To include a validator into the validation process, we must include a <validator> tag at the same level as the scripts tags, into the <validation> tag.

    These are the attributes of the <validator> tag:

    • type: Contains the fully qualified class name of the class implementing the StateValidator interface. This class must be in the class path for jStates to be able to instantiate it.
    • parameters: This is a String containing whatever information needs the validator class for initialization. This String is passed to the validator constructor during instantiation as its only argument.

    Validators can be used as bindings or connectors to another validation frameworks, such as Jakarta Commons Validator. Currently, there is a connector to Commons Validator, see jstates-utils sourceforge package for usage and integration instructions.