1 編程方式一
1.1 畫狀態圖
秒錶的狀態圖,其中秒錶有:重置,運行中,**已停止**3個狀態
1.2 編寫xml文件
秒錶狀態機定義文件:stopwatch1.xml
,xml文件分析請看後面
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0"
initial="reset">
<state id="reset">
<onentry>
<script>
stopWatchEntity.reset()
</script>
</onentry>
<transition event="watch.start" target="running" />
</state>
<state id="running">
<onentry>
<script>
stopWatchEntity.run()
</script>
</onentry>
<transition event="watch.stop" target="stopped" />
</state>
<state id="stopped">
<onentry>
<script>
stopWatchEntity.stop()
</script>
</onentry>
<transition event="watch.start" target="running" >
</transition>
<transition event="watch.reset" target="reset" />
</state>
</scxml>
1.3 編寫程序控制狀態轉移
需要操作的實體類,用來約束秒錶的行爲:StopWatchEntity.java
package stopwatch;
import java.io.Serializable;
import java.util.Timer;
import java.util.TimerTask;
public class StopWatchEntity implements Serializable {
private static final long serialVersionUID = 1L;
//時分秒
private int hr;
private int min;
private int sec;
//100毫秒
private int fract;
private transient Timer timer;
/**
* 重置當前狀態機
*/
public synchronized void reset() {
hr = min = sec = fract = 0;
}
/**
* 運行秒錶
*/
public synchronized void run() {
if (timer == null) {
timer = new Timer(true);
//使用timer來定時執行,秒錶計數,每100毫秒,執行一次increment方法
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
increment();
}
}, 100, 100);
}
}
/**
* 停止秒錶
*/
public synchronized void stop() {
timer.cancel();
timer = null;
}
/**
* 得到當前秒錶的時間
* @return
*/
public synchronized String getDisplay() {
return String.format("%d:%02d:%02d,%d", hr, min, sec, fract);
}
/**
* 自增方法
*/
private synchronized void increment() {
if (fract < 9) {
fract++;
} else {
fract = 0;
if (sec < 59) {
sec++;
} else {
sec = 0;
if (min < 59) {
min++;
} else {
min = 0;
hr++;
}
}
}
}
}
界面類:StopWatchFrame.java
/**
* Created by zhengshouzi on 2015/11/20.
*/
package stopwatch;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
import org.apache.commons.scxml2.Context;
import org.apache.commons.scxml2.Evaluator;
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.TriggerEvent;
import org.apache.commons.scxml2.env.SimpleErrorReporter;
import org.apache.commons.scxml2.env.jexl.JexlEvaluator;
import org.apache.commons.scxml2.io.SCXMLReader;
import org.apache.commons.scxml2.model.ModelException;
import org.apache.commons.scxml2.model.SCXML;
public class StopWatchFrame extends JFrame implements ActionListener {
private static final long serialVersionUID = 1L;
private JLabel displayLabel;
private JButton startButton;
private JButton stopButton;
private JButton resetButton;
private SCXMLExecutor executor;
private StopWatchEntity stopWatchEntity;
public static void main(String[] args) {
new StopWatchFrame();
}
public StopWatchFrame() {
super("SCXML StopWatch");
//初始化狀態機
initStopWatch();
//初始化界面
initUI();
}
/**
* 監聽器需要執行的方法,自動調用
* @param event 事件源
*/
public void actionPerformed(ActionEvent event) {
//得到綁定在每個按鈕上的命令
String command = event.getActionCommand();
//對各個命令進行判斷,在執行相應的內容
try {
if ("START".equals(command)) {
//生成watch.start事件,將轉到running狀態
executor.triggerEvent(new TriggerEvent("watch.start", TriggerEvent.SIGNAL_EVENT));
//設置一些列按鈕的可見性
startButton.setEnabled(false);
stopButton.setEnabled(true);
resetButton.setEnabled(false);
} else if ("STOP".equals(command)) {
//生成watch.stop事件,將轉到stoped狀態
executor.triggerEvent(new TriggerEvent("watch.stop", TriggerEvent.SIGNAL_EVENT));
startButton.setEnabled(true);
stopButton.setEnabled(false);
resetButton.setEnabled(true);
} else if ("RESET".equals(command)) {
//生成watch.reset事件,將轉到reset狀態
executor.triggerEvent(new TriggerEvent("watch.reset", TriggerEvent.SIGNAL_EVENT));
startButton.setEnabled(true);
stopButton.setEnabled(false);
resetButton.setEnabled(false);
}
} catch (ModelException e) {
e.printStackTrace();
}
}
/**
* 初始化秒錶
*/
private void initStopWatch() {
//得到資源文件路徑
final URL STOPWATCH = this.getClass().getResource("stopwatch1.xml");
//實例化數據模型解析器
Evaluator evaluator = new JexlEvaluator();
//實例化引擎
executor = new SCXMLExecutor(evaluator, null, new SimpleErrorReporter());
try {
//加載資源文件,實例化到一個SCXML對象,兩者之間一一對應
SCXML scxml = SCXMLReader.read(STOPWATCH);
//將這樣的一個SCXML實例,作爲狀態機對象,傳入到引擎裏面。
executor.setStateMachine(scxml);
//設置引擎執行的根上下文
Context rootContext = evaluator.newContext(null);
final StopWatchEntity stopWatchEntity = new StopWatchEntity();
rootContext.set("stopWatchEntity", stopWatchEntity);
executor.setRootContext(rootContext);
//設置當前對象
this.stopWatchEntity = stopWatchEntity;
//開始啓動流程
executor.go();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 初始化界面
*/
private void initUI() {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
setContentPane(mainPanel);
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new FlowLayout());
displayLabel = new JLabel("0:00:00,000");
displayLabel.setFont(new Font(Font.DIALOG,100,50));
contentPanel.add(displayLabel, BorderLayout.CENTER);
mainPanel.add(contentPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
startButton = createButton("START", "Start");
buttonPanel.add(startButton);
stopButton = createButton("STOP", "Stop");
stopButton.setEnabled(false);
buttonPanel.add(stopButton);
resetButton = createButton("RESET", "Reset");
resetButton.setEnabled(false);
buttonPanel.add(resetButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
setLocation(250,300);
setSize(400,200);
setResizable(true);
setVisible(true);
Timer displayTimer = new Timer();
displayTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
displayLabel.setText(stopWatchEntity.getDisplay());
}
}, 100, 100);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
/**
* 創建一個按鈕
* @param command 按鈕的命令
* @param text 按鈕上的文本
* @return 返回一個新建的按鈕
*/
private JButton createButton(final String command, final String text) {
JButton button = new JButton(text);
button.setActionCommand(command);
button.addActionListener(this);
return button;
}
}
1.4 程序結果分析
程序界面類中的定時器一直在訪問 StopWatchEntity的getDisplay屬性,來得到秒錶的值。我們通過Start,Stop,Reset來控制秒錶的狀態,在進入某一個狀態機的時候,我們調用這個狀態機根上下文設置的stopWatcheEntity對象相應的方法,來改變秒錶的值。當秒錶一直處於某一個狀態的時候,我們又通過點擊按鈕來改變秒錶的狀態。
2 編程方式2
2.1 畫狀態圖
圖和方式一一樣
2.2 編寫狀態圖xml文件
stopwatch2.xml
,這個類裏面沒有了srcipt等標籤。
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml"
version="1.0"
initial="reset">
<state id="reset">
<transition event="watch.start" target="running"/>
</state>
<state id="running">
<transition event="watch.stop" target="stopped"/>
</state>
<state id="stopped">
<transition event="watch.start" target="running"/>
<transition event="watch.reset" target="reset"/>
</state>
</scxml>
2.3編寫程序控制狀態轉移
需要操作的實體類(同時也是狀態機類),用來約束秒錶的行爲:StopWatchStateMachine.java
。這個類中的方法名字和上面的StopWatchEntity.java
名字稍有不同,這個類裏面的名字必須要和所對應的xml文件裏面的狀態名字相同。這是因爲當狀態發生轉移的時候,進入某一個狀態的時候,由框架自身根據反射機制去調用對應的方法。
package stopwatch;
import org.apache.commons.scxml2.env.AbstractStateMachine;
import org.apache.commons.scxml2.model.ModelException;
import java.util.Timer;
import java.util.TimerTask;
public class StopWatchStateMachine extends AbstractStateMachine {
public static final String EVENT_START = "watch.start";
public static final String EVENT_STOP = "watch.stop";
public static final String EVENT_RESET = "watch.reset";
private int hr, min, sec, fract;
private Timer timer;
public StopWatchStateMachine() throws ModelException {
super(StopWatchStateMachine.class.
getResource("stopwatch3.xml"));
}
/**
* 重置當前狀態機,方法名和所在的狀態名相同,又框架自己調用
*/
public void reset() {
hr = min = sec = fract=0;
timer=null;
}
/**
* 運行秒錶,方法名和所在的狀態名相同,又框架自己調用
*/
public void running() {
if (timer == null) {
timer = new Timer(true);
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
increment();
}
}, 100, 100);
}
}
/**
* 停止秒錶,方法名和所在的狀態名相同,又框架自己調用
*/
public void stopped() {
timer.cancel();
timer = null;
}
/**
* 得到當前秒錶的時間
* @return
*/
public synchronized String getDisplay() {
return String.format("%d:%02d:%02d,%d", hr, min, sec, fract);
}
//只是做一個演示,不使用這個方法
public String getCurrentState() {
return getEngine().getStatus().getStates().iterator().next().getId();
}
/**
* 自增方法
*/
private synchronized void increment() {
if (fract < 9) {
fract++;
} else {
fract = 0;
if (sec < 59) {
sec++;
} else {
sec = 0;
if (min < 59) {
min++;
} else {
min = 0;
hr++;
}
}
}
}
}
StopWatchDisplay.java
界面展現類
package stopwatch;
import org.apache.commons.scxml2.model.ModelException;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Timer;
import java.util.TimerTask;
public class StopWatchDisplay extends JFrame
implements ActionListener {
private static final long serialVersionUID = 1L;
private JLabel displayLabel;
private JButton startButton;
private JButton stopButton;
private JButton resetButton;
private StopWatchStateMachine stopWatchStateMachine;
public static void main(String[] args) throws Exception {
new StopWatchDisplay();
}
public StopWatchDisplay() throws ModelException {
super("SCXML StopWatch StateMachine");
stopWatchStateMachine = new StopWatchStateMachine();
initUI();
}
/**
* 監聽器需要執行的方法,自動調用
* @param event 事件源
*/
public void actionPerformed(ActionEvent event) {
//得到綁定在每個按鈕上的命令
String command = event.getActionCommand();
//對各個命令進行判斷,在執行相應的內容
if ("START".equals(command)) {
//生成watch.start事件,將轉到running狀態
stopWatchStateMachine.fireEvent(StopWatchStateMachine.EVENT_START);
//設置一些列按鈕的可見性
startButton.setEnabled(false);
stopButton.setEnabled(true);
resetButton.setEnabled(false);
} else if ("STOP".equals(command)) {
//生成watch.stop事件,將轉到stoped狀態
stopWatchStateMachine.fireEvent(StopWatchStateMachine.EVENT_STOP);
startButton.setEnabled(true);
stopButton.setEnabled(false);
resetButton.setEnabled(true);
} else if ("RESET".equals(command)) {
//生成watch.reset事件,將轉到reset狀態
stopWatchStateMachine.fireEvent(StopWatchStateMachine.EVENT_RESET);
startButton.setEnabled(true);
stopButton.setEnabled(false);
resetButton.setEnabled(false);
}
}
/**
* 初始化界面
*/
private void initUI() {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
setContentPane(mainPanel);
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new FlowLayout());
displayLabel = new JLabel("0:00:00,000");
displayLabel.setFont(new Font(Font.DIALOG, 100, 50));
contentPanel.add(displayLabel, BorderLayout.CENTER);
mainPanel.add(contentPanel, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
buttonPanel.setLayout(new FlowLayout());
startButton = createButton("START", "Start");
buttonPanel.add(startButton);
stopButton = createButton("STOP", "Stop");
stopButton.setEnabled(false);
buttonPanel.add(stopButton);
resetButton = createButton("RESET", "Reset");
resetButton.setEnabled(false);
buttonPanel.add(resetButton);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
setLocation(250, 300);
setSize(400,200);
setResizable(true);
setVisible(true);
Timer displayTimer = new Timer();
displayTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
displayLabel.setText(stopWatchStateMachine.getDisplay());
}
}, 100, 100);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
/**
* 創建一個按鈕
* @param command 按鈕的命令
* @param text 按鈕上的文本
* @return 返回一個新建的按鈕
*/
private JButton createButton(final String command, final String text) {
JButton button = new JButton(text);
button.setActionCommand(command);
button.addActionListener(this);
return button;
}
}
2.4 程序結果分析
程序界面類中的定時器一直在訪問 StopWatctStateMachine的getDisplay方法,來得到秒錶的值。我們通過Start,Stop,Reset來控制秒錶的狀態,在進入某一個狀態機的時候,由框架自動調用對應狀態名相同的的函數,來改變秒錶的值。當秒錶一直處於某一個狀態的時候,我們又通過點擊按鈕來改變秒錶的狀態。
源代碼AbstractStateMachine.java
中對應的調用語句如下
/**
* {@inheritDoc}
*/
public void onEntry(final EnterableState entered) {
invoke(entered.getId());
}
/**
* Invoke the no argument method with the following name.
*
* @param methodName The method to invoke.
* @return Whether the invoke was successful.
*/
public boolean invoke(final String methodName) {
Class<?> clas = this.getClass();
try {
Method method = clas.getDeclaredMethod(methodName, SIGNATURE);
method.invoke(this, PARAMETERS);
} catch (SecurityException se) {
logError(se);
return false;
} catch (NoSuchMethodException nsme) {
logError(nsme);
return false;
} catch (IllegalArgumentException iae) {
logError(iae);
return false;
} catch (IllegalAccessException iae) {
logError(iae);
return false;
} catch (InvocationTargetException ite) {
logError(ite);
return false;
}
return true;
}
3 兩種方式的總結
其實第二種方式是官網給出的例子裏面的,同時也是更貼近狀態機對象的思想。但是也有如下缺點(也許)
1、 stopWatchStateMachine.fireEvent(StopWatchStateMachine.EVENT_START);
只有這一個觸發事件的函數,不能傳遞數據。而第一種方式裏面的executor.triggerEvent(new TriggerEvent("watch.start", TriggerEvent.SIGNAL_EVENT),數據);
可以通過觸發時間傳遞數據進入狀態機裏面。