io.statusmachina.core.MachineInstanceImpl Maven / Gradle / Ivy
Show all versions of status-machina-core Show documentation
/*
*
* 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;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import io.statusmachina.core.api.*;
import java.util.*;
public class MachineInstanceImpl implements Machine {
private final String id;
private final MachineDefinition def;
private final ImmutableMap context;
private final ImmutableList> history;
private final Optional error;
private final S currentState;
public static MachineBuilder ofType(MachineDefinition definition) {
return new MachineInstanceBuilderImpl().ofType(definition);
}
MachineInstanceImpl(MachineDefinition def, Map context) throws TransitionException {
this(def, UUID.randomUUID().toString(), context);
}
MachineInstanceImpl(MachineDefinition def,String id, Map context) throws TransitionException {
this.def = def;
final StateAndContext kickoff = kickoff(def.getInitialState(), ImmutableMap.builder().putAll(context).build());
this.id = id;
this.history = ImmutableList.>builder().build();
this.currentState = kickoff.getState();
this.context = kickoff.getContext();
this.error = Optional.empty();
}
public MachineInstanceImpl(
String id,
MachineDefinition def,
S currentState,
Map context,
List> history,
Optional error
) throws TransitionException {
this.def = def;
final StateAndContext kickoff = currentState == def.getInitialState() ?
kickoff(def.getInitialState(), ImmutableMap.builder().putAll(context).build())
: new StateAndContext(currentState, ImmutableMap.builder().putAll(context).build());
this.id = id;
this.history = ImmutableList.>builder().addAll(history).build();
this.error = error;
this.currentState = kickoff.getState();
this.context = kickoff.getContext();
}
private StateAndContext kickoff(S initialState, ImmutableMap initialContext) {
S runningState = initialState;
ImmutableMap runningContext = ImmutableMap.builder().putAll(initialContext).build();
Optional> transition;
while ((transition = def.findStpTransition(runningState)).isPresent()) {
final Transition seTransition = transition.get();
final Optional> action = seTransition.getAction();
final ImmutableMap tmpContext = ImmutableMap.builder().putAll(runningContext).build();
runningContext = action.map(mapConsumer -> mapConsumer.apply(tmpContext, null)).orElse(tmpContext);
runningState = seTransition.getTo();
}
return new StateAndContext(runningState, runningContext);
}
@Override
public String getId() {
return id;
}
@Override
public S getCurrentState() {
return currentState;
}
@Override
public Map getContext() {
return new HashMap<>(context);
}
@Override
public List> getHistory() {
return new ArrayList<>(history);
}
@Override
public Optional getError() {
return error;
}
@Override
public MachineDefinition getDefinition() {
return def;
}
@Override
public boolean isErrorState() {
return error.isPresent();
}
@Override
public Machine sendEvent(E event) throws TransitionException {
Transition transition = def.findEventTransion(currentState, event).orElseThrow(() -> new IllegalStateException("for machines of type " + def.getName() + " event " + event.toString() + " does not trigger any transition out of state " + currentState.toString()));
return applyTransition(transition, null);
}
@Override
public Machine sendEvent(E event, P param) throws TransitionException {
final Transition transition = def.findEventTransion(currentState, event).orElseThrow(() -> new IllegalStateException("for machines of type " + def.getName() + " event " + event.toString() + " does not trigger any transition out of state " + currentState.toString()));
return applyTransition(transition, param);
}
@Override
public Machine recoverFromError(S state, Map context) {
Optional newError = Optional.empty();
ImmutableMap newContext = ImmutableMap.builder().putAll(context).build();
return new MachineInstanceImpl(id, def, state, newContext, history, newError);
}
private Machine tryStp() throws TransitionException {
final Optional> stpTransition = def.findStpTransition(currentState);
if (stpTransition.isPresent()) {
return applyTransition(stpTransition.get(), null);
} else
return this;
}
private Machine applyTransition(Transition transition, P param) throws TransitionException {
final Optional> action = transition.getAction();
try {
ImmutableMap newContext = action.map(mapConsumer -> ((TransitionAction) mapConsumer).apply(context, param)).orElse(context);
Optional newError = Optional.empty();
S newState = transition.getTo();
return new MachineInstanceImpl<>(id, def, newState, newContext, history, newError).tryStp();
} catch (Throwable t) {
def.getErrorHandler().accept(new DefaultErrorData<>(transition, param, t));
Optional newError = Optional.of(t.getMessage());
return new MachineInstanceImpl<>(id, def, currentState, context, history, newError);
}
}
@Override
public boolean isTerminalState() {
return def.getTerminalStates().contains(currentState);
}
private class StateAndContext {
private final S state;
private ImmutableMap context;
public StateAndContext(S state, ImmutableMap context) {
this.state = state;
this.context = context;
}
public S getState() {
return state;
}
public ImmutableMap getContext() {
return context;
}
}
private class DefaultErrorData implements ErrorData {
private final Transition transition;
private final P param;
private final Throwable t;
public DefaultErrorData(Transition transition, P param, Throwable t) {
this.transition = transition;
this.param = param;
this.t = t;
}
@Override
public S getFrom() {
return transition.getFrom();
}
@Override
public S getTo() {
return transition.getTo();
}
@Override
public Optional getEvent() {
return transition.getEvent();
}
@Override
public Map getContext() {
return new HashMap<>(context);
}
@Override
public P getEventParameter() {
return param;
}
@Override
public String getErrorMessage() {
return t.getMessage();
}
}
}