View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.statemachine;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.util.LinkedList;
26  
27  import org.apache.mina.statemachine.annotation.Transition;
28  import org.apache.mina.statemachine.annotation.Transitions;
29  import org.apache.mina.statemachine.annotation.OnEntry;
30  import org.apache.mina.statemachine.annotation.OnExit;
31  import org.apache.mina.statemachine.context.StateContext;
32  import org.apache.mina.statemachine.event.Event;
33  import org.apache.mina.statemachine.transition.MethodSelfTransition;
34  import org.apache.mina.statemachine.transition.MethodTransition;
35  import org.junit.Test;
36  
37  /**
38   * Tests {@link StateMachineProxyBuilder}.
39   *
40   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
41   */
42  public class StateMachineProxyBuilderTest {
43      @Test
44      public void testReentrantStateMachine() throws Exception {
45          ReentrantStateMachineHandler handler = new ReentrantStateMachineHandler();
46  
47          State s1 = new State("s1");
48          State s2 = new State("s2");
49          State s3 = new State("s3");
50  
51          s1.addTransition(new MethodTransition("call1", s2, handler));
52          s2.addTransition(new MethodTransition("call2", s3, handler));
53          s3.addTransition(new MethodTransition("call3", handler));
54  
55          StateMachine sm = new StateMachine(new State[] { s1, s2, s3 }, "s1");
56          Reentrant reentrant = new StateMachineProxyBuilder().create(Reentrant.class, sm);
57          reentrant.call1(reentrant);
58          assertTrue(handler.finished);
59      }
60  
61      @Test
62      public void testTapeDeckStateMachine() throws Exception {
63          TapeDeckStateMachineHandler handler = new TapeDeckStateMachineHandler();
64  
65          State parent = new State("parent");
66          State s1 = new State("s1", parent);
67          State s2 = new State("s2", parent);
68          State s3 = new State("s3", parent);
69          State s4 = new State("s4", parent);
70          State s5 = new State("s5", parent);
71  
72          parent.addTransition(new MethodTransition("*", "error", handler));
73          s1.addTransition(new MethodTransition("insert", s2, "inserted", handler));
74          s2.addTransition(new MethodTransition("start", s3, "playing", handler));
75          s3.addTransition(new MethodTransition("stop", s4, "stopped", handler));
76          s3.addTransition(new MethodTransition("pause", s5, "paused", handler));
77          s4.addTransition(new MethodTransition("eject", s1, "ejected", handler));
78          s5.addTransition(new MethodTransition("pause", s3, "playing", handler));
79  
80          s2.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS2", handler));
81          s2.addOnExitSelfTransaction(new MethodSelfTransition("onExitS2", handler));
82  
83          s3.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS3", handler));
84          s3.addOnExitSelfTransaction(new MethodSelfTransition("onExitS3", handler));
85  
86          s4.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS4", handler));
87          s4.addOnExitSelfTransaction(new MethodSelfTransition("onExitS4", handler));
88  
89          StateMachine sm = new StateMachine(new State[] { s1, s2, s3, s4, s5 }, "s1");
90          TapeDeck player = new StateMachineProxyBuilder().create(TapeDeck.class, sm);
91          player.insert("Kings of convenience - Riot on an empty street");
92          player.start();
93          player.pause();
94          player.pause();
95          player.eject();
96          player.stop();
97          player.eject();
98  
99          LinkedList<String> messages = handler.messages;
100         assertEquals("Tape 'Kings of convenience - Riot on an empty street' inserted", messages.removeFirst());
101         assertEquals("S2 entered", messages.removeFirst());
102         assertEquals("Playing", messages.removeFirst());
103         assertEquals("S2 exited", messages.removeFirst());
104         assertEquals("S3 entered with stateContext", messages.removeFirst());
105         assertEquals("Paused", messages.removeFirst());
106         assertEquals("S3 exited with stateContext", messages.removeFirst());
107         assertEquals("Playing", messages.removeFirst());
108         assertEquals("S3 entered with stateContext", messages.removeFirst());
109         assertEquals("Error: Cannot eject at this time", messages.removeFirst());
110         assertEquals("Stopped", messages.removeFirst());
111         assertEquals("S3 exited with stateContext", messages.removeFirst());
112         assertEquals("S4 entered with stateContext and state", messages.removeFirst());
113         assertEquals("Tape ejected", messages.removeFirst());
114         assertEquals("S4 exited with stateContext and state", messages.removeFirst());
115 
116         assertTrue(messages.isEmpty());
117     }
118 
119     @Test
120     public void testTapeDeckStateMachineAnnotations() throws Exception {
121         TapeDeckStateMachineHandler handler = new TapeDeckStateMachineHandler();
122 
123         StateMachine sm = StateMachineFactory.getInstance(Transition.class).create(TapeDeckStateMachineHandler.S1,
124                 handler);
125 
126         TapeDeck player = new StateMachineProxyBuilder().create(TapeDeck.class, sm);
127         player.insert("Kings of convenience - Riot on an empty street");
128         player.start();
129         player.pause();
130         player.pause();
131         player.eject();
132         player.stop();
133         player.eject();
134 
135         LinkedList<String> messages = handler.messages;
136         assertEquals("Tape 'Kings of convenience - Riot on an empty street' inserted", messages.removeFirst());
137         assertEquals("S2 entered", messages.removeFirst());
138         assertEquals("Playing", messages.removeFirst());
139         assertEquals("S2 exited", messages.removeFirst());
140         assertEquals("S3 entered with stateContext", messages.removeFirst());
141         assertEquals("Paused", messages.removeFirst());
142         assertEquals("S3 exited with stateContext", messages.removeFirst());
143         assertEquals("Playing", messages.removeFirst());
144         assertEquals("S3 entered with stateContext", messages.removeFirst());
145         assertEquals("Error: Cannot eject at this time", messages.removeFirst());
146         assertEquals("Stopped", messages.removeFirst());
147         assertEquals("S3 exited with stateContext", messages.removeFirst());
148         assertEquals("S4 entered with stateContext and state", messages.removeFirst());
149         assertEquals("Tape ejected", messages.removeFirst());
150         assertEquals("S4 exited with stateContext and state", messages.removeFirst());
151 
152         assertTrue(messages.isEmpty());
153     }
154 
155     public interface Reentrant {
156         void call1(Reentrant proxy);
157 
158         void call2(Reentrant proxy);
159 
160         void call3(Reentrant proxy);
161     }
162 
163     public static class ReentrantStateMachineHandler {
164         private boolean finished = false;
165 
166         public void call1(Reentrant proxy) {
167             proxy.call2(proxy);
168         }
169 
170         public void call2(Reentrant proxy) {
171             proxy.call3(proxy);
172         }
173 
174         public void call3(Reentrant proxy) {
175             finished = true;
176         }
177     }
178 
179     public interface TapeDeck {
180         void insert(String name);
181 
182         void eject();
183 
184         void start();
185 
186         void pause();
187 
188         void stop();
189     }
190 
191     public static class TapeDeckStateMachineHandler {
192         @org.apache.mina.statemachine.annotation.State
193         public static final String PARENT = "parent";
194 
195         @org.apache.mina.statemachine.annotation.State(PARENT)
196         public static final String S1 = "s1";
197 
198         @org.apache.mina.statemachine.annotation.State(PARENT)
199         public static final String S2 = "s2";
200 
201         @org.apache.mina.statemachine.annotation.State(PARENT)
202         public static final String S3 = "s3";
203 
204         @org.apache.mina.statemachine.annotation.State(PARENT)
205         public static final String S4 = "s4";
206 
207         @org.apache.mina.statemachine.annotation.State(PARENT)
208         public static final String S5 = "s5";
209 
210         private LinkedList<String> messages = new LinkedList<String>();
211 
212         @OnEntry(S2)
213         public void onEntryS2() {
214             messages.add("S2 entered");
215         }
216 
217         @OnExit(S2)
218         public void onExitS2() {
219             messages.add("S2 exited");
220         }
221 
222         @OnEntry(S3)
223         public void onEntryS3(StateContext stateContext) {
224             messages.add("S3 entered with stateContext");
225         }
226 
227         @OnExit(S3)
228         public void onExitS3(StateContext stateContext) {
229             messages.add("S3 exited with stateContext");
230         }
231 
232         @OnEntry(S4)
233         public void onEntryS4(StateContext stateContext, State state) {
234             messages.add("S4 entered with stateContext and state");
235         }
236 
237         @OnExit(S4)
238         public void onExitS4(StateContext stateContext, State state) {
239             messages.add("S4 exited with stateContext and state");
240         }
241 
242         @Transition(on = "insert", in = "s1", next = "s2")
243         public void inserted(String name) {
244             messages.add("Tape '" + name + "' inserted");
245         }
246 
247         @Transition(on = "eject", in = "s4", next = "s1")
248         public void ejected() {
249             messages.add("Tape ejected");
250         }
251 
252         @Transitions({ @Transition(on = "start", in = "s2", next = "s3"),
253                 @Transition(on = "pause", in = "s5", next = "s3") })
254         public void playing() {
255             messages.add("Playing");
256         }
257 
258         @Transition(on = "pause", in = "s3", next = "s5")
259         public void paused() {
260             messages.add("Paused");
261         }
262 
263         @Transition(on = "stop", in = "s3", next = "s4")
264         public void stopped() {
265             messages.add("Stopped");
266         }
267 
268         @Transition(on = "*", in = "parent")
269         public void error(Event event) {
270             messages.add("Error: Cannot " + event.getId() + " at this time");
271         }
272     }
273 }