001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.statemachine;
021
022import static org.junit.Assert.assertEquals;
023import static org.junit.Assert.assertTrue;
024
025import java.util.LinkedList;
026
027import org.apache.mina.statemachine.annotation.Transition;
028import org.apache.mina.statemachine.annotation.Transitions;
029import org.apache.mina.statemachine.annotation.OnEntry;
030import org.apache.mina.statemachine.annotation.OnExit;
031import org.apache.mina.statemachine.context.StateContext;
032import org.apache.mina.statemachine.event.Event;
033import org.apache.mina.statemachine.transition.MethodSelfTransition;
034import org.apache.mina.statemachine.transition.MethodTransition;
035import org.junit.Test;
036
037/**
038 * Tests {@link StateMachineProxyBuilder}.
039 *
040 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
041 */
042public class StateMachineProxyBuilderTest {
043    @Test
044    public void testReentrantStateMachine() throws Exception {
045        ReentrantStateMachineHandler handler = new ReentrantStateMachineHandler();
046
047        State s1 = new State("s1");
048        State s2 = new State("s2");
049        State s3 = new State("s3");
050
051        s1.addTransition(new MethodTransition("call1", s2, handler));
052        s2.addTransition(new MethodTransition("call2", s3, handler));
053        s3.addTransition(new MethodTransition("call3", handler));
054
055        StateMachine sm = new StateMachine(new State[] { s1, s2, s3 }, "s1");
056        Reentrant reentrant = new StateMachineProxyBuilder().create(Reentrant.class, sm);
057        reentrant.call1(reentrant);
058        assertTrue(handler.finished);
059    }
060
061    @Test
062    public void testTapeDeckStateMachine() throws Exception {
063        TapeDeckStateMachineHandler handler = new TapeDeckStateMachineHandler();
064
065        State parent = new State("parent");
066        State s1 = new State("s1", parent);
067        State s2 = new State("s2", parent);
068        State s3 = new State("s3", parent);
069        State s4 = new State("s4", parent);
070        State s5 = new State("s5", parent);
071
072        parent.addTransition(new MethodTransition("*", "error", handler));
073        s1.addTransition(new MethodTransition("insert", s2, "inserted", handler));
074        s2.addTransition(new MethodTransition("start", s3, "playing", handler));
075        s3.addTransition(new MethodTransition("stop", s4, "stopped", handler));
076        s3.addTransition(new MethodTransition("pause", s5, "paused", handler));
077        s4.addTransition(new MethodTransition("eject", s1, "ejected", handler));
078        s5.addTransition(new MethodTransition("pause", s3, "playing", handler));
079
080        s2.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS2", handler));
081        s2.addOnExitSelfTransaction(new MethodSelfTransition("onExitS2", handler));
082
083        s3.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS3", handler));
084        s3.addOnExitSelfTransaction(new MethodSelfTransition("onExitS3", handler));
085
086        s4.addOnEntrySelfTransaction(new MethodSelfTransition("onEntryS4", handler));
087        s4.addOnExitSelfTransaction(new MethodSelfTransition("onExitS4", handler));
088
089        StateMachine sm = new StateMachine(new State[] { s1, s2, s3, s4, s5 }, "s1");
090        TapeDeck player = new StateMachineProxyBuilder().create(TapeDeck.class, sm);
091        player.insert("Kings of convenience - Riot on an empty street");
092        player.start();
093        player.pause();
094        player.pause();
095        player.eject();
096        player.stop();
097        player.eject();
098
099        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}