Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat(statemachine): add current state fetcher for event verification and firing in StateMachine implementation #566

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.alibaba.cola.statemachine;

/**
* CurrentStateFetcher is used to fetch the current state from the context
*
* @author Yanchi Zhang
* @date 2024-12-09 11:21 AM
*/
public interface CurrentStateFetcher<S, C> {

/**
* Fetch the current state from the context
*
* @param context the context
* @return the current state
*/
S currentState(C context);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,21 @@ public interface StateMachine<S, E, C> extends Visitable{

/**
* Verify if an event {@code E} can be fired from current state {@code S}
*
* @param sourceStateId
* @param event
* @return
*/
boolean verify(S sourceStateId,E event);
boolean verify(S sourceStateId, E event);

/**
* Verify if an event {@code E} can be fired from current state {@code S}
* The source state will be fetched by {@link CurrentStateFetcher}
*
* @param event
* @return
*/
boolean verify(E event);

/**
* Send an event {@code E} to the state machine.
Expand All @@ -32,8 +42,20 @@ public interface StateMachine<S, E, C> extends Visitable{
*/
S fireEvent(S sourceState, E event, C ctx);

/**
* Send an event {@code E} to the state machine.
* The source state will be fetched by {@link CurrentStateFetcher}
*
* @param event the event to send
* @param ctx the user defined context
* @return the target state
*/
S fireEvent(E event, C ctx);

List<S> fireParallelEvent(S sourceState, E event, C ctx);

List<S> fireParallelEvent(E event, C ctx);

/**
* MachineId is the identifier for a State Machine
* @return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.alibaba.cola.statemachine.builder;

import com.alibaba.cola.statemachine.CurrentStateFetcher;
import com.alibaba.cola.statemachine.StateMachine;

/**
Expand Down Expand Up @@ -43,6 +44,13 @@ public interface StateMachineBuilder<S, E, C> {
*/
void setFailCallback(FailCallback<S, E, C> callback);

/**
* Set up current state fetcher
*
* @param fetcher the current state fetcher
*/
void setCurrentStateFetcher(CurrentStateFetcher<S, C> fetcher);

StateMachine<S, E, C> build(String machineId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.alibaba.cola.statemachine.CurrentStateFetcher;
import com.alibaba.cola.statemachine.State;
import com.alibaba.cola.statemachine.StateMachine;
import com.alibaba.cola.statemachine.StateMachineFactory;
Expand All @@ -23,6 +24,7 @@ public class StateMachineBuilderImpl<S, E, C> implements StateMachineBuilder<S,
private final Map<S, State<S, E, C>> stateMap = new ConcurrentHashMap<>();
private final StateMachineImpl<S, E, C> stateMachine = new StateMachineImpl<>(stateMap);
private FailCallback<S, E, C> failCallback = new NumbFailCallback<>();
private CurrentStateFetcher<S, C> currentStateFetcher;

@Override
public ExternalTransitionBuilder<S, E, C> externalTransition() {
Expand All @@ -49,11 +51,17 @@ public void setFailCallback(FailCallback<S, E, C> callback) {
this.failCallback = callback;
}

@Override
public void setCurrentStateFetcher(CurrentStateFetcher<S, C> fetcher) {
this.currentStateFetcher = fetcher;
}

@Override
public StateMachine<S, E, C> build(String machineId) {
stateMachine.setMachineId(machineId);
stateMachine.setReady(true);
stateMachine.setFailCallback(failCallback);
stateMachine.setCurrentStateFetcher(currentStateFetcher);
StateMachineFactory.register(stateMachine);
return stateMachine;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,9 @@
import java.util.List;
import java.util.Map;

import com.alibaba.cola.statemachine.State;
import com.alibaba.cola.statemachine.StateMachine;
import com.alibaba.cola.statemachine.Transition;
import com.alibaba.cola.statemachine.Visitor;
import com.alibaba.cola.statemachine.*;
import com.alibaba.cola.statemachine.builder.FailCallback;
import com.alibaba.cola.statemachine.exception.TransitionFailException;

/**
* For performance consideration,
Expand All @@ -30,6 +28,8 @@ public class StateMachineImpl<S, E, C> implements StateMachine<S, E, C> {

private FailCallback<S, E, C> failCallback;

private CurrentStateFetcher<S,C> currentStateFetcher;

public StateMachineImpl(Map<S, State<S, E, C>> stateMap) {
this.stateMap = stateMap;
}
Expand All @@ -45,6 +45,17 @@ public boolean verify(S sourceStateId, E event) {
return transitions != null && transitions.size() != 0;
}

@Override
public boolean verify(E event) {
isReady();
if (this.currentStateFetcher != null) {
S currentState = this.currentStateFetcher.currentState(null);
return verify(currentState, event);
} else {
throw new TransitionFailException("currentStateFetcher not set");
}
}

@Override
public S fireEvent(S sourceStateId, E event, C ctx) {
isReady();
Expand All @@ -58,6 +69,18 @@ public S fireEvent(S sourceStateId, E event, C ctx) {

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

@Override
public S fireEvent(E event, C ctx) {
isReady();
if (this.currentStateFetcher != null) {
S currentState = this.currentStateFetcher.currentState(ctx);
return fireEvent(currentState, event, ctx);
} else {
throw new TransitionFailException("currentStateFetcher not set");
}
}

@Override
public List<S> fireParallelEvent(S sourceState, E event, C context) {
isReady();
Expand All @@ -76,6 +99,17 @@ public List<S> fireParallelEvent(S sourceState, E event, C context) {
return result;
}

@Override
public List<S> fireParallelEvent(E event, C ctx) {
isReady();
if (this.currentStateFetcher != null) {
S currentState = this.currentStateFetcher.currentState(ctx);
return fireParallelEvent(currentState, event, ctx);
} else {
throw new TransitionFailException("currentStateFetcher not set");
}
}

private Transition<S, E, C> routeTransition(S sourceStateId, E event, C ctx) {
State sourceState = getState(sourceStateId);

Expand Down Expand Up @@ -171,4 +205,8 @@ public void setReady(boolean ready) {
public void setFailCallback(FailCallback<S, E, C> failCallback) {
this.failCallback = failCallback;
}

public void setCurrentStateFetcher(CurrentStateFetcher<S,C> currentStateFetcher) {
this.currentStateFetcher = currentStateFetcher;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,21 @@ public void testVerify() {
Assertions.assertFalse(stateMachine.verify(States.STATE1, Events.EVENT2));
}

@Test
public void testCurrentStatusFetcher() {
StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
builder.externalTransition()
.from(States.STATE1)
.to(States.STATE2)
.on(Events.EVENT1)
.when(checkCondition())
.perform(doAction());
builder.setCurrentStateFetcher((ctx) -> States.STATE1);
StateMachine<States, Events, Context> stateMachine = builder.build(MACHINE_ID + "-testCurrentStatusFetcher");
Assertions.assertTrue(stateMachine.verify(Events.EVENT1));
Assertions.assertFalse(stateMachine.verify(Events.EVENT2));
}

@Test
public void testExternalTransitionsNormal() {
StateMachineBuilder<States, Events, Context> builder = StateMachineBuilderFactory.create();
Expand Down
Loading