Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package rinde.sim.util.fsm;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import rinde.sim.event.Event;
import rinde.sim.event.EventAPI;
import rinde.sim.event.EventDispatcher;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table.Cell;
/**
* A simple state machine. The state machine is represented by a transition
* table. A transition has the form current state + event →
* new state. A transition can be recurrent, meaning that an event
* will not initiate a transition. Events can be initiated from within a
* state by means of the {@link State#handle(Object, Object)} method or from
* outside the state machine by means of the {@link #handle(Object, Object)}. An
* attempt to perform a transition not present in the transition table result in
* a {@link RuntimeException}. Note that the transition table is immutable.
* StateMachine instances can only be created using its builder via the
* {@link #create(State)} method.
*
* @param The event type. Concrete event objects that describe the same
* event should be equal (according to {@link #equals(Object)} )
* and their {@link #hashCode()} implementation should return the same
* value. If the events do not need to contain additional meta data,
* {@link Enum}s are the best choice.
* @param The context type. This is typically the object that contains the
* {@link StateMachine}, a {@link State} represents a state of this
* object.
* @author Rinde van Lon
*/
public class StateMachine {
// TODO events *inside* the state machine should be renamed to triggers (for
// example) to avoid confusion with the event package.
private static final String NL = System.getProperty("line.separator");
private static final String NODE = "node";
private static final String NODE_DEFINITION = "[label=\"\",shape=point]" + NL;
private static final String CONN = " -> ";
private static final String LABEL_OPEN = "[label=\"";
private static final String LABEL_CLOSE = "\"]" + NL;
private static final String FILE_OPEN = "digraph stategraph {" + NL;
private static final String FILE_CLOSE = "}";
/**
* The type of {@link Event}s that this {@link StateMachine} supports.
*/
public enum StateMachineEvent {
/**
* This event is dispatched when the state machine changes its state. It is
* called right after the actual state change, after the
* {@link State#onEntry(Object, Object)} is called but before
* {@link State#handle(Object, Object)} is called.
*/
STATE_TRANSITION;
}
/**
* The {@link EventDispatcher} used for dispatching events.
*/
protected final EventDispatcher eventDispatcher;
/**
* The transition table which defines the allowed transitions.
*/
protected final ImmutableTable, E, State> transitionTable;
/**
* The current state.
*/
protected State currentState;
/**
* The initial state.
*/
protected final State startState;
/**
* This indicates whether recursive transitions should be handled explicitly.
* If true a recursive transition will be handled just like a normal
* transition, {@link State#onExit(Object, Object)} and
* {@link State#onEntry(Object, Object)} methods are called.
*/
protected final boolean explicitRecursiveTransitions;
StateMachine(State start,
ImmutableTable, E, State> table,
boolean explRecurTrns) {
eventDispatcher = new EventDispatcher(StateMachineEvent.values());
startState = start;
currentState = start;
transitionTable = table;
explicitRecursiveTransitions = explRecurTrns;
}
/**
* Gives the current {@link State} time to update.
* @param context Reference to the context.
*/
public void handle(C context) {
handle(null, context);
}
/**
* Handle the specified event.
* @param event The event that needs to be handled by the state machine. If
* this results in an attempt to perform a transition which is not
* allowed an {@link IllegalArgumentException} is thrown.
* @param context Reference to the context.
*/
public void handle(@Nullable E event, C context) {
E ev = event;
do {
if (ev != null) {
changeState(ev, context);
}
ev = currentState.handle(event, context);
} while (ev != null);
}
/**
* Perform a state change if possible.
* @param event The event that may initiate a state change.
* @param context Reference to the context.
*/
protected void changeState(E event, C context) {
checkArgument(transitionTable.contains(currentState, event),
"The event %s is not supported when in state %s.", event, currentState);
final State newState = transitionTable.get(currentState, event);
if (!newState.equals(currentState) || explicitRecursiveTransitions) {
currentState.onExit(event, context);
final State oldState = currentState;
currentState = newState;
currentState.onEntry(event, context);
eventDispatcher.dispatchEvent(new StateTransitionEvent(this,
oldState, event, newState));
}
}
/**
* @return A reference to the current state of this {@link StateMachine}.
*/
public State getCurrentState() {
return currentState;
}
/**
* Convenience method for checking whether the current state is the same as
* the specified state.
* @param s The state to be checked.
* @return true when the states are the same object,
* false otherwise.
*/
public boolean stateIs(State s) {
return currentState.equals(s);
}
/**
* Convenience method for checking whether the current state is one of the
* specified states.
* @param states The states to be checked.
* @return true when the current state is one of the specified
* states, false otherwise.
*/
public boolean stateIsOneOf(State... states) {
for (final State s : states) {
if (stateIs(s)) {
return true;
}
}
return false;
}
/**
* @return An {@link ImmutableCollection} of all states in this state machine.
*/
public ImmutableCollection> getStates() {
return transitionTable.values();
}
/**
* Looks up a state of the specified (sub)type if it exists. If there exist
* multiple the first encountered is returned.
* @param type The (sub)type to look for.
* @param The type.
* @return The state of the specified type.
* @throws IllegalArgumentException if there is no state of the specified
* type.
*/
public T getStateOfType(Class type) {
for (final State state : getStates()) {
if (type.isInstance(state)) {
return type.cast(state);
}
}
throw new IllegalArgumentException("There is no instance of " + type
+ " in this state machine.");
}
/**
* Returns true if the current state supports the event.
* @param event The event to check.
* @return true when the specified event is supported by the
* current state, false otherwise.
*/
public boolean isSupported(E event) {
return transitionTable.contains(currentState, event);
}
/**
* @return The {@link EventAPI} which allows to add and remove
* {@link rinde.sim.event.Listener}s to this {@link StateMachine}.
*/
public EventAPI getEventAPI() {
return eventDispatcher.getPublicEventAPI();
}
@Override
public int hashCode() {
return Objects.hashCode(startState, transitionTable, currentState,
explicitRecursiveTransitions);
}
@Override
public boolean equals(@Nullable Object other) {
if (other == null) {
return false;
}
if (this == other) {
return true;
}
if (!(other instanceof StateMachine)) {
return false;
}
@SuppressWarnings("unchecked")
final StateMachine fsm = (StateMachine) other;
return Objects.equal(startState, fsm.startState)
&& Objects.equal(transitionTable, fsm.transitionTable)
&& Objects.equal(currentState, fsm.currentState)
&& Objects.equal(explicitRecursiveTransitions,
fsm.explicitRecursiveTransitions);
}
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("startState", startState)
.add("transitionTable", transitionTable)
.add("currentState", currentState)
.add("explicitRecursiveTransitions", explicitRecursiveTransitions)
.toString();
}
/**
* @return A dot representation of the state machine, can be used for
* debugging the transition table.
*/
public String toDot() {
int id = 0;
final StringBuilder builder = new StringBuilder();
builder.append(FILE_OPEN);
final Set> allStates = newHashSet();
allStates.addAll(transitionTable.rowKeySet());
allStates.addAll(transitionTable.values());
final Map, Integer> idMap = newHashMap();
for (final State s : allStates) {
builder.append(NODE).append(id).append(LABEL_OPEN).append(s.name())
.append(LABEL_CLOSE);
idMap.put(s, id);
id++;
}
builder.append(NODE).append(id).append(NODE_DEFINITION);
builder.append(NODE).append(id).append(CONN).append(NODE)
.append(idMap.get(startState)).append(NL);
for (final Cell, E, State> cell : transitionTable
.cellSet()) {
final int id1 = idMap.get(cell.getRowKey());
final int id2 = idMap.get(cell.getValue());
builder.append(NODE).append(id1).append(CONN).append(NODE).append(id2)
.append(LABEL_OPEN).append(cell.getColumnKey()).append(LABEL_CLOSE);
}
builder.append(FILE_CLOSE);
return builder.toString();
}
/**
* Create a new {@link StateMachine} instance with the specified initial
* state. This method returns a reference to the {@link StateMachineBuilder}
* which allows for adding of transitions to the state machine.
* @param initialState The start state of the state machine.
* @param The event type.
* @param The context type.
* @return A reference to the {@link StateMachineBuilder} which is used for
* creating the {@link StateMachine}.
*/
public static StateMachineBuilder create(State initialState) {
return new StateMachineBuilder(initialState);
}
/**
* Facilitates the creation of a {@link StateMachine}.
* @param Event parameter of {@link StateMachine}.
* @param Context parameter of {@link StateMachine}.
* @see StateMachine
*/
public static final class StateMachineBuilder {
private final ImmutableTable.Builder, E, State> tableBuilder;
private final State start;
private boolean explicitRecursiveTransitions;
StateMachineBuilder(State initialState) {
tableBuilder = ImmutableTable.builder();
start = initialState;
explicitRecursiveTransitions = false;
}
/**
* Add a transition: state + event → new state.
* @param from The from state.
* @param event The event which triggers the transition.
* @param to The destination of the transition, the new state.
* @return A reference to this for method chaining.
*/
public StateMachineBuilder addTransition(State from, E event,
State to) {
tableBuilder.put(from, event, to);
return this;
}
/**
* Adds all transitions in the specified {@link StateMachine} to this
* builder. Duplicates are not allowed.
* @param sm The {@link StateMachine} from which the transitions are copied.
* @return The builder reference.
*/
public StateMachineBuilder addTransitionsFrom(StateMachine sm) {
tableBuilder.putAll(sm.transitionTable);
return this;
}
/**
* Enables explicit recursive transitions, this means that when an recursive
* transition is attempted the {@link State#onExit(Object, Object)} and
* {@link State#onEntry(Object, Object)} are called on that state and a
* {@link StateMachine.StateTransitionEvent} is dispatched. By default
* recursive transitions are ignored.
* @return The builder reference.
*/
public StateMachineBuilder explicitRecursiveTransitions() {
explicitRecursiveTransitions = true;
return this;
}
/**
* Builds the {@link StateMachine} as configured by this
* {@link rinde.sim.util.fsm.StateMachine.StateMachineBuilder}.
* @return The {@link StateMachine}.
*/
public StateMachine build() {
return new StateMachine(start, tableBuilder.build(),
explicitRecursiveTransitions);
}
}
/**
* Event class used by {@link StateMachine}.
* @param Event parameter of {@link StateMachine}.
* @param Context parameter of {@link StateMachine}.
* @see StateMachine
* @see StateMachineEvent
*/
public static class StateTransitionEvent extends Event {
private static final long serialVersionUID = -1478171329851890047L;
/**
* The previous state of the state machine prior to the current state.
*/
public final State previousState;
/**
* The new state which was activated just before this event was issued.
*/
public final State newState;
/**
* The event which trigger the event transition.
*/
public final E event;
/**
* Create new event instance.
* @param issuer Issuer of the event.
* @param prev {@link #previousState}.
* @param e {@link #event}.
* @param next {@link #newState}.
*/
protected StateTransitionEvent(StateMachine issuer, State prev,
E e, State next) {
super(StateMachineEvent.STATE_TRANSITION, issuer);
previousState = prev;
event = e;
newState = next;
}
/**
* Convenience method for checking whether this event equals a transition as
* specified by the parameters.
* @param prev Previous state.
* @param ev Trigger event.
* @param next New state.
* @return true when all parameters are equal to the properties
* of this event, false otherwise.
*/
public boolean equalTo(State prev, E ev, State next) {
return previousState.equals(prev) && event.equals(ev)
&& newState.equals(next);
}
@Override
public String toString() {
return new StringBuilder("[Event ").append(getEventType()).append(" ")
.append(previousState).append(" + ").append(event).append(CONN)
.append(newState).append("]").toString();
}
@Override
public int hashCode() {
return Objects.hashCode(previousState, event, newState);
}
@Override
public boolean equals(@Nullable Object other) {
if (other == null) {
return false;
}
if (this == other) {
return true;
}
if (!(other instanceof StateTransitionEvent, ?>)) {
return false;
}
@SuppressWarnings("unchecked")
final StateTransitionEvent ev = (StateTransitionEvent) other;
return Objects.equal(previousState, ev.previousState) &&
Objects.equal(newState, ev.newState) &&
Objects.equal(event, ev.event) &&
Objects.equal(eventType, ev.eventType) &&
Objects.equal(getIssuer(), ev.getIssuer());
}
}
}