io.statusmachina.core.stdimpl.MachineDefImpl Maven / Gradle / Ivy
/*
* Copyright 2019 <---> Present Status Machina Contributors (https://github.com/entzik/status-machina/graphs/contributors)
*
* 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
*
* This software 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 io.statusmachina.core.stdimpl;
import com.google.common.collect.ImmutableSet;
import io.statusmachina.core.api.ErrorData;
import io.statusmachina.core.api.MachineDefinition;
import io.statusmachina.core.api.MachineDefinitionBuilder;
import io.statusmachina.core.api.Transition;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Defines a state machine by its states, events and transitions
*
* @param
* @param
*/
public class MachineDefImpl implements MachineDefinition {
final private ImmutableSet allStates;
final private S initialState;
final private ImmutableSet terminalStates;
final private ImmutableSet events;
final private ImmutableSet> transitions;
final private String name;
final private Function stateToString;
final private Function stringToState;
final private Function eventToString;
final private Function stringToEvent;
private Consumer> errorHandler;
public static MachineDefinitionBuilder newBuilder() {
return new BuilderImpl<>();
}
public MachineDefImpl(
String name,
Set allStates,
S initialState,
Set terminalStates,
Set events,
Set> transitions,
Consumer> errorHandler,
Function stateToString,
Function stringToState,
Function eventToString,
Function stringToEvent
) {
this.name = name;
this.errorHandler = errorHandler;
this.stateToString = stateToString;
this.stringToState = stringToState;
this.eventToString = eventToString;
this.stringToEvent = stringToEvent;
this.allStates = ImmutableSet.builder().addAll(allStates).build();
this.initialState = initialState;
this.terminalStates = ImmutableSet.builder().addAll(terminalStates).build();
this.events = ImmutableSet.builder().addAll(events).build();
this.transitions = ImmutableSet.>builder().addAll(transitions).build();
}
/**
* @return the set of states the machine can find itself in
*/
@Override
public Set getAllStates() {
return allStates;
}
/**
* the machine initial state. the state which the machine will find itself in right after instantiations. must be
* one of the states provided by {@link #getAllStates()}
*
* @return the initial state
*/
@Override
public S getInitialState() {
return initialState;
}
/**
* States considered terminal. once in one of these states, the machine can not transition any more. Terminal states
* must be a subset of the states provided by {@link #getAllStates()}
*
* @return terminal states
*/
@Override
public Set getTerminalStates() {
return terminalStates;
}
/**
* @return a list of all events that can cause a transition
*/
@Override
public Set getEvents() {
return events;
}
/**
* @return the list of transitions this machine can execute
*/
@Override
public Set> getTransitions() {
return transitions;
}
/**
* @return a function that converts a state to a string
*/
@Override
public Function getStateToString() {
return stateToString;
}
/**
* @return a function that converts a string to a state
*/
@Override
public Function getStringToState() {
return stringToState;
}
/**
* @return a function that converts an event to a string
*/
@Override
public Function getEventToString() {
return eventToString;
}
/**
* @return a function that converts an event to a string
*/
@Override
public Function getStringToEvent() {
return stringToEvent;
}
/**
* find the stp transition configured out of the specified state, if any
*
* @param state the state out of which we ae looking for an STP transition
* @return the stp transition
*/
@Override
public Optional> findStpTransition(S state) {
return transitions.stream().filter(t -> t.getFrom().equals(state) && t.isSTP()).findFirst();
}
/**
* finds the transition configured out of the specified state for the specified event, if any
*
* @param currentState the state out of which we ae looking for an event transition
* @param event the event that triggers the transition
* @return the traansition
*/
@Override
public Optional> findEventTransion(S currentState, E event) {
final Predicate> fromCurrentState = t -> t.getFrom().equals(currentState);
final Predicate> forThisEvent = t -> !t.isSTP() && t.getEvent().get().equals(event);
return transitions.stream().filter(fromCurrentState.and(forThisEvent)).findFirst();
}
/**
* @return a handler to be called when the machine enters an error state (such as failing during a transition)
*/
@Override
public Consumer> getErrorHandler() {
return errorHandler;
}
/**
* @return the name of this machine type
*/
@Override
public String getName() {
return name;
}
public static class BuilderImpl implements MachineDefinitionBuilder {
private Set allStates;
private S initialState;
private Set terminalStates;
private Set events;
private Set> transitions;
private String name;
private Consumer> errorHandler;
private Function stateToString;
private Function stringToState;
private Function eventToString;
private Function stringToEvent;
BuilderImpl() {
}
@Override
public MachineDefinitionBuilder name(String name) {
this.name = name;
return this;
}
@Override
public MachineDefinitionBuilder states(S... allStates) {
this.allStates = new HashSet<>(Arrays.asList(allStates));
return this;
}
@Override
public MachineDefinitionBuilder initialState(S initialState) {
if (allStates == null || notAState(initialState))
throw new IllegalArgumentException("All states must be defined before defining the initial state. The initial state must be selected among previously defined states.");
else
this.initialState = initialState;
return this;
}
@Override
public MachineDefinitionBuilder terminalStates(S... terminals) {
Set terminalStates = new HashSet<>(Arrays.asList(terminals));
if (allStates == null || !allStates.containsAll(terminalStates))
throw new IllegalArgumentException("All states must be defined before defining the initial state. Terminal states must be selected among previously defined states.");
else
this.terminalStates = terminalStates;
return this;
}
@Override
public MachineDefinitionBuilder events(E... events) {
this.events = new HashSet<>(Arrays.asList(events));
return this;
}
@Override
public MachineDefinitionBuilder stateToString(Function stateToString) {
this.stateToString = stateToString;
return this;
}
@Override
public MachineDefinitionBuilder stringToState(Function stringToState) {
this.stringToState = stringToState;
return this;
}
@Override
public MachineDefinitionBuilder eventToString(Function eventToString) {
this.eventToString = eventToString;
return this;
}
@Override
public MachineDefinitionBuilder stringToEvent(Function stringToEvent) {
this.stringToEvent = stringToEvent;
return this;
}
@Override
public MachineDefinitionBuilder errorHandler(Consumer> errorHandler) {
this.errorHandler = errorHandler;
return this;
}
@Override
public MachineDefinitionBuilder transitions(Transition... allTransitions) {
Set> transitions = new HashSet<>(Arrays.asList(allTransitions));
if (events == null || allStates == null)
throw new IllegalArgumentException("All states and events must be defined before defining transitions. All states except the inital state must be reachable from another state. Each state except terminal states must transition to other states");
if (!validateTransitions(transitions))
throw new IllegalStateException("Transitions are not valid");
this.transitions = transitions;
return this;
}
@Override
public MachineDefinition build() {
return new MachineDefImpl(
name,
new HashSet<>(allStates),
initialState,
new HashSet<>(terminalStates),
new HashSet<>(events),
new HashSet<>(transitions),
errorHandler == null ? errorData -> {
} : errorHandler,
stateToString,
stringToState,
eventToString,
stringToEvent
);
}
private boolean validateTransitions(Set> transitions) {
for (Transition transition : transitions)
if (notASourcetState(transition.getFrom()) || notADestinationState(transition.getTo()) || notAnEvent(transition.getEvent()))
return false;
return true;
}
private boolean notASourcetState(S state) {
return terminalStates.contains(state) || notAState(state);
}
private boolean notADestinationState(S state) {
return notAState(state);
}
private boolean notAState(S state) {
return !allStates.contains(state);
}
private boolean notAnEvent(Optional event) {
return event.isPresent() && !events.contains(event.get());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy