Apache Commons-SCXML系列之Demo:"秒錶"

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),數據);可以通過觸發時間傳遞數據進入狀態機裏面。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章