利用Antlr开发状态机
程序员文章站
2022-04-13 14:09:32
...
Antlr 不用多介绍了,只想说此乃神器也~~~
进入正题,首先是Antlr 定义的语法:
grammar StateMachine;
options {
output=AST;
ASTLabelType=CommonTree;
}
tokens {
RULE_ROOT;
STATE_DECLARATION;
CASE_CLAUSE;
CASE_DECLARATION;
}
@header {package compiler.statemachine;}
@lexer::header {package compiler.statemachine;}
ruleRoot
:
stateDeclaration* EOF
->^(RULE_ROOT stateDeclaration*)
;
stateDeclaration
:
Identifier '{' caseDeclaration* '}' ';'?
->^(STATE_DECLARATION Identifier ^(CASE_CLAUSE caseDeclaration*))
;
caseDeclaration
:
Identifier '=>' Identifier ';'
->^(CASE_DECLARATION Identifier+)
;
Identifier
:
('A'..'Z'|'a'..'z'|'_')('A'..'Z'|'a'..'z'|'0'..'9'|'_')*
;
COMMENT
:
'//' ~('\n'|'\r')* '\r'? ('\n'|EOF) {$channel=HIDDEN;}
|
'/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
;
WS
:
(' '|'\t'|'\r'|'\u000C'|'\n') {$channel=HIDDEN;}
;
从语法定义中可以看出,我们使用时候需要输入的格式为
状态{动作=>新状态}
比如我们有业务是,商务专员填写好报价单后,提交到招标经理,招标经理审批通过后,提交到大区经理。
现在来定义我们业务中所会用到的State 和Action 的枚举
两个枚举根据实际业务可以*修改,比如 招标经理拒绝报价单等
接下来是重头戏,如何解析由Antlr生成的抽象语法树!
先定义StateMachine 接口
然后编写实现类
最主要的就是complieRule 方法
解析Antlr 生成的抽象语法树,把 状态{动作=>新状态}这样格式的字符串,转换为
Map<TState,Map<TAction,TState>> dict = new HashMap<TState,Map<TAction,TState>>()
这样的一个Map
最后编写测试类
从代码:
stateMachine.setRule("UnInitialized { CommercialCreate => CommercialApplying;} CommercialApplying {CommercialModify => CommercialApplying;CommercialCommit => BiddingManagerAuditing;BiddingManagerModify => CommercialApplying; BiddingManagerApprove => CDManagerAuditing; }");
可以看出 输入字符串
"UnInitialized { CommercialCreate => CommercialApplying;} CommercialApplying {CommercialModify => CommercialApplying;CommercialCommit => BiddingManagerAuditing;BiddingManagerModify => CommercialApplying; BiddingManagerApprove => CDManagerAuditing; }"
调用stateMachine.complieRule();
通过 stateMachine.getValidActions 得到 当前状态的报价单所对应的所有可以执行的Action
例如现在招标经理要将商务提交的报价单审批通过;
则通过Set<RequestAction> currentActions = stateMachine.getValidActions(requestState);
得到所有能够执行的Action
通过if(currentActions.contains(RequestAction.BiddingManagerApprove)){
requestState = stateMachine.changeState(requestState, RequestAction.BiddingManagerApprove);
}
来改变报价单的状态从 RequestState requestState = RequestState.CommercialApplying;
报价单状态从,商务申请中变为,大区经理审批中 CDManagerAuditing
实际运用中结合Spring可以优化
StateMachineImpl<RequestState, RequestAction> stateMachine = new StateMachineImpl<RequestState, RequestAction>();
stateMachine.setStateType(RequestState.class);
stateMachine.setActionType(RequestAction.class);
stateMachine.complieRule();
进入正题,首先是Antlr 定义的语法:
grammar StateMachine;
options {
output=AST;
ASTLabelType=CommonTree;
}
tokens {
RULE_ROOT;
STATE_DECLARATION;
CASE_CLAUSE;
CASE_DECLARATION;
}
@header {package compiler.statemachine;}
@lexer::header {package compiler.statemachine;}
ruleRoot
:
stateDeclaration* EOF
->^(RULE_ROOT stateDeclaration*)
;
stateDeclaration
:
Identifier '{' caseDeclaration* '}' ';'?
->^(STATE_DECLARATION Identifier ^(CASE_CLAUSE caseDeclaration*))
;
caseDeclaration
:
Identifier '=>' Identifier ';'
->^(CASE_DECLARATION Identifier+)
;
Identifier
:
('A'..'Z'|'a'..'z'|'_')('A'..'Z'|'a'..'z'|'0'..'9'|'_')*
;
COMMENT
:
'//' ~('\n'|'\r')* '\r'? ('\n'|EOF) {$channel=HIDDEN;}
|
'/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;}
;
WS
:
(' '|'\t'|'\r'|'\u000C'|'\n') {$channel=HIDDEN;}
;
从语法定义中可以看出,我们使用时候需要输入的格式为
状态{动作=>新状态}
比如我们有业务是,商务专员填写好报价单后,提交到招标经理,招标经理审批通过后,提交到大区经理。
现在来定义我们业务中所会用到的State 和Action 的枚举
package compiler.statemachine;
public enum RequestState {
UnInitialized,
CommercialApplying, // 商务申请报价单
BiddingManagerAuditing, //招标经理审批报价单
CDManagerAuditing //大区经理审批报价单
}
package compiler.statemachine;
public enum RequestAction {
CommercialCreate, //商务专员创建报价单
CommercialModify, //商务专员修改报价单
CommercialCommit, //商务专员提交报价单
BiddingManagerModify, //招标经理修改报价单
BiddingManagerApprove //招标经理审批通过报价单
}
两个枚举根据实际业务可以*修改,比如 招标经理拒绝报价单等
接下来是重头戏,如何解析由Antlr生成的抽象语法树!
先定义StateMachine 接口
package compiler.statemachine;
import java.util.Set;
public interface StateMachine<TState, TAction> {
Set<TState> getStates() ;
Set<TAction> getActions() ;
Set<TAction> getValidActions(TState state);
TState changeState(TState currentState, TAction action);
}
然后编写实现类
package compiler.statemachine;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.Tree;
public class StateMachineImpl<TState extends Enum<TState>, TAction extends Enum<TAction>> implements StateMachine<TState, TAction>{
private Class<TState> stateType;
private Class<TAction> actionType;
private String rule;
private Map<TState,Map<TAction,TState>> dict = new HashMap<TState,Map<TAction,TState>>();
public Class<TState> getStateType() {
return stateType;
}
public void setStateType(Class<TState> stateType) {
this.stateType = stateType;
}
public Class<TAction> getActionType() {
return actionType;
}
public void setActionType(Class<TAction> actionType) {
this.actionType = actionType;
}
public String getRule() {
return rule;
}
public void setRule(String rule) {
this.rule = rule;
}
public StateMachineImpl(){
}
public StateMachineImpl(Class<TState> state,Class<TAction> action){
this.stateType = state;
this.actionType = action;
}
public void complieRule(){
StateMachineLexer lexer = new StateMachineLexer(new ANTLRStringStream(rule));
CommonTokenStream tokens = new CommonTokenStream(lexer);
StateMachineParser parser = new StateMachineParser(tokens);
try {
Tree ruleRootNode = (Tree)parser.ruleRoot().getTree();
for(int i=0;i<ruleRootNode.getChildCount();i++){
Tree stateDeclarationNode = ruleRootNode.getChild(i);
String stateText = stateDeclarationNode.getChild(0).getText();
TState state;
state = (TState) Enum.valueOf(this.stateType, stateText);
System.out.println(state.getClass());
Map<TAction,TState> nestedDict = new HashMap<TAction,TState>();
dict.put(state, nestedDict);
for(int ii=0;ii<stateDeclarationNode.getChildCount();ii++){
Tree caseClauseNode = stateDeclarationNode.getChild(ii);
for(int iii=0;iii<caseClauseNode.getChildCount();iii++){
Tree caseDeclarationNode = caseClauseNode.getChild(iii);
String actionText = caseDeclarationNode.getChild(0).getText();
String targetStateText = caseDeclarationNode.getChild(1).getText();
TAction action;
TState targetState;
action = (TAction) Enum.valueOf(this.actionType, actionText);
targetState = (TState) Enum.valueOf(this.stateType, targetStateText);
nestedDict.put(action, targetState);
}
}
}
} catch (RecognitionException e) {
e.printStackTrace();
}
}
@Override
public Set<TState> getStates() {
return dict.keySet();
}
@Override
public Set<TAction> getActions() {
Set<TAction> actionsSet = new HashSet<TAction>();
for (Map<TAction, TState> map : dict.values()) {
actionsSet.addAll(map.keySet());
}
return actionsSet;
}
@Override
public Set<TAction> getValidActions(TState state) {
if(!dict.containsKey(state)){
throw new RuntimeException("State not in the system");
}
return dict.get(state).keySet();
}
@Override
public TState changeState(TState currentState, TAction action) {
if(!dict.containsKey(currentState)){
throw new IllegalArgumentException();
}
Map<TAction,TState> rules = dict.get(currentState);
TState returnState = rules.get(action);
if(returnState==null){
throw new UnsupportedOperationException();
}
return returnState;
}
}
最主要的就是complieRule 方法
解析Antlr 生成的抽象语法树,把 状态{动作=>新状态}这样格式的字符串,转换为
Map<TState,Map<TAction,TState>> dict = new HashMap<TState,Map<TAction,TState>>()
这样的一个Map
最后编写测试类
package test.statemachine;
import java.util.Map;
import java.util.Set;
import compiler.statemachine.EnumerationStateMechineLocalObject;
import compiler.statemachine.RequestAction;
import compiler.statemachine.RequestState;
import compiler.statemachine.StateMachineImpl;
public class StateMachineTest {
public static void main(String[] args) {
StateMachineTest();
}
public static void StateMachineTest() {
StateMachineImpl<RequestState, RequestAction> stateMachine = new StateMachineImpl<RequestState, RequestAction>();
stateMachine.setRule("UnInitialized { CommercialCreate => CommercialApplying;} CommercialApplying {CommercialModify => CommercialApplying;CommercialCommit => BiddingManagerAuditing;BiddingManagerModify => CommercialApplying; BiddingManagerApprove => CDManagerAuditing; }");
stateMachine.setStateType(RequestState.class);
stateMachine.setActionType(RequestAction.class);
stateMachine.complieRule();
RequestState requestState = RequestState.CommercialApplying;
Set<RequestAction> currentActions = stateMachine.getValidActions(requestState);
if(currentActions.contains(RequestAction.BiddingManagerApprove)){
requestState = stateMachine.changeState(requestState, RequestAction.BiddingManagerApprove);
}
Set<RequestAction> actions = stateMachine.getActions();
for (RequestAction requestAction : actions) {
System.out.println(requestAction);
}
System.out.println(requestState);
}
}
从代码:
stateMachine.setRule("UnInitialized { CommercialCreate => CommercialApplying;} CommercialApplying {CommercialModify => CommercialApplying;CommercialCommit => BiddingManagerAuditing;BiddingManagerModify => CommercialApplying; BiddingManagerApprove => CDManagerAuditing; }");
可以看出 输入字符串
"UnInitialized { CommercialCreate => CommercialApplying;} CommercialApplying {CommercialModify => CommercialApplying;CommercialCommit => BiddingManagerAuditing;BiddingManagerModify => CommercialApplying; BiddingManagerApprove => CDManagerAuditing; }"
调用stateMachine.complieRule();
通过 stateMachine.getValidActions 得到 当前状态的报价单所对应的所有可以执行的Action
例如现在招标经理要将商务提交的报价单审批通过;
则通过Set<RequestAction> currentActions = stateMachine.getValidActions(requestState);
得到所有能够执行的Action
通过if(currentActions.contains(RequestAction.BiddingManagerApprove)){
requestState = stateMachine.changeState(requestState, RequestAction.BiddingManagerApprove);
}
来改变报价单的状态从 RequestState requestState = RequestState.CommercialApplying;
报价单状态从,商务申请中变为,大区经理审批中 CDManagerAuditing
实际运用中结合Spring可以优化
StateMachineImpl<RequestState, RequestAction> stateMachine = new StateMachineImpl<RequestState, RequestAction>();
stateMachine.setStateType(RequestState.class);
stateMachine.setActionType(RequestAction.class);
stateMachine.complieRule();
上一篇: 自定义SparkSql语法的一般步骤
下一篇: js优化阿里云图片加载(二)