
org.jallaby.execution.StateMachine Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jallaby-core Show documentation
Show all versions of jallaby-core Show documentation
The Jallaby Hierarchical State Machine Container Core Module
The newest version!
/*
* Copyright 2022, The Jallaby Development Team
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jallaby.execution;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jallaby.event.Event;
import org.jallaby.event.EventError;
import org.jallaby.event.EventProcessingException;
import org.jallaby.event.EventResult;
import org.jallaby.event.EventValidator;
import org.jallaby.util.Stack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A state machine, consisting of {@link State}s and their {@link Transition}s.
*
* @author Matthias Rothe
*/
public class StateMachine {
private static final Logger LOGGER = LoggerFactory.getLogger(StateMachine.class);
private static final String STATE_PENDING = "STATE PENDING";
private final String name;
private final State initialState;
private final EventValidator validator;
private Stack currentStates = new Stack<>();
private boolean initialized;
/**
* Ctor.
*
* @param name the name of the state machine
* @param initialState the initial state of the state machine
* @param validator the event validator
*/
public StateMachine(final String name, final State initialState, final EventValidator validator) {
Objects.requireNonNull(name, "name must not be null");
Objects.requireNonNull(initialState, "initialState must not be null");
Objects.requireNonNull(validator, "validator must not be null");
this.name = name;
this.initialState = initialState;
this.validator = validator;
}
/**
* @return the name of the state machine
*/
public String getName() {
return name;
}
/**
* @return a new instance of this state machine
*/
public StateMachine newInstance() {
return new StateMachine(name, initialState, validator);
}
/**
* Checks whether the given event is valid for this state machine.
*
* @param event the event to be checked
* @return {@code true} if and only if the given event is valid for this state machine,
* {@code false} otherwise
*/
public boolean isValidEvent(Event event) {
return validator.isValidEvent(event);
}
/**
* Processes the given event.
*
* @param event the event to process
* @return the result of processing the event
* @throws EventProcessingException in case an exception occurs while processing the event
*/
public synchronized EventResult processEvent(Event event) throws EventProcessingException {
Objects.requireNonNull(event, "event must not be null");
if (!initialized) {
init();
}
State currentState = currentStates.peek();
Transition transition = currentState.offerEvent(event);
if (transition != null) {
return performTransition(currentState, transition, event);
} else {
return new EventResult(
event.getStateMachineName(),
event.getInstanceId().toString(),
STATE_PENDING);
}
}
private void init() {
currentStates.push(initialState);
currentStates.peek().performEntryAction(null);
initialized = true;
}
private EventResult performTransition(State state, Transition transition, Event event)
throws EventProcessingException {
Map> eventData = state.getEventData();
if (canProceed(transition.getTransitionGuards(), eventData)) {
StateInfo stateInfo = transition.getTargetStateInfo();
String nameOfNewState = stateInfo.getStates().peekLast().getName();
int statesToExit = stateInfo.getStatesToExit();
if (statesToExit >= 1) {
exitStates(statesToExit, event, eventData);
}
performTransitionActions(transition.getTransitionActionGroups(), eventData);
EventResult result = enterNewStates(stateInfo, eventData, event.getInstanceId());
if (result == null) {
result = new EventResult(
getName(),
event.getInstanceId().toString(),
nameOfNewState);
}
return result;
} else {
throw causedByTransitionGuard(event);
}
}
private boolean canProceed(final List transitionGuards,
final Map> eventData) {
for (TransitionGuard guard : transitionGuards) {
if (!guard.canProceed(eventData)) {
return false;
}
}
return true;
}
private EventProcessingException causedByTransitionGuard(Event event) {
return new EventProcessingException(
new EventError(
event.getStateMachineName(),
event.getInstanceId().toString(),
event.getEventName(),
"A transition guard prevented the event from being processed.",
202));
}
private void exitStates(int statesToExit, Event event,
Map> eventData)
throws EventProcessingException {
if (statesToExit > currentStates.size()) {
throw new EventProcessingException(new EventError(
event.getStateMachineName(),
event.getInstanceId().toString(),
event.getEventName(),
"Cannot exit " + statesToExit + " states, as there are only "
+ currentStates.size() + " states available.",
300));
}
for (int i = 0; i < statesToExit; i++) {
currentStates.pop().performExitAction(eventData);
}
}
private void performTransitionActions(final List transitionActionGroups,
final Map> eventData) {
for (TransitionActionGroup group : transitionActionGroups) {
if (group.isConcurrent()) {
performTransitionActionsConcurrently(group, eventData);
} else if (group.isSequential()) {
performTransitionActionsSequentially(group, eventData);
}
}
}
private void performTransitionActionsConcurrently(final TransitionActionGroup group,
Map> eventData) {
ExecutorService service = Executors.newCachedThreadPool();
for (TransitionAction action : group.getTransitionActions()) {
service.execute(() -> action.run(eventData));
}
service.shutdown();
}
private void performTransitionActionsSequentially(final TransitionActionGroup group,
Map> eventData) {
for (TransitionAction action : group.getTransitionActions()) {
action.run(eventData);
}
}
private EventResult enterNewStates(StateInfo stateInfo,
Map> eventData, UUID instanceId) throws EventProcessingException {
Iterator stateIterator = stateInfo.getStates().iterator();
while (stateIterator.hasNext()) {
State state = stateIterator.next();
currentStates.push(state);
FinishState finishState = state.performEntryAction(eventData);
if (finishState == FinishState.FINISHED) {
if (!stateIterator.hasNext()) {
return processEvent(new Event(getName(), instanceId.toString(),
"finished", new HashMap<>()));
} else {
LOGGER.warn(String.format("State [%s] cannot be finished as"
+ " there are more states to enter!", state.getName()));
}
}
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy