org.restcomm.connect.commons.fsm.FiniteStateMachine Maven / Gradle / Ivy
The newest version!
/*
* TeleStax, Open Source Cloud Communications
* Copyright 2011-2014, Telestax Inc and individual contributors
* by the @authors tag.
*
* This program is free software: you can redistribute it and/or modify
* under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see
*
*/
package org.restcomm.connect.commons.fsm;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.restcomm.connect.commons.annotations.concurrency.NotThreadSafe;
import com.google.common.collect.ImmutableMap;
/**
* @author [email protected] (Thomas Quintana)
*/
@NotThreadSafe
public class FiniteStateMachine {
private final ImmutableMap> transitions;
private State state;
private TransitionEndListener transitionEndListener;
public FiniteStateMachine(final State initial, final Set transitions) {
super();
checkNotNull(initial, "The initial state for a finite state machine can not be null.");
checkNotNull(transitions, "A finite state machine can not be created with transitions set to null.");
this.state = initial;
this.transitions = toImmutableMap(transitions);
}
public State state() {
return state;
}
public void addTransitionEndListener(TransitionEndListener transitionEndListener) {
this.transitionEndListener = transitionEndListener;
}
public void transition(final Object event, final State target) throws TransitionFailedException,
TransitionNotFoundException, TransitionRollbackException {
checkNotNull(event, "The message passed can not be null.");
checkNotNull(target, "The target state can not be null");
if (transitions.get(state) == null || !transitions.get(state).containsKey(target)) {
final StringBuilder buffer = new StringBuilder();
buffer.append("No transition could be found from a(n) ").append(state.getId()).append(" state to a(n) ")
.append(target.getId()).append(" state.");
throw new TransitionNotFoundException(buffer.toString(), event, state, target);
}
final Transition transition = transitions.get(state).get(target);
final Guard guard = transition.getGuard();
boolean accept = true;
if (guard != null) {
try {
accept = guard.accept(event, transition);
} catch (final Exception exception) {
throw new TransitionFailedException(exception, event, transition);
}
}
if (accept) {
// Execute action before leaving previous state (post-processing)
final Action actionOnExit = state.getActionOnExit();
if (actionOnExit != null) {
try {
actionOnExit.execute(event);
} catch (final Exception exception) {
throw new TransitionFailedException(exception, event, transition);
}
}
// Execute action before entering new state (pre-processing)
final Action actionOnEnter = target.getActionOnEnter();
if (actionOnEnter != null) {
try {
actionOnEnter.execute(event);
} catch (final Exception exception) {
throw new TransitionFailedException(exception, event, transition);
}
}
// Move to a new state
State source = state;
state = target;
// Execute action after entering new state (processing)
final Action actionOnState = target.getActionOnState();
if (actionOnState != null) {
try {
actionOnState.execute(event);
} catch (final Exception exception) {
throw new TransitionFailedException(exception, event, transition);
}
}
if (transitionEndListener != null) {
transitionEndListener.onTransitionEnd(source, target, event);
}
} else {
final StringBuilder buffer = new StringBuilder();
buffer.append("The condition guarding a transition from a(n) ").append(transition.getStateOnEnter().getId())
.append(" state to a(n) ").append(transition.getStateOnExit().getId()).append(" state has failed.");
throw new TransitionRollbackException(buffer.toString(), event, transition);
}
}
private ImmutableMap> toImmutableMap(final Set transitions) {
final Map> map = new HashMap>();
for (final Transition transition : transitions) {
final State stateOnEnter = transition.getStateOnEnter();
if (!map.containsKey(stateOnEnter)) {
map.put(stateOnEnter, new HashMap());
}
final State stateOnExit = transition.getStateOnExit();
map.get(stateOnEnter).put(stateOnExit, transition);
}
return ImmutableMap.copyOf(map);
}
}