接下来我们要把这样的方式用在通信中。用通信的方式来模拟录放机的按钮。
我们先看状态的定义:
001 | package com.a2.desktop.example10.mina.statemachine; |
003 | import static org.apache.mina.statemachine.event.IoHandlerEvents.EXCEPTION_CAUGHT; |
004 | import static org.apache.mina.statemachine.event.IoHandlerEvents.MESSAGE_RECEIVED; |
005 | import static org.apache.mina.statemachine.event.IoHandlerEvents.SESSION_OPENED; |
007 | import org.apache.mina.core.future.IoFutureListener; |
008 | import org.apache.mina.core.session.IoSession; |
010 | import org.apache.mina.statemachine.StateControl; |
011 | import org.apache.mina.statemachine.annotation.IoHandlerTransition; |
012 | import org.apache.mina.statemachine.annotation.IoHandlerTransitions; |
013 | import org.apache.mina.statemachine.annotation.State; |
014 | import org.apache.mina.statemachine.context.AbstractStateContext; |
015 | import org.apache.mina.statemachine.context.StateContext; |
016 | import org.apache.mina.statemachine.event.Event; |
018 | public class TapeDeckServer { |
021 | public static final String ROOT = "Root" ; |
024 | public static final String EMPTY = "Empty" ; |
026 | public static final String LOADED = "Loaded" ; |
028 | public static final String PLAYING = "Playing" ; |
030 | public static final String PAUSED = "Paused" ; |
032 | private final String[] tapes = { "盖世英雄-王力宏" , "唯一-王力宏" }; |
034 | static class TapeDeckContext extends AbstractStateContext { |
035 | public String tapeName; |
038 | @IoHandlerTransition (on = SESSION_OPENED, in = EMPTY) |
039 | public void connect(IoSession session) { |
040 | session.write( "+ Greetings from your tape deck!" ); |
043 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = EMPTY, next = LOADED) |
044 | public void loadTape(TapeDeckContext context, IoSession session, LoadCommand cmd) { |
046 | if (cmd.getTapeNumber() < 1 || cmd.getTapeNumber() > tapes.length) { |
047 | session.write( "- Unknown tape number: " + cmd.getTapeNumber()); |
048 | StateControl.breakAndGotoNext(EMPTY); |
050 | context.tapeName = tapes[cmd.getTapeNumber() - 1 ]; |
051 | session.write( "+ \"" + context.tapeName + "\" loaded" ); |
055 | @IoHandlerTransitions ({ |
056 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = LOADED, next = PLAYING), |
057 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = PAUSED, next = PLAYING) |
059 | public void playTape(TapeDeckContext context, IoSession session, PlayCommand cmd) { |
060 | session.write( "+ Playing \"" + context.tapeName + "\"" ); |
063 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = PLAYING, next = PAUSED) |
064 | public void pauseTape(TapeDeckContext context, IoSession session, PauseCommand cmd) { |
065 | session.write( "+ \"" + context.tapeName + "\" paused" ); |
068 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = PLAYING, next = LOADED) |
069 | public void stopTape(TapeDeckContext context, IoSession session, StopCommand cmd) { |
070 | session.write( "+ \"" + context.tapeName + "\" stopped" ); |
073 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = LOADED, next = EMPTY) |
074 | public void ejectTape(TapeDeckContext context, IoSession session, EjectCommand cmd) { |
075 | session.write( "+ \"" + context.tapeName + "\" ejected" ); |
076 | context.tapeName = null ; |
079 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = ROOT) |
080 | public void listTapes(IoSession session, ListCommand cmd) { |
081 | StringBuilder response = new StringBuilder( "+ (" ); |
082 | for ( int i = 0 ; i < tapes.length; i++) { |
083 | response.append(i + 1 ).append( ": " ); |
084 | response.append( '"' ).append(tapes[i]).append( '"' ); |
085 | if (i < tapes.length - 1 ) { |
086 | response.append( ", " ); |
089 | response.append( ')' ); |
090 | session.write(response); |
093 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = ROOT) |
094 | public void info(TapeDeckContext context, IoSession session, InfoCommand cmd) { |
095 | String state = context.getCurrentState().getId().toLowerCase(); |
096 | if (context.tapeName == null ) { |
097 | session.write( "+ Tape deck is " + state + "" ); |
099 | session.write( "+ Tape deck is " + state |
100 | + ". Current tape: \"" + context.tapeName + "\"" ); |
104 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = ROOT) |
105 | public void quit(TapeDeckContext context, IoSession session, QuitCommand cmd) { |
106 | session.write( "+ Bye! Please come back!" ).addListener(IoFutureListener.CLOSE); |
109 | @IoHandlerTransition (on = MESSAGE_RECEIVED, in = ROOT, weight = 10 ) |
110 | public void error(Event event, StateContext context, IoSession session, Command cmd) { |
111 | session.write( "- Cannot " + cmd.getName() |
112 | + " while " + context.getCurrentState().getId().toLowerCase()); |
115 | @IoHandlerTransition (on = EXCEPTION_CAUGHT, in = ROOT) |
116 | public void commandSyntaxError(IoSession session, CommandSyntaxException e) { |
117 | session.write( "- " + e.getMessage()); |
120 | @IoHandlerTransition (on = EXCEPTION_CAUGHT, in = ROOT, weight = 10 ) |
121 | public void exceptionCaught(IoSession session, Exception e) { |
126 | @IoHandlerTransition (in = ROOT, weight = 100 ) |
127 | public void unhandledEvent() { |
命令的抽象类:
1 | package com.a2.desktop.example10.mina.statemachine; |
3 | public abstract class Command { |
4 | public abstract String getName(); |
以下是各类命令,实现形式相似:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | public class LoadCommand extends Command { |
05 | public static final String NAME = "load" ; |
07 | private final int tapeNumber; |
09 | public LoadCommand( int tapeNumber) { |
10 | this .tapeNumber = tapeNumber; |
13 | public int getTapeNumber() { |
18 | public String getName() { |
24 | package com.a2.desktop.example10.mina.statemachine; |
26 | public class PlayCommand extends Command { |
28 | public static final String NAME = "play" ; |
31 | public String getName() { |
下面是解码器,继承了文本的解码方式:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | import java.nio.charset.Charset; |
04 | import java.util.LinkedList; |
06 | import org.apache.mina.core.buffer.IoBuffer; |
07 | import org.apache.mina.core.filterchain.IoFilter.NextFilter; |
08 | import org.apache.mina.core.session.IoSession; |
09 | import org.apache.mina.filter.codec.ProtocolDecoderOutput; |
10 | import org.apache.mina.filter.codec.textline.LineDelimiter; |
11 | import org.apache.mina.filter.codec.textline.TextLineDecoder; |
13 | public class CommandDecoder extends TextLineDecoder { |
15 | public CommandDecoder() { |
16 | super (Charset.forName( "UTF8" ), LineDelimiter.WINDOWS); |
19 | private Object parseCommand(String line) throws CommandSyntaxException { |
20 | String[] temp = line.split( " +" , 2 ); |
21 | String cmd = temp[ 0 ].toLowerCase(); |
22 | String arg = temp.length > 1 ? temp[ 1 ] : null ; |
24 | if (LoadCommand.NAME.equals(cmd)) { |
26 | throw new CommandSyntaxException( "No tape number specified" ); |
29 | return new LoadCommand(Integer.parseInt(arg)); |
30 | } catch (NumberFormatException nfe) { |
31 | throw new CommandSyntaxException( "Illegal tape number: " + arg); |
33 | } else if (PlayCommand.NAME.equals(cmd)) { |
34 | return new PlayCommand(); |
35 | } else if (PauseCommand.NAME.equals(cmd)) { |
36 | return new PauseCommand(); |
37 | } else if (StopCommand.NAME.equals(cmd)) { |
38 | return new StopCommand(); |
39 | } else if (ListCommand.NAME.equals(cmd)) { |
40 | return new ListCommand(); |
41 | } else if (EjectCommand.NAME.equals(cmd)) { |
42 | return new EjectCommand(); |
43 | } else if (QuitCommand.NAME.equals(cmd)) { |
44 | return new QuitCommand(); |
45 | } else if (InfoCommand.NAME.equals(cmd)) { |
46 | return new InfoCommand(); |
49 | throw new CommandSyntaxException( "Unknown command: " + cmd); |
53 | public void decode(IoSession session, IoBuffer in, final ProtocolDecoderOutput out) |
56 | final LinkedList<String> lines = new LinkedList<String>(); |
57 | super .decode(session, in, new ProtocolDecoderOutput() { |
58 | public void write(Object message) { |
59 | lines.add((String) message); |
61 | public void flush(NextFilter nextFilter, IoSession session) {} |
64 | for (String s: lines) { |
65 | out.write(parseCommand(s)); |
处理异常类:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | import org.apache.mina.filter.codec.ProtocolDecoderException; |
06 | * Exception thrown by CommandDecoder when a line cannot be decoded as a Command |
10 | public class CommandSyntaxException extends ProtocolDecoderException { |
11 | private final String message; |
13 | public CommandSyntaxException(String message) { |
15 | this .message = message; |
19 | public String getMessage() { |
测试类:
01 | package com.a2.desktop.example10.mina.statemachine; |
03 | import java.net.InetSocketAddress; |
05 | import org.apache.mina.core.service.IoHandler; |
06 | import org.apache.mina.filter.codec.ProtocolCodecFilter; |
07 | import org.apache.mina.filter.codec.textline.TextLineEncoder; |
08 | import org.apache.mina.filter.logging.LoggingFilter; |
09 | import org.apache.mina.statemachine.StateMachine; |
10 | import org.apache.mina.statemachine.StateMachineFactory; |
11 | import org.apache.mina.statemachine.StateMachineProxyBuilder; |
12 | import org.apache.mina.statemachine.annotation.IoHandlerTransition; |
13 | import org.apache.mina.statemachine.context.IoSessionStateContextLookup; |
14 | import org.apache.mina.statemachine.context.StateContext; |
15 | import org.apache.mina.statemachine.context.StateContextFactory; |
16 | import org.apache.mina.transport.socket.SocketAcceptor; |
17 | import org.apache.mina.transport.socket.nio.NioSocketAcceptor; |
19 | public class TestMain { |
20 | private static final int PORT = 8082 ; |
22 | private static IoHandler createIoHandler() { |
23 | StateMachine sm = StateMachineFactory.getInstance( |
24 | IoHandlerTransition. class ).create(TapeDeckServer.EMPTY, |
25 | new TapeDeckServer()); |
27 | return new StateMachineProxyBuilder().setStateContextLookup( |
28 | new IoSessionStateContextLookup( new StateContextFactory() { |
29 | public StateContext create() { |
30 | return new TapeDeckServer.TapeDeckContext(); |
32 | })).create(IoHandler. class , sm); |
35 | public static void main(String[] args) throws Exception { |
36 | SocketAcceptor acceptor = new NioSocketAcceptor(); |
37 | acceptor.setReuseAddress( true ); |
38 | ProtocolCodecFilter pcf = new ProtocolCodecFilter( |
39 | new TextLineEncoder(), new CommandDecoder()); |
40 | acceptor.getFilterChain().addLast( "log1" , new LoggingFilter( "log1" )); |
41 | acceptor.getFilterChain().addLast( "codec" , pcf); |
42 | acceptor.getFilterChain().addLast( "log2" , new LoggingFilter( "log2" )); |
43 | acceptor.setHandler(createIoHandler()); |
44 | acceptor.bind( new InetSocketAddress(PORT)); |
启动测试类,用telnet去连,然后输入各种命令,效果如下:
更详细的代码可以参阅: org.apache.mina.example.tapedeck
代码其实都不难,只是我们需要灵活的将状态机这样的模式运用到自己的项目中去,通过状态之间的有规则的切换来控制更复杂的业务逻辑。