Skip to content

Commit

Permalink
feat:同一个事件支持if-else选择状态流转
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiteBookMan1994 committed Jan 6, 2021
1 parent 44e1fd8 commit 3b2af73
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 26 deletions.
5 changes: 5 additions & 0 deletions cola-components/cola-component-statemachine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.alibaba.cola.statemachine.impl.TransitionType;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

/**
Expand Down Expand Up @@ -31,7 +32,7 @@ public interface State<S,E,C> extends Visitable{
*/
Transition<S,E,C> addTransition(E event, State<S, E, C> target, TransitionType transitionType);

Optional<Transition<S,E,C>> getTransition(E event);
List<Transition<S,E,C>> getTransition(E event);

Collection<Transition<S,E,C>> getTransitions();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import com.alibaba.cola.statemachine.State;
import com.alibaba.cola.statemachine.Transition;
import com.alibaba.cola.statemachine.Visitor;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;

/**
Expand All @@ -16,7 +18,7 @@
*/
public class StateImpl<S,E,C> implements State<S,E,C> {
protected final S stateId;
private HashMap<E, Transition<S, E,C>> transitions = new HashMap<>();
private ListMultimap<E, Transition<S, E, C>> transitions = ArrayListMultimap.create();

StateImpl(S stateId){
this.stateId = stateId;
Expand All @@ -42,17 +44,17 @@ public Transition<S, E, C> addTransition(E event, State<S,E,C> target, Transitio
* @param newTransition
*/
private void verify(E event, Transition<S,E,C> newTransition) {
Transition existingTransition = transitions.get(event);
if(existingTransition != null){
if(existingTransition.equals(newTransition)){
throw new StateMachineException(existingTransition+" already Exist, you can not add another one");
List<Transition<S, E, C>> existingTransitions = transitions.get(event);
for (Transition transition : existingTransitions) {
if (transition.equals(newTransition)) {
throw new StateMachineException(transition + " already Exist, you can not add another one");
}
}
}

@Override
public Optional<Transition<S, E, C>> getTransition(E event) {
return Optional.ofNullable(transitions.get(event));
public List<Transition<S, E, C>> getTransition(E event) {
return transitions.get(event);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,78 @@
import com.alibaba.cola.statemachine.Transition;
import com.alibaba.cola.statemachine.Visitor;

import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
* For performance consideration,
* The state machine is made "stateless" on purpose.
* Once it's built, it can be shared by multi-thread
*
* <p>
* One side effect is since the state machine is stateless, we can not get current state from State Machine.
*
* @author Frank Zhang
* @date 2020-02-07 5:40 PM
*/
public class StateMachineImpl<S,E,C> implements StateMachine<S, E, C> {
public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {

private String machineId;

private final Map<S, State<S,E,C>> stateMap;
private final Map<S, State<S, E, C>> stateMap;

private boolean ready;

public StateMachineImpl(Map<S, State< S, E, C>> stateMap){
public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {
this.stateMap = stateMap;
}

public S fireEvent(S sourceStateId, E event, C ctx){
@Override
public S fireEvent(S sourceStateId, E event, C ctx) {
isReady();
State sourceState = getState(sourceStateId);
return doTransition(sourceState, event, ctx).getId();
Transition<S, E, C> transition = routeTransition(sourceStateId, event, ctx);

if (transition == null) {
Debugger.debug("There is no Transition for " + event);
return sourceStateId;
}

return transition.transit(ctx).getId();
}

private State<S, E, C> doTransition(State sourceState, E event, C ctx) {
Optional<Transition<S,E,C>> transition = sourceState.getTransition(event);
if(transition.isPresent()){
return transition.get().transit(ctx);
private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {
State sourceState = getState(sourceStateId);

List<Transition<S, E, C>> transitions = sourceState.getTransition(event);

if (transitions == null || transitions.size() == 0) {
return null;
}
Debugger.debug("There is no Transition for " + event);
return sourceState;

Transition<S, E, C> transit = null;
for (Transition<S, E, C> transition : transitions) {
if (transition.getCondition() == null) {
transit = transition;
} else if (transition.getCondition().isSatisfied(ctx)) {
transit = transition;
break;
}
}

return transit;
}

private State getState(S currentStateId) {
State state = StateHelper.getState(stateMap, currentStateId);
if(state == null){
if (state == null) {
showStateMachine();
throw new StateMachineException(currentStateId + " is not found, please check state machine");
}
return state;
}

private void isReady() {
if(!ready){
if (!ready) {
throw new StateMachineException("State machine is not built yet, can not work");
}
}
Expand All @@ -64,7 +85,7 @@ private void isReady() {
public String accept(Visitor visitor) {
StringBuilder sb = new StringBuilder();
sb.append(visitor.visitOnEntry(this));
for(State state: stateMap.values()){
for (State state : stateMap.values()) {
sb.append(state.accept(visitor));
}
sb.append(visitor.visitOnExit(this));
Expand All @@ -78,7 +99,7 @@ public void showStateMachine() {
}

@Override
public String generatePlantUML(){
public String generatePlantUML() {
PlantUMLVisitor plantUMLVisitor = new PlantUMLVisitor();
return accept(plantUMLVisitor);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.alibaba.cola.test;

import com.alibaba.cola.statemachine.Action;
import com.alibaba.cola.statemachine.Condition;
import com.alibaba.cola.statemachine.StateMachine;
import com.alibaba.cola.statemachine.builder.StateMachineBuilder;
import com.alibaba.cola.statemachine.builder.StateMachineBuilderFactory;
import org.junit.Assert;
import org.junit.Test;

/**
* @author dingchenchen
* @since 2021/1/6
*/
public class StateMachineChoiceTest {

static class Context{
private String condition;

public Context(String condition){
this.condition = condition;
}

public String getCondition() {
return condition;
}
}

/**
* 测试选择分支,针对同一个事件:EVENT1
* if condition == "1", STATE1 --> STATE1
* if condition == "2" , STATE1 --> STATE2
* if condition == "3" , STATE1 --> STATE3
*/
@Test
public void testChoice(){
StateMachineBuilder<StateMachineTest.States, StateMachineTest.Events, Context> builder = StateMachineBuilderFactory.create();
builder.internalTransition()
.within(StateMachineTest.States.STATE1)
.on(StateMachineTest.Events.EVENT1)
.when(checkCondition1())
.perform(doAction());
builder.externalTransition()
.from(StateMachineTest.States.STATE1)
.to(StateMachineTest.States.STATE2)
.on(StateMachineTest.Events.EVENT1)
.when(checkCondition2())
.perform(doAction());
builder.externalTransition()
.from(StateMachineTest.States.STATE1)
.to(StateMachineTest.States.STATE3)
.on(StateMachineTest.Events.EVENT1)
.when(checkCondition3())
.perform(doAction());

StateMachine<StateMachineTest.States, StateMachineTest.Events, Context> stateMachine = builder.build("ChoiceConditionMachine");
StateMachineTest.States target1 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("1"));
Assert.assertEquals(StateMachineTest.States.STATE1,target1);
StateMachineTest.States target2 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("2"));
Assert.assertEquals(StateMachineTest.States.STATE2,target2);
StateMachineTest.States target3 = stateMachine.fireEvent(StateMachineTest.States.STATE1, StateMachineTest.Events.EVENT1, new Context("3"));
Assert.assertEquals(StateMachineTest.States.STATE3,target3);
}

private Condition<Context> checkCondition1() {
return (ctx) -> "1".equals(ctx.getCondition());
}

private Condition<Context> checkCondition2() {
return (ctx) -> "2".equals(ctx.getCondition());
}

private Condition<Context> checkCondition3() {
return (ctx) -> "3".equals(ctx.getCondition());
}

private Action<StateMachineTest.States, StateMachineTest.Events, Context> doAction() {
return (from, to, event, ctx)->{
System.out.println("from:"+from+" to:"+to+" on:"+event+" condition:" + ctx.getCondition());
};
}
}

0 comments on commit 3b2af73

Please sign in to comment.