├── .gitignore ├── framework ├── src │ ├── test │ │ ├── java │ │ │ └── com │ │ │ │ └── chiralbehaviors │ │ │ │ └── tron │ │ │ │ ├── examples │ │ │ │ ├── simpleProtocol │ │ │ │ │ ├── BufferHandler.java │ │ │ │ │ ├── impl │ │ │ │ │ │ └── SimpleProtocolImpl.java │ │ │ │ │ ├── SimpleProtocol.java │ │ │ │ │ ├── SimpleFsm.java │ │ │ │ │ └── stateMaps │ │ │ │ │ │ ├── SimpleServer.java │ │ │ │ │ │ ├── Simple.java │ │ │ │ │ │ └── SimpleClient.java │ │ │ │ ├── task │ │ │ │ │ ├── TaskModel.java │ │ │ │ │ ├── TaskFsm.java │ │ │ │ │ └── Task.java │ │ │ │ └── telephone │ │ │ │ │ ├── TelephoneFsm.java │ │ │ │ │ ├── Telephone.java │ │ │ │ │ ├── Call.java │ │ │ │ │ └── PhoneNumber.java │ │ │ │ ├── TestTask.java │ │ │ │ └── TestSimple.java │ │ └── resources │ │ │ └── logback-test.xml │ └── main │ │ └── java │ │ └── com │ │ └── chiralbehaviors │ │ └── tron │ │ ├── FsmExecutor.java │ │ ├── Exit.java │ │ ├── Entry.java │ │ ├── Default.java │ │ ├── InvalidTransition.java │ │ └── Fsm.java └── pom.xml ├── README.md ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | **/.classpath 3 | **/.project 4 | **/.settings 5 | **/src/site 6 | **/.classpath 7 | **/.project 8 | **/.settings 9 | **/src/site 10 | **/.DS_Store 11 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/BufferHandler.java: -------------------------------------------------------------------------------- 1 | package com.chiralbehaviors.tron.examples.simpleProtocol; 2 | 3 | public class BufferHandler { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /framework/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | System.err 7 | 8 | %-5level %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /framework/src/main/java/com/chiralbehaviors/tron/FsmExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause 6 | */ 7 | package com.chiralbehaviors.tron; 8 | 9 | /** 10 | * @author hal.hildebrand 11 | * 12 | */ 13 | public interface FsmExecutor { 14 | default Context context() { 15 | return Fsm.thisContext(); 16 | } 17 | 18 | default Fsm fsm() { 19 | return Fsm.thisFsm(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/impl/SimpleProtocolImpl.java: -------------------------------------------------------------------------------- 1 | package com.chiralbehaviors.tron.examples.simpleProtocol.impl; 2 | 3 | import com.chiralbehaviors.tron.examples.simpleProtocol.BufferHandler; 4 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleProtocol; 5 | 6 | public class SimpleProtocolImpl implements SimpleProtocol { 7 | private BufferHandler handler; 8 | 9 | @Override 10 | public void ackReceived() { 11 | } 12 | 13 | @Override 14 | public void awaitAck() { 15 | } 16 | 17 | @Override 18 | public void enableSend() { 19 | } 20 | 21 | @Override 22 | public void establishClientSession() { 23 | } 24 | 25 | public BufferHandler getHandler() { 26 | return handler; 27 | } 28 | 29 | @Override 30 | public void sendGoodbye() { 31 | } 32 | 33 | @Override 34 | public void setHandler(BufferHandler handler) { 35 | this.handler = handler; 36 | } 37 | 38 | @Override 39 | public void transmitMessage(String message) { 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/task/TaskModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.task; 17 | 18 | /** 19 | * 20 | * @author hhildebrand 21 | * 22 | */ 23 | public interface TaskModel { 24 | void blockTask(); 25 | 26 | void continueTask(); 27 | 28 | void releaseResources(); 29 | 30 | void startSliceTimer(long timeslice); 31 | 32 | void stopSliceTimer(); 33 | 34 | void stopTask(); 35 | 36 | void suspendTask(); 37 | } 38 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/SimpleProtocol.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.simpleProtocol; 17 | 18 | /** 19 | * 20 | * @author hhildebrand 21 | * 22 | */ 23 | public interface SimpleProtocol { 24 | void ackReceived(); 25 | 26 | void awaitAck(); 27 | 28 | void enableSend(); 29 | 30 | void establishClientSession(); 31 | 32 | void sendGoodbye(); 33 | 34 | void setHandler(BufferHandler handler); 35 | 36 | void transmitMessage(String message); 37 | } 38 | -------------------------------------------------------------------------------- /framework/src/main/java/com/chiralbehaviors/tron/Exit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | import static java.lang.annotation.ElementType.METHOD; 19 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 20 | 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * The annotation indicating the method is an exit action for the enclosing 26 | * state enumeration. 27 | * 28 | * @author hhildebrand 29 | * 30 | */ 31 | @Retention(value = RUNTIME) 32 | @Target(value = { METHOD }) 33 | public @interface Exit { 34 | } 35 | -------------------------------------------------------------------------------- /framework/src/main/java/com/chiralbehaviors/tron/Entry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | import static java.lang.annotation.ElementType.METHOD; 19 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 20 | 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * The annotation indicating the method is an entry action for the enclosing 26 | * state enumeration. 27 | * 28 | * @author hhildebrand 29 | * 30 | */ 31 | @Retention(value = RUNTIME) 32 | @Target(value = { METHOD }) 33 | public @interface Entry { 34 | } 35 | -------------------------------------------------------------------------------- /framework/src/main/java/com/chiralbehaviors/tron/Default.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | import static java.lang.annotation.ElementType.METHOD; 19 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 20 | 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | /** 25 | * The annotation marking a method as the default transition for the state. The 26 | * method must take no arguments 27 | * 28 | * @author hhildebrand 29 | * 30 | */ 31 | @Retention(value = RUNTIME) 32 | @Target(value = { METHOD }) 33 | public @interface Default { 34 | } 35 | -------------------------------------------------------------------------------- /framework/src/main/java/com/chiralbehaviors/tron/InvalidTransition.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | /** 19 | * The exception indicating that the transition is invalid for the current state 20 | * of the Fsm 21 | * 22 | * @author hhildebrand 23 | * 24 | */ 25 | public class InvalidTransition extends RuntimeException { 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | public InvalidTransition(String msg) { 30 | super(msg); 31 | } 32 | 33 | public InvalidTransition(String string, Throwable e) { 34 | super(string, e); 35 | } 36 | 37 | @SuppressWarnings("unused") 38 | private InvalidTransition() { 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tron 2 | ==== 3 | 4 | A simple framework for creating sophisticated Finite State Machines 5 | 6 | ==== 7 | 8 | See the [Tron wiki](https://github.com/ChiralBehaviors/Tron/wiki) for useage and design notes. 9 | 10 | Tron is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/). 11 | 12 | Tron is built using Maven 3.x and requires Java 7. 13 | 14 | To build, cd to the top level directory and do: 15 | 16 | mvn clean install 17 | 18 | For prebuilt versions of Tron, add the ChiralBehaviors cloudbees repositories to your project's pom.xml: 19 | 20 | For snapshots: 21 | 22 | 23 | chiralbehaviors-snapshots 24 | ChiralBehaviors Snapshots 25 | http://repository-chiralbehaviors.forge.cloudbees.com/snapshot/ 26 | 27 | true 28 | 29 | 30 | 31 | For releases: 32 | 33 | 34 | chiralbehaviors-releases 35 | ChiralBehaviors Releases 36 | http://repository-chiralbehaviors.forge.cloudbees.com/release/ 37 | 38 | 39 | The current version of Tron is 0.0.4. To use Tron in your project, add the following dependency in your project's pom.xml: 40 | 41 | 42 | 43 | com.chiralbehaviors 44 | tron 45 | 0.0.4 46 | 47 | 48 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/telephone/TelephoneFsm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.telephone; 17 | 18 | /** 19 | * 20 | * @author hhildebrand 21 | * 22 | */ 23 | public interface TelephoneFsm { 24 | TelephoneFsm clockTimer(); 25 | 26 | TelephoneFsm depositMoney(); 27 | 28 | TelephoneFsm dialingDone(int callType, String areaCode, String exchange, 29 | String local); 30 | 31 | TelephoneFsm digit(String digit); 32 | 33 | TelephoneFsm emergency(); 34 | 35 | TelephoneFsm invalidDigit(); 36 | 37 | TelephoneFsm invalidNumber(); 38 | 39 | TelephoneFsm leftOfHook(); 40 | 41 | TelephoneFsm lineBusy(); 42 | 43 | TelephoneFsm loopTimer(); 44 | 45 | TelephoneFsm nycTemp(); 46 | 47 | TelephoneFsm offHook(); 48 | 49 | TelephoneFsm offHookTimer(); 50 | 51 | TelephoneFsm onHook(); 52 | 53 | TelephoneFsm ringTimer(); 54 | 55 | TelephoneFsm time(); 56 | } 57 | -------------------------------------------------------------------------------- /framework/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | com.chiralbehaviors 7 | tron.app 8 | 0.0.5-SNAPSHOT 9 | 10 | tron 11 | Tron - A finite state machine framework 12 | A framework for creating finite state machines 13 | 14 | https://github.com/ChiralBehaviors/Tron 15 | 16 | 17 | 18 | Apache License, Version 2.0 19 | http://www.apache.org/licenses/LICENSE-2.0 20 | repo 21 | 22 | 23 | 24 | 25 | https://github.com/ChiralBehaviors/Tron.git 26 | 27 | 28 | 29 | 30 | org.slf4j 31 | slf4j-api 32 | 1.7.5 33 | 34 | 35 | 36 | org.junit.jupiter 37 | junit-jupiter 38 | 5.6.2 39 | test 40 | 41 | 42 | org.mockito 43 | mockito-all 44 | 2.0.2-beta 45 | test 46 | 47 | 48 | ch.qos.logback 49 | logback-classic 50 | 1.2.3 51 | test 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/task/TaskFsm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.task; 17 | 18 | import com.chiralbehaviors.tron.FsmExecutor; 19 | 20 | /** 21 | * 22 | * @author hhildebrand 23 | * 24 | */ 25 | public interface TaskFsm extends FsmExecutor { 26 | default TaskFsm block() { 27 | return null; // loopback transition 28 | } 29 | 30 | default TaskFsm delete() { 31 | return null; // loopback transition 32 | } 33 | 34 | default TaskFsm done() { 35 | return null; // loopback transition 36 | } 37 | 38 | default TaskFsm start(long timeslice) { 39 | return null; // loopback transition 40 | } 41 | 42 | default TaskFsm stop() { 43 | return null; // loopback transition 44 | } 45 | 46 | default TaskFsm stopped() { 47 | return null; // loopback transition 48 | } 49 | 50 | default TaskFsm suspended() { 51 | return null; // loopback transition 52 | } 53 | 54 | default TaskFsm unblock() { 55 | return null; // loopback transition 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/SimpleFsm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.simpleProtocol; 17 | 18 | import com.chiralbehaviors.tron.FsmExecutor; 19 | 20 | /** 21 | * 22 | * @author hhildebrand 23 | * 24 | */ 25 | public interface SimpleFsm extends FsmExecutor { 26 | default SimpleFsm accepted(BufferHandler buffer) { 27 | return protocolError(); 28 | } 29 | 30 | default SimpleFsm closing() { 31 | return protocolError(); 32 | } 33 | 34 | default SimpleFsm connected(BufferHandler buffer) { 35 | return protocolError(); 36 | } 37 | 38 | default SimpleFsm protocolError() { 39 | throw fsm().invalidTransitionOn(); 40 | } 41 | 42 | default SimpleFsm readError() { 43 | return protocolError(); 44 | } 45 | 46 | default SimpleFsm readReady() { 47 | return protocolError(); 48 | } 49 | 50 | default SimpleFsm sendGoodbye() { 51 | return protocolError(); 52 | } 53 | 54 | default SimpleFsm transmitMessage(String message) { 55 | return protocolError(); 56 | } 57 | 58 | default SimpleFsm writeError() { 59 | return protocolError(); 60 | } 61 | 62 | default SimpleFsm writeReady() { 63 | return protocolError(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/telephone/Telephone.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.telephone; 17 | 18 | /** 19 | * 20 | * @author hhildebrand 21 | * 22 | */ 23 | public interface Telephone { 24 | public enum CallType { 25 | EMERGENCY, LOCAL, LONG_DISTANCE; 26 | } 27 | 28 | String LONG_DISTANCE = null; 29 | 30 | void addDisplay(String string); 31 | 32 | void clearDisplay(); 33 | 34 | String getAreaCode(); 35 | 36 | String getExchange(); 37 | 38 | String getLocal(); 39 | 40 | int getType(); 41 | 42 | void loop(String string); 43 | 44 | void playDepositMoney(); 45 | 46 | void playEmergency(); 47 | 48 | void playInvalidNumber(); 49 | 50 | void playNYCTemp(); 51 | 52 | void playTime(); 53 | 54 | void playTT(int d); 55 | 56 | void resetTimer(String string); 57 | 58 | void routeCall(int callType, String areaCode, String exchange, String local); 59 | 60 | void saveAreaCode(int d); 61 | 62 | void saveExchange(int d); 63 | 64 | void saveLocal(int d); 65 | 66 | void setReceiver(String string, String string2); 67 | 68 | void setType(CallType longDistance); 69 | 70 | void startClockTimer(); 71 | 72 | void startTimer(String string, int i); 73 | 74 | void stopLoop(String string); 75 | 76 | void stopPlayback(); 77 | 78 | void stopTimer(String timer); 79 | 80 | void updateClock(); 81 | } 82 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/stateMaps/SimpleServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.simpleProtocol.stateMaps; 17 | 18 | import com.chiralbehaviors.tron.examples.simpleProtocol.BufferHandler; 19 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleFsm; 20 | 21 | /** 22 | * 23 | * @author hhildebrand 24 | * 25 | */ 26 | public enum SimpleServer implements SimpleFsm { 27 | ACCEPTED, AWAIT_MESSAGE, PROCESS_MESSAGE, SESSION_ESTABLISHED, ; 28 | 29 | @Override 30 | public SimpleFsm accepted(BufferHandler buffer) { 31 | // TODO Auto-generated method stub 32 | return null; 33 | } 34 | 35 | @Override 36 | public SimpleFsm closing() { 37 | // TODO Auto-generated method stub 38 | return null; 39 | } 40 | 41 | @Override 42 | public SimpleFsm connected(BufferHandler buffer) { 43 | // TODO Auto-generated method stub 44 | return null; 45 | } 46 | 47 | @Override 48 | public SimpleFsm protocolError() { 49 | // TODO Auto-generated method stub 50 | return null; 51 | } 52 | 53 | @Override 54 | public SimpleFsm readError() { 55 | // TODO Auto-generated method stub 56 | return null; 57 | } 58 | 59 | @Override 60 | public SimpleFsm readReady() { 61 | // TODO Auto-generated method stub 62 | return null; 63 | } 64 | 65 | @Override 66 | public SimpleFsm sendGoodbye() { 67 | // TODO Auto-generated method stub 68 | return null; 69 | } 70 | 71 | @Override 72 | public SimpleFsm transmitMessage(String message) { 73 | // TODO Auto-generated method stub 74 | return null; 75 | } 76 | 77 | @Override 78 | public SimpleFsm writeError() { 79 | // TODO Auto-generated method stub 80 | return null; 81 | } 82 | 83 | @Override 84 | public SimpleFsm writeReady() { 85 | // TODO Auto-generated method stub 86 | return null; 87 | } 88 | } -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/TestTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.verify; 21 | 22 | import org.junit.jupiter.api.Test; 23 | import org.mockito.internal.verification.Times; 24 | 25 | import com.chiralbehaviors.tron.examples.task.Task; 26 | import com.chiralbehaviors.tron.examples.task.TaskFsm; 27 | import com.chiralbehaviors.tron.examples.task.TaskModel; 28 | 29 | /** 30 | * 31 | * @author hhildebrand 32 | * 33 | */ 34 | public class TestTask { 35 | @Test 36 | public void testIt() { 37 | long timeslice = 100; 38 | TaskModel model = mock(TaskModel.class); 39 | Fsm fsm = Fsm.construct(model, TaskFsm.class, Task.Suspended, false); 40 | TaskFsm transitions = fsm.getTransitions(); 41 | assertEquals(Task.Suspended, fsm.getCurrentState()); 42 | transitions.start(timeslice); 43 | verify(model).continueTask(); 44 | verify(model).startSliceTimer(timeslice); 45 | assertEquals(Task.Running, fsm.getCurrentState()); 46 | transitions.suspended(); 47 | assertEquals(Task.Suspended, fsm.getCurrentState()); 48 | verify(model).stopSliceTimer(); 49 | transitions.start(timeslice); 50 | verify(model, new Times(2)).startSliceTimer(timeslice); 51 | transitions.block(); 52 | assertEquals(Task.Blocked, fsm.getCurrentState()); 53 | verify(model, new Times(2)).stopSliceTimer(); 54 | transitions.unblock(); 55 | assertEquals(Task.Suspended, fsm.getCurrentState()); 56 | transitions.start(timeslice); 57 | verify(model, new Times(3)).startSliceTimer(timeslice); 58 | transitions.stop(); 59 | assertEquals(Task.Stopping, fsm.getCurrentState()); 60 | verify(model, new Times(3)).stopSliceTimer(); 61 | transitions.stopped(); 62 | assertEquals(Task.Stopped, fsm.getCurrentState()); 63 | transitions.delete(); 64 | assertEquals(Task.Deleted, fsm.getCurrentState()); 65 | transitions.delete(); 66 | assertEquals(Task.Deleted, fsm.getCurrentState()); 67 | transitions.stop(); 68 | assertEquals(Task.Deleted, fsm.getCurrentState()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/TestSimple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertNotNull; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | import com.chiralbehaviors.tron.examples.simpleProtocol.BufferHandler; 24 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleFsm; 25 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleProtocol; 26 | import com.chiralbehaviors.tron.examples.simpleProtocol.impl.SimpleProtocolImpl; 27 | import com.chiralbehaviors.tron.examples.simpleProtocol.stateMaps.Simple; 28 | import com.chiralbehaviors.tron.examples.simpleProtocol.stateMaps.SimpleClient; 29 | 30 | /** 31 | * 32 | * @author hhildebrand 33 | * 34 | */ 35 | public class TestSimple { 36 | @Test 37 | public void testIt() { 38 | SimpleProtocol protocol = new SimpleProtocolImpl(); 39 | Fsm fsm = Fsm.construct(protocol, SimpleFsm.class, Simple.INITIAL, true); 40 | verifyFsmStates(fsm, protocol); 41 | } 42 | 43 | @Test 44 | public void testItWithCustomClassLoader() { 45 | SimpleProtocol protocol = new SimpleProtocolImpl(); 46 | Fsm fsm = Fsm.construct(protocol, SimpleFsm.class, SimpleFsm.class.getClassLoader(), 47 | Simple.INITIAL, true); 48 | verifyFsmStates(fsm, protocol); 49 | } 50 | 51 | private void verifyFsmStates(Fsm fsm, SimpleProtocol protocol) { 52 | assertNotNull(fsm); 53 | BufferHandler handler = new BufferHandler(); 54 | fsm.getTransitions().connected(handler); 55 | assertEquals(handler, ((SimpleProtocolImpl) protocol).getHandler()); 56 | assertEquals(SimpleClient.CONNECTED, fsm.getCurrentState()); 57 | fsm.getTransitions().writeReady(); 58 | assertEquals(SimpleClient.ESTABLISH_SESSION, fsm.getCurrentState()); 59 | fsm.getTransitions().readReady(); 60 | assertEquals(SimpleClient.SEND_MESSAGE, fsm.getCurrentState()); 61 | fsm.getTransitions().sendGoodbye(); 62 | assertEquals(SimpleClient.SEND_GOODBYE, fsm.getCurrentState()); 63 | fsm.getTransitions().readReady(); 64 | assertEquals(Simple.CLOSED, fsm.getCurrentState()); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/stateMaps/Simple.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.simpleProtocol.stateMaps; 17 | 18 | import com.chiralbehaviors.tron.examples.simpleProtocol.BufferHandler; 19 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleFsm; 20 | 21 | /** 22 | * 23 | * @author hhildebrand 24 | * 25 | */ 26 | public enum Simple implements SimpleFsm { 27 | CLOSED, CONNECTED() { 28 | @Override 29 | public SimpleFsm closing() { 30 | return CLOSED; 31 | } 32 | 33 | @Override 34 | public SimpleFsm readError() { 35 | return CLOSED; 36 | } 37 | 38 | @Override 39 | public SimpleFsm writeError() { 40 | return CLOSED; 41 | } 42 | }, 43 | INITIAL() { 44 | @Override 45 | public SimpleFsm accepted(BufferHandler handler) { 46 | context().setHandler(handler); 47 | fsm().push(SimpleServer.ACCEPTED); 48 | return CONNECTED; 49 | } 50 | 51 | @Override 52 | public SimpleFsm connected(BufferHandler handler) { 53 | context().setHandler(handler); 54 | fsm().push(SimpleClient.CONNECTED); 55 | return CONNECTED; 56 | } 57 | }, 58 | PROTOCOL_ERROR() { 59 | 60 | }; 61 | 62 | @Override 63 | public SimpleFsm accepted(BufferHandler buffer) { 64 | return PROTOCOL_ERROR; 65 | } 66 | 67 | @Override 68 | public SimpleFsm closing() { 69 | return CLOSED; 70 | } 71 | 72 | @Override 73 | public SimpleFsm connected(BufferHandler buffer) { 74 | return PROTOCOL_ERROR; 75 | } 76 | 77 | @Override 78 | public SimpleFsm protocolError() { 79 | return PROTOCOL_ERROR; 80 | } 81 | 82 | @Override 83 | public SimpleFsm readError() { 84 | return CLOSED; 85 | } 86 | 87 | @Override 88 | public SimpleFsm readReady() { 89 | return CLOSED; 90 | } 91 | 92 | @Override 93 | public SimpleFsm sendGoodbye() { 94 | return PROTOCOL_ERROR; 95 | } 96 | 97 | @Override 98 | public SimpleFsm transmitMessage(String message) { 99 | return PROTOCOL_ERROR; 100 | } 101 | 102 | @Override 103 | public SimpleFsm writeError() { 104 | return CLOSED; 105 | } 106 | 107 | @Override 108 | public SimpleFsm writeReady() { 109 | return PROTOCOL_ERROR; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | com.chiralbehaviors 6 | tron.app 7 | 0.0.5-SNAPSHOT 8 | pom 9 | Tron - Top level app 10 | A framework for creating finite state machines 11 | 12 | UTF-8 13 | 14 | 15 | https://github.com/Hellblazer/Tron 16 | 17 | 18 | 19 | Apache License, Version 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | repo 22 | 23 | 24 | 25 | 26 | https://github.com/Hellblazer/Tron.git 27 | 28 | 29 | 30 | framework 31 | 32 | 33 | 34 | 35 | 36 | ${project.groupId} 37 | tron 38 | ${project.version} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-compiler-plugin 48 | 3.7.0 49 | 50 | 11 51 | 52 | 53 | org.ow2.asm 54 | asm 55 | 6.2 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-source-plugin 63 | 3.2.0 64 | 65 | false 66 | 67 | 68 | 69 | 70 | 71 | 73 | 74 | org.eclipse.m2e 75 | lifecycle-mapping 76 | 1.0.0 77 | 78 | 79 | 80 | 81 | 82 | 83 | org.apache.felix 84 | 85 | 86 | maven-bundle-plugin 87 | 88 | 89 | [2.4.0,) 90 | 91 | 92 | manifest 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/simpleProtocol/stateMaps/SimpleClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.simpleProtocol.stateMaps; 17 | 18 | import com.chiralbehaviors.tron.Entry; 19 | import com.chiralbehaviors.tron.examples.simpleProtocol.BufferHandler; 20 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleFsm; 21 | import com.chiralbehaviors.tron.examples.simpleProtocol.SimpleProtocol; 22 | 23 | /** 24 | * 25 | * @author hhildebrand 26 | * 27 | */ 28 | public enum SimpleClient implements SimpleFsm { 29 | ACK_MESSAGE() { 30 | @Entry 31 | public void entry() { 32 | context().ackReceived(); 33 | } 34 | }, 35 | AWAIT_ACK() { 36 | @Override 37 | public SimpleFsm readReady() { 38 | return ACK_MESSAGE; 39 | } 40 | }, 41 | CONNECTED() { 42 | // Context injection into Entry action 43 | @Entry 44 | public void establishClientSession(SimpleProtocol context) { 45 | context.establishClientSession(); 46 | } 47 | 48 | @Override 49 | public SimpleFsm writeReady() { 50 | return ESTABLISH_SESSION; 51 | } 52 | }, 53 | ESTABLISH_SESSION() { 54 | @Entry 55 | public void entry() { 56 | context().awaitAck(); 57 | } 58 | 59 | @Override 60 | public SimpleFsm readReady() { 61 | context().enableSend(); 62 | return SEND_MESSAGE; 63 | } 64 | }, 65 | MessageSent() { 66 | @Entry 67 | public void entry() { 68 | context().awaitAck(); 69 | } 70 | 71 | @Override 72 | public SimpleFsm writeReady() { 73 | return AWAIT_ACK; 74 | } 75 | }, 76 | SEND_GOODBYE { 77 | @Entry 78 | public void entry() { 79 | context().sendGoodbye(); 80 | } 81 | 82 | @Override 83 | public SimpleFsm readReady() { 84 | SimpleFsm popTransition = fsm().pop(); 85 | popTransition.closing(); 86 | return null; 87 | } 88 | }, 89 | SEND_MESSAGE() { 90 | @Override 91 | public SimpleFsm sendGoodbye() { 92 | return SEND_GOODBYE; 93 | } 94 | 95 | @Override 96 | public SimpleFsm transmitMessage(String message) { 97 | context().transmitMessage(message); 98 | return MessageSent; 99 | } 100 | }; 101 | 102 | @Override 103 | public SimpleFsm accepted(BufferHandler buffer) { 104 | return null; 105 | } 106 | 107 | @Override 108 | public SimpleFsm closing() { 109 | SimpleFsm popTransition = fsm().pop(); 110 | popTransition.closing(); 111 | return null; 112 | } 113 | 114 | @Override 115 | public SimpleFsm protocolError() { 116 | SimpleFsm popTransition = fsm().pop(); 117 | popTransition.protocolError(); 118 | return null; 119 | } 120 | 121 | @Override 122 | public SimpleFsm sendGoodbye() { 123 | throw fsm().invalidTransitionOn(); 124 | } 125 | 126 | @Override 127 | public SimpleFsm transmitMessage(String message) { 128 | throw fsm().invalidTransitionOn(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/task/Task.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.chiralbehaviors.tron.examples.task; 18 | 19 | import com.chiralbehaviors.tron.Exit; 20 | 21 | /** 22 | * 23 | * @author hhildebrand 24 | * 25 | */ 26 | public enum Task implements TaskFsm { 27 | /** 28 | * the uncompleted task is externally prevented from running again. It will stay 29 | * in this state until either stopped or unblocked. 30 | */ 31 | Blocked() { 32 | /** 33 | * The task may continue working now. No actions needed. 34 | */ 35 | @Override 36 | public TaskFsm unblock() { 37 | return Suspended; 38 | } 39 | 40 | }, 41 | /** 42 | * the task is completely stopped and all associated resources returned. The 43 | * task may now be safely deleted. This is the FSM end state. 44 | */ 45 | Deleted() { 46 | 47 | @Override 48 | public TaskFsm delete() { 49 | return null; 50 | } 51 | 52 | @Override 53 | public TaskFsm stop() { 54 | return null; 55 | } 56 | }, 57 | /** 58 | * the task is actively doing work. The task is allowed to run for a specified 59 | * time limit. 60 | */ 61 | Running() { 62 | @Override 63 | public TaskFsm block() { 64 | TaskModel context = context(); 65 | context.blockTask(); 66 | return Blocked; 67 | } 68 | 69 | @Override 70 | public TaskFsm done() { 71 | TaskModel context = context(); 72 | context.releaseResources(); 73 | return Stopped; 74 | } 75 | 76 | @Exit 77 | public void exit() { 78 | context().stopSliceTimer(); 79 | } 80 | 81 | /** 82 | * Wait for another time slice. 83 | */ 84 | @Override 85 | public TaskFsm suspended() { 86 | TaskModel context = context(); 87 | context.suspendTask(); 88 | return Suspended; 89 | } 90 | }, 91 | /** 92 | * the task has either completed running or externally stopped. 93 | */ 94 | Stopped() { 95 | 96 | @Override 97 | public TaskFsm stop() { 98 | return null; 99 | } 100 | 101 | }, 102 | /** 103 | * the task is cleaning up allocated resources before entering the stop state. 104 | */ 105 | Stopping { 106 | 107 | @Override 108 | public TaskFsm stop() { 109 | return null; 110 | } 111 | 112 | @Override 113 | public TaskFsm stopped() { 114 | context().releaseResources(); 115 | return Stopped; 116 | } 117 | 118 | }, 119 | /** 120 | * the task is waiting to run again since it is not yet completed. 121 | */ 122 | Suspended() { 123 | @Override 124 | public TaskFsm block() { 125 | context().blockTask(); 126 | return Blocked; 127 | } 128 | 129 | /** 130 | * Time to do more work. The timeslice duration is passed in as a transition 131 | * argument. 132 | */ 133 | @Override 134 | public TaskFsm start(long timeslice) { 135 | TaskModel context = context(); 136 | context.continueTask(); 137 | context.startSliceTimer(timeslice); 138 | return Running; 139 | } 140 | 141 | }; 142 | 143 | /** 144 | * All but the Delete state follow this transition. Define it here. 145 | */ 146 | @Override 147 | public TaskFsm delete() { 148 | return Deleted; 149 | } 150 | 151 | /** 152 | * Three states follow this transition, three states ignore. So define the 153 | * active definition. 154 | */ 155 | @Override 156 | public TaskFsm stop() { 157 | context().stopTask(); 158 | return Stopping; 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/telephone/Call.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.telephone; 17 | 18 | import com.chiralbehaviors.tron.Default; 19 | import com.chiralbehaviors.tron.Entry; 20 | import com.chiralbehaviors.tron.Exit; 21 | import com.chiralbehaviors.tron.Fsm; 22 | 23 | /** 24 | * 25 | * @author hhildebrand 26 | * 27 | */ 28 | public enum Call implements TelephoneFsm { 29 | BusySignal() { 30 | @Entry 31 | public void entry() { 32 | context().loop("busy"); 33 | } 34 | 35 | @Exit 36 | public void exit() { 37 | context().stopLoop("busy"); 38 | } 39 | }, 40 | DepositMoney() { 41 | @Entry 42 | public void entry() { 43 | Telephone context = context(); 44 | context.loop("ringing"); 45 | context.startTimer("RingTimer", 5000); 46 | } 47 | 48 | @Exit 49 | public void exit() { 50 | Telephone context = context(); 51 | context.stopTimer("RingTimer"); 52 | context.stopLoop("ringing"); 53 | } 54 | 55 | @Override 56 | public TelephoneFsm ringTimer() { 57 | context().playDepositMoney(); 58 | return PlayingMessage; 59 | } 60 | }, 61 | /** 62 | * The number is being dialed. 63 | */ 64 | Dialing() { 65 | 66 | @Override 67 | public TelephoneFsm dialingDone(int callType, String areaCode, String exchange, String local) { 68 | context().routeCall(callType, areaCode, exchange, local); 69 | return Routing; 70 | } 71 | 72 | @Override 73 | public TelephoneFsm invalidDigit() { 74 | return InvalidDigit; 75 | } 76 | 77 | @Override 78 | public TelephoneFsm leftOfHook() { 79 | return LeftOffHook; 80 | } 81 | 82 | }, 83 | InvalidDigit() { 84 | @Default 85 | public TelephoneFsm defaultTransition() { 86 | return null; 87 | } 88 | 89 | @Entry 90 | public void entry() { 91 | Telephone context = context(); 92 | context.startTimer("LoopTimer", 10000); 93 | context.loop("fast_busy"); 94 | } 95 | 96 | @Exit 97 | public void exit() { 98 | Telephone context = context(); 99 | context.stopTimer("LoopTimer"); 100 | context.stopLoop("fast_busy"); 101 | } 102 | 103 | @Override 104 | public TelephoneFsm loopTimer() { 105 | return WaitForOnHook; 106 | } 107 | }, 108 | LeftOffHook() { 109 | @Default 110 | public TelephoneFsm defaultTransition() { 111 | return null; 112 | } 113 | 114 | @Entry 115 | public void entry() { 116 | Telephone context = context(); 117 | context.startTimer("LoopTimer", 10000); 118 | context.loop("phone_off_hook"); 119 | } 120 | 121 | @Exit 122 | public void exit() { 123 | Telephone context = context(); 124 | context.stopTimer("LoopTimer"); 125 | context.stopLoop("phone_off_hook"); 126 | } 127 | 128 | @Override 129 | public TelephoneFsm loopTimer() { 130 | return WaitForOnHook; 131 | } 132 | }, 133 | MessagePlayed() { 134 | @Entry 135 | public void entry() { 136 | context().startTimer("OffHookTimer", 10000); 137 | } 138 | 139 | @Exit 140 | public void exit() { 141 | context().stopTimer("OffHookTimer"); 142 | } 143 | 144 | @Override 145 | public TelephoneFsm offHookTimer() { 146 | return LeftOffHook; 147 | } 148 | }, 149 | NYCTemp() { 150 | @Entry 151 | public void entry() { 152 | Telephone context = context(); 153 | context.loop("ringing"); 154 | context.startTimer("RingTimer", 10000); 155 | } 156 | 157 | @Exit 158 | public void exit() { 159 | Telephone context = context(); 160 | context.stopTimer("RingTimer"); 161 | context.stopLoop("ringing"); 162 | } 163 | 164 | @Override 165 | public TelephoneFsm ringTimer() { 166 | context().playNYCTemp(); 167 | return PlayingMessage; 168 | } 169 | }, 170 | OnHook() { 171 | /** 172 | * Time to update the clock's display. 173 | */ 174 | @Override 175 | public TelephoneFsm clockTimer() { 176 | Telephone context = context(); 177 | context.updateClock(); 178 | context.startClockTimer(); 179 | return null; 180 | } 181 | 182 | @Entry 183 | public void entry() { 184 | Telephone context = context(); 185 | context.updateClock(); 186 | context.startClockTimer(); 187 | } 188 | 189 | @Exit 190 | public void exit() { 191 | context().stopTimer("ClockTimer"); 192 | } 193 | 194 | /** 195 | * We are handling the caller's side of the connection. 196 | */ 197 | @Override 198 | public TelephoneFsm offHook() { 199 | fsm().push(PhoneNumber.DialTone); 200 | Telephone context = context(); 201 | context.clearDisplay(); 202 | context.setReceiver("on hook", "Put down receiver"); 203 | return Dialing; 204 | } 205 | 206 | /** 207 | * Oops. 208 | */ 209 | @Override 210 | public TelephoneFsm onHook() { 211 | return null; 212 | } 213 | }, 214 | PlayingMessage() { 215 | @Override 216 | public TelephoneFsm onHook() { 217 | Telephone context = context(); 218 | context.stopPlayback(); 219 | context.setReceiver("off hook", "Pick up receiver"); 220 | context.clearDisplay(); 221 | return OnHook; 222 | } 223 | }, 224 | /** 225 | * The call is now being routed. 226 | */ 227 | Routing() { 228 | @Override 229 | public TelephoneFsm depositMoney() { 230 | return DepositMoney; 231 | } 232 | 233 | @Override 234 | public TelephoneFsm emergency() { 235 | context().playEmergency(); 236 | return PlayingMessage; 237 | } 238 | 239 | @Override 240 | public TelephoneFsm invalidNumber() { 241 | context().playInvalidNumber(); 242 | return PlayingMessage; 243 | } 244 | 245 | @Override 246 | public TelephoneFsm lineBusy() { 247 | return BusySignal; 248 | } 249 | 250 | @Override 251 | public TelephoneFsm nycTemp() { 252 | return NYCTemp; 253 | } 254 | 255 | @Override 256 | public TelephoneFsm time() { 257 | return Time; 258 | } 259 | }, 260 | Time() { 261 | @Entry 262 | public void entry() { 263 | Telephone context = context(); 264 | context.loop("ringing"); 265 | context.startTimer("RingTimer", 10000); 266 | } 267 | 268 | @Exit 269 | public void exit() { 270 | Telephone context = context(); 271 | context.stopTimer("RingTimer"); 272 | context.stopLoop("ringing"); 273 | } 274 | 275 | @Override 276 | public TelephoneFsm ringTimer() { 277 | context().playTime(); 278 | return PlayingMessage; 279 | } 280 | }, 281 | WaitForOnHook() { 282 | @Default 283 | public TelephoneFsm defaultTransition() { 284 | return null; 285 | } 286 | }; 287 | 288 | private static Telephone context() { 289 | Telephone context = Fsm.thisContext(); 290 | return context; 291 | } 292 | 293 | private static Fsm fsm() { 294 | Fsm fsm = Fsm.thisFsm(); 295 | return fsm; 296 | } 297 | 298 | @Override 299 | public TelephoneFsm clockTimer() { 300 | return null; 301 | } 302 | 303 | @Override 304 | public TelephoneFsm depositMoney() { 305 | throw fsm().invalidTransitionOn(); 306 | } 307 | 308 | @Override 309 | public TelephoneFsm dialingDone(int callType, String areaCode, String exchange, String local) { 310 | throw null; 311 | } 312 | 313 | @Override 314 | public TelephoneFsm digit(String digit) { 315 | return null; 316 | } 317 | 318 | @Override 319 | public TelephoneFsm emergency() { 320 | throw fsm().invalidTransitionOn(); 321 | } 322 | 323 | @Override 324 | public TelephoneFsm invalidDigit() { 325 | throw null; 326 | } 327 | 328 | @Override 329 | public TelephoneFsm invalidNumber() { 330 | throw fsm().invalidTransitionOn(); 331 | } 332 | 333 | @Override 334 | public TelephoneFsm leftOfHook() { 335 | throw fsm().invalidTransitionOn(); 336 | } 337 | 338 | @Override 339 | public TelephoneFsm lineBusy() { 340 | throw fsm().invalidTransitionOn(); 341 | } 342 | 343 | @Override 344 | public TelephoneFsm loopTimer() { 345 | throw fsm().invalidTransitionOn(); 346 | } 347 | 348 | @Override 349 | public TelephoneFsm nycTemp() { 350 | throw fsm().invalidTransitionOn(); 351 | } 352 | 353 | @Override 354 | public TelephoneFsm offHook() { 355 | throw fsm().invalidTransitionOn(); 356 | } 357 | 358 | @Override 359 | public TelephoneFsm offHookTimer() { 360 | throw fsm().invalidTransitionOn(); 361 | } 362 | 363 | /** 364 | * No matter when it happens, when the phone is hung up, this call is OVER! 365 | */ 366 | @Override 367 | public TelephoneFsm onHook() { 368 | Telephone context = context(); 369 | context.setReceiver("off hook", "Pick up receiver"); 370 | context.clearDisplay(); 371 | return null; 372 | } 373 | 374 | @Override 375 | public TelephoneFsm ringTimer() { 376 | throw fsm().invalidTransitionOn(); 377 | } 378 | 379 | @Override 380 | public TelephoneFsm time() { 381 | throw fsm().invalidTransitionOn(); 382 | } 383 | } 384 | -------------------------------------------------------------------------------- /framework/src/test/java/com/chiralbehaviors/tron/examples/telephone/PhoneNumber.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron.examples.telephone; 17 | 18 | import com.chiralbehaviors.tron.Entry; 19 | import com.chiralbehaviors.tron.Exit; 20 | import com.chiralbehaviors.tron.Fsm; 21 | 22 | /** 23 | * 24 | * @author hhildebrand 25 | * 26 | */ 27 | public enum PhoneNumber implements TelephoneFsm { 28 | DialTone() { 29 | /** 30 | * If an invalid digit is dialed, give up collecting digits immediately. 31 | */ 32 | @Override 33 | public TelephoneFsm digit(String digit) { 34 | int d = Integer.parseInt(digit); 35 | if (d < 0 || d > 9) { 36 | fsm().pop().invalidDigit(); 37 | context().clearDisplay(); 38 | return null; 39 | } else if (d == 1) { 40 | Telephone context = context(); 41 | context.playTT(d); 42 | context.setType(Telephone.CallType.LONG_DISTANCE); 43 | context.saveAreaCode(d); 44 | context.addDisplay("-"); 45 | return LongDistance; 46 | } else if (d == 9) { 47 | Telephone context = context(); 48 | context.playTT(d); 49 | context.saveExchange(d); 50 | return OneOneStart; 51 | } else { 52 | Telephone context = context(); 53 | context.playTT(d); 54 | context.setType(Telephone.CallType.LOCAL); 55 | context.saveExchange(d); 56 | return Exchange; 57 | } 58 | } 59 | 60 | @Entry 61 | public void entry() { 62 | Telephone context = context(); 63 | context.loop("dialtone"); 64 | context.startTimer("OffHookTimer", 10000); 65 | } 66 | 67 | @Exit 68 | public void exit() { 69 | Telephone context = context(); 70 | context.stopTimer("OffHookTimer"); 71 | context.stopLoop("dialtone"); 72 | } 73 | }, 74 | Exchange() { 75 | @Override 76 | public TelephoneFsm digit(String digit) { 77 | int d = Integer.parseInt(digit); 78 | Telephone context = context(); 79 | if (d < 0 || d > 9) { 80 | fsm().pop().invalidDigit(); 81 | context().clearDisplay(); 82 | return null; 83 | } else if (context.getExchange().length() < 2) { 84 | context.playTT(d); 85 | context.saveExchange(d); 86 | context.resetTimer("OffHookTimer"); 87 | return null; 88 | } else { 89 | context.playTT(d); 90 | context.saveExchange(d); 91 | context.addDisplay("-"); 92 | return LocalCall; 93 | } 94 | } 95 | 96 | @Entry 97 | public void entry() { 98 | context().startTimer("OffHookTimer", 10000); 99 | } 100 | 101 | @Exit 102 | public void exit() { 103 | context().stopTimer("OffHookTimer"); 104 | } 105 | }, 106 | LocalCall() { 107 | @Override 108 | public TelephoneFsm digit(String digit) { 109 | int d = Integer.parseInt(digit); 110 | Telephone context = context(); 111 | if (d < 0 || d > 9) { 112 | fsm().pop().invalidDigit(); 113 | context().clearDisplay(); 114 | return null; 115 | } else if (context.getLocal().length() < 3) { 116 | context.playTT(d); 117 | context.saveLocal(d); 118 | context.resetTimer("OffHookTimer"); 119 | return null; 120 | } else { 121 | fsm().pop() 122 | .dialingDone(context.getType(), context.getAreaCode(), context.getExchange(), context.getLocal()); 123 | context.playTT(d); 124 | context.saveLocal(d); 125 | return null; 126 | } 127 | } 128 | 129 | @Entry 130 | public void entry() { 131 | context().startTimer("OffHookTimer", 10000); 132 | } 133 | 134 | @Exit 135 | public void exit() { 136 | context().stopTimer("OffHookTimer"); 137 | } 138 | }, 139 | LongDistance() { 140 | @Override 141 | public TelephoneFsm digit(String digit) { 142 | int d = Integer.parseInt(digit); 143 | Telephone context = context(); 144 | if (d < 0 || d > 9) { 145 | fsm().pop().invalidDigit(); 146 | context().clearDisplay(); 147 | return null; 148 | } else if (context.getAreaCode().length() < 3) { 149 | context.playTT(d); 150 | context.saveAreaCode(d); 151 | context.resetTimer("OffHookTimer"); 152 | return null; 153 | } else { 154 | context.playTT(d); 155 | context.saveAreaCode(d); 156 | context.addDisplay("-"); 157 | return Exchange; 158 | } 159 | } 160 | 161 | @Entry 162 | public void entry() { 163 | context().startTimer("OffHookTimer", 10000); 164 | } 165 | 166 | @Exit 167 | public void exit() { 168 | context().stopTimer("OffHookTimer"); 169 | } 170 | }, 171 | NineOne() { 172 | @Override 173 | public TelephoneFsm digit(String digit) { 174 | int d = Integer.parseInt(digit); 175 | Telephone context = context(); 176 | if (d < 0 || d > 9) { 177 | fsm().pop().invalidDigit(); 178 | context().clearDisplay(); 179 | return null; 180 | } else if (d == 1) { 181 | fsm().pop() 182 | .dialingDone(context.getType(), context.getAreaCode(), context.getExchange(), context.getLocal()); 183 | context.playTT(d); 184 | context.setType(Telephone.CallType.EMERGENCY); 185 | context.saveExchange(d); 186 | return null; 187 | } else { 188 | context.playTT(d); 189 | context.setType(Telephone.CallType.LOCAL); 190 | context.saveExchange(d); 191 | context.addDisplay("-"); 192 | return LocalCall; 193 | } 194 | } 195 | 196 | @Entry 197 | public void entry() { 198 | context().startTimer("OffHookTimer", 10000); 199 | } 200 | 201 | @Exit 202 | public void exit() { 203 | context().stopTimer("OffHookTimer"); 204 | } 205 | }, 206 | OneOneStart() { 207 | @Override 208 | public TelephoneFsm digit(String digit) { 209 | int d = Integer.parseInt(digit); 210 | Telephone context = context(); 211 | if (d < 0 || d > 9) { 212 | fsm().pop().invalidDigit(); 213 | context().clearDisplay(); 214 | return null; 215 | } else if (d == 1) { 216 | context.playTT(d); 217 | context.saveExchange(d); 218 | return NineOne; 219 | } else { 220 | context.playTT(d); 221 | context.setType(Telephone.CallType.LOCAL); 222 | context.saveExchange(d); 223 | return Exchange; 224 | } 225 | } 226 | 227 | @Entry 228 | public void entry() { 229 | context().startTimer("OffHookTimer", 10000); 230 | } 231 | 232 | @Exit 233 | public void exit() { 234 | context().stopTimer("OffHookTimer"); 235 | } 236 | }; 237 | 238 | private static Telephone context() { 239 | Telephone context = Fsm.thisContext(); 240 | return context; 241 | } 242 | 243 | private static Fsm fsm() { 244 | Fsm fsm = Fsm.thisFsm(); 245 | return fsm; 246 | } 247 | 248 | @Override 249 | public TelephoneFsm clockTimer() { 250 | return null; 251 | } 252 | 253 | @Override 254 | public TelephoneFsm depositMoney() { 255 | throw fsm().invalidTransitionOn(); 256 | } 257 | 258 | @Override 259 | public TelephoneFsm dialingDone(int callType, String areaCode, String exchange, String local) { 260 | throw fsm().invalidTransitionOn(); 261 | } 262 | 263 | @Override 264 | public TelephoneFsm digit(String digit) { 265 | throw fsm().invalidTransitionOn(); 266 | } 267 | 268 | @Override 269 | public TelephoneFsm emergency() { 270 | throw fsm().invalidTransitionOn(); 271 | } 272 | 273 | @Override 274 | public TelephoneFsm invalidDigit() { 275 | fsm().pop().invalidDigit(); 276 | context().clearDisplay(); 277 | return null; 278 | } 279 | 280 | @Override 281 | public TelephoneFsm invalidNumber() { 282 | throw fsm().invalidTransitionOn(); 283 | } 284 | 285 | @Override 286 | public TelephoneFsm leftOfHook() { 287 | throw fsm().invalidTransitionOn(); 288 | } 289 | 290 | @Override 291 | public TelephoneFsm lineBusy() { 292 | throw fsm().invalidTransitionOn(); 293 | } 294 | 295 | @Override 296 | public TelephoneFsm loopTimer() { 297 | throw fsm().invalidTransitionOn(); 298 | } 299 | 300 | @Override 301 | public TelephoneFsm nycTemp() { 302 | throw fsm().invalidTransitionOn(); 303 | } 304 | 305 | @Override 306 | public TelephoneFsm offHook() { 307 | throw fsm().invalidTransitionOn(); 308 | } 309 | 310 | @Override 311 | public TelephoneFsm offHookTimer() { 312 | fsm().pop().leftOfHook(); 313 | context().clearDisplay(); 314 | return null; 315 | } 316 | 317 | @Override 318 | public TelephoneFsm onHook() { 319 | fsm().pop().onHook(); 320 | context().clearDisplay(); 321 | return null; 322 | } 323 | 324 | @Override 325 | public TelephoneFsm ringTimer() { 326 | throw fsm().invalidTransitionOn(); 327 | } 328 | 329 | @Override 330 | public TelephoneFsm time() { 331 | throw fsm().invalidTransitionOn(); 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /framework/src/main/java/com/chiralbehaviors/tron/Fsm.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2013 ChiralBehaviors LLC, all rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.chiralbehaviors.tron; 17 | 18 | import java.lang.reflect.InvocationHandler; 19 | import java.lang.reflect.InvocationTargetException; 20 | import java.lang.reflect.Method; 21 | import java.lang.reflect.Proxy; 22 | import java.util.ArrayDeque; 23 | import java.util.Deque; 24 | import java.util.concurrent.Callable; 25 | import java.util.concurrent.locks.Lock; 26 | import java.util.concurrent.locks.ReentrantLock; 27 | 28 | import org.slf4j.Logger; 29 | import org.slf4j.LoggerFactory; 30 | 31 | /** 32 | * A Finite State Machine implementation. 33 | * 34 | * @author hhildebrand 35 | * 36 | * @param the transition interface 37 | * @param the fsm context interface 38 | */ 39 | public final class Fsm { 40 | private static class State { 41 | private final Context context; 42 | private final Transitions transitions; 43 | 44 | public State(Context context, Transitions transitions) { 45 | this.context = context; 46 | this.transitions = transitions; 47 | } 48 | 49 | } 50 | 51 | private static class PendingTransition implements InvocationHandler { 52 | private volatile Object[] args; 53 | private volatile Method method; 54 | 55 | @Override 56 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 57 | if (this.method != null) { 58 | throw new IllegalStateException( 59 | String.format("Pop transition '%s' has already been established", method.toGenericString())); 60 | } 61 | this.method = method; 62 | this.args = args; 63 | return null; 64 | } 65 | } 66 | 67 | private static final Logger DEFAULT_LOG = LoggerFactory.getLogger(Fsm.class); 68 | private static final ThreadLocal> thisFsm = new ThreadLocal<>(); 69 | 70 | /** 71 | * Construct a new instance of a finite state machine. 72 | * 73 | * @param fsmContext - the object used as the action context for this FSM 74 | * @param transitions - the interface class used to define the transitions for 75 | * this FSM 76 | * @param transitionsCL - the class loader to be used to load the transitions 77 | * interface class 78 | * @param initialState - the initial state of the FSM 79 | * @param sync - true if this FSM is to synchronize state transitions. 80 | * This is required for multi-threaded use of the FSM 81 | * @return the Fsm instance 82 | */ 83 | public static Fsm construct(Context fsmContext, 84 | Class transitions, 85 | ClassLoader transitionsCL, 86 | Enum initialState, boolean sync) { 87 | if (!transitions.isAssignableFrom(initialState.getClass())) { 88 | throw new IllegalArgumentException( 89 | String.format("Supplied initial state '%s' does not implement the transitions interface '%s'", 90 | initialState, transitions)); 91 | } 92 | Fsm fsm = new Fsm<>(fsmContext, sync, transitions, transitionsCL); 93 | @SuppressWarnings("unchecked") 94 | Transitions initial = (Transitions) initialState; 95 | fsm.current = initial; 96 | return fsm; 97 | } 98 | 99 | /** 100 | * Construct a new instance of a finite state machine with a default 101 | * ClassLoader. 102 | */ 103 | public static Fsm construct(Context fsmContext, 104 | Class transitions, 105 | Enum initialState, boolean sync) { 106 | return construct(fsmContext, transitions, fsmContext.getClass().getClassLoader(), initialState, sync); 107 | } 108 | 109 | /** 110 | * 111 | * @return the Context of the currently executing Fsm 112 | */ 113 | public static Context thisContext() { 114 | @SuppressWarnings("unchecked") 115 | Fsm fsm = (Fsm) thisFsm.get(); 116 | return fsm.getContext(); 117 | } 118 | 119 | /** 120 | * 121 | * @return the currrently executing Fsm 122 | */ 123 | public static Fsm thisFsm() { 124 | @SuppressWarnings("unchecked") 125 | Fsm fsm = (Fsm) thisFsm.get(); 126 | return fsm; 127 | } 128 | 129 | private Context context; 130 | private Transitions current; 131 | private Logger log; 132 | private String name = ""; 133 | private boolean pendingPop = false; 134 | private State pendingPush; 135 | private PendingTransition popTransition; 136 | private Transitions previous; 137 | private final Transitions proxy; 138 | private PendingTransition pushTransition; 139 | private final Deque> stack = new ArrayDeque<>(); 140 | private final Lock sync; 141 | private String transition; 142 | private final Class transitionsType; 143 | 144 | Fsm(Context context, boolean sync, Class transitionsType, ClassLoader transitionsCL) { 145 | this.setContext(context); 146 | this.sync = sync ? new ReentrantLock() : null; 147 | this.transitionsType = transitionsType; 148 | this.log = DEFAULT_LOG; 149 | @SuppressWarnings("unchecked") 150 | Transitions facade = (Transitions) Proxy.newProxyInstance(transitionsCL, new Class[] { transitionsType }, 151 | transitionsHandler()); 152 | proxy = facade; 153 | } 154 | 155 | /** 156 | * Execute the initial state's entry action. Note that we do not guard against 157 | * multiple invocations. 158 | */ 159 | public void enterStartState() { 160 | if (log.isTraceEnabled()) { 161 | log.trace(String.format("[%s] Entering start state %s", name, prettyPrint(current))); 162 | } 163 | executeEntryAction(); 164 | } 165 | 166 | /** 167 | * 168 | * @return the action context object of this Fsm 169 | */ 170 | public Context getContext() { 171 | return context; 172 | } 173 | 174 | /** 175 | * 176 | * @return the current state of the Fsm 177 | */ 178 | public Transitions getCurrentState() { 179 | return locked(() -> { 180 | Transitions transitions = current; 181 | return transitions; 182 | }); 183 | } 184 | 185 | /** 186 | * 187 | * @return the logger used by this Fsm 188 | */ 189 | public Logger getLog() { 190 | return locked(() -> { 191 | Logger c = log; 192 | return c; 193 | }); 194 | } 195 | 196 | public String getName() { 197 | return name; 198 | } 199 | 200 | /** 201 | * 202 | * @return the previous state of the Fsm, or null if no previous state 203 | */ 204 | public Transitions getPreviousState() { 205 | return locked(() -> { 206 | Transitions transitions = previous; 207 | return transitions; 208 | }); 209 | } 210 | 211 | /** 212 | * 213 | * @return the String representation of the current transition 214 | */ 215 | public String getTransition() { 216 | return locked(() -> { 217 | return transition; 218 | }); 219 | } 220 | 221 | /** 222 | * 223 | * @return the Transitions object that drives this Fsm through its transitions 224 | */ 225 | public Transitions getTransitions() { 226 | return proxy; 227 | } 228 | 229 | /** 230 | * @return the invalid transition excepiton based current transition attempt 231 | */ 232 | public InvalidTransition invalidTransitionOn() { 233 | return new InvalidTransition(String.format("[%s] %s.%s", name, prettyPrint(current), transition)); 234 | } 235 | 236 | /** 237 | * Pop the state off of the stack of pushed states. This state will become the 238 | * current state of the Fsm. Answer the Transitions object that may be used to 239 | * send a transition to the popped state. 240 | * 241 | * @return the Transitions object that may be used to send a transition to the 242 | * popped state. 243 | */ 244 | public Transitions pop() { 245 | if (pendingPop) { 246 | throw new IllegalStateException(String.format("[%s] State has already been popped", name)); 247 | } 248 | if (pendingPush != null) { 249 | throw new IllegalStateException(String.format("[%s] Cannot pop after pushing", name)); 250 | } 251 | if (stack.size() == 0) { 252 | throw new IllegalStateException( 253 | String.format("[%s] State stack is empty, current state: %s, transition: %s", name, 254 | prettyPrint(current), transition)); 255 | } 256 | pendingPop = true; 257 | popTransition = new PendingTransition(); 258 | @SuppressWarnings("unchecked") 259 | Transitions pendingTransition = (Transitions) Proxy.newProxyInstance(getContext().getClass().getClassLoader(), 260 | new Class[] { transitionsType }, 261 | popTransition); 262 | return pendingTransition; 263 | } 264 | 265 | public String prettyPrint(Transitions state) { 266 | if (state == null) { 267 | return "null"; 268 | } 269 | Class enclosingClass = state.getClass().getEnclosingClass(); 270 | return String.format("%s.%s", (enclosingClass != null ? enclosingClass : state.getClass()).getSimpleName(), 271 | ((Enum) state).name()); 272 | } 273 | 274 | /** 275 | * Push the current state of the Fsm on the state stack. The supplied state 276 | * becomes the current state of the Fsm 277 | * 278 | * @param state - the new current state of the Fsm. 279 | */ 280 | public Transitions push(Transitions state) { 281 | return push(state, context); 282 | } 283 | 284 | /** 285 | * Push the current state of the Fsm on the state stack. The supplied state 286 | * becomes the current state of the Fsm 287 | * 288 | * @param state - the new current state of the Fsm. 289 | * @param context - the new current context of the FSM 290 | */ 291 | public Transitions push(Transitions state, Context context) { 292 | if (state == null) { 293 | throw new IllegalStateException(String.format("[%s] Cannot push a null state", name)); 294 | } 295 | if (pendingPush != null) { 296 | throw new IllegalStateException(String.format("[%s] Cannot push state twice", name)); 297 | } 298 | if (pendingPop) { 299 | throw new IllegalStateException(String.format("[%s] Cannot push after pop", name)); 300 | } 301 | pushTransition = new PendingTransition(); 302 | pendingPush = new State<>(context, state); 303 | @SuppressWarnings("unchecked") 304 | Transitions pendingTransition = (Transitions) Proxy.newProxyInstance(getContext().getClass().getClassLoader(), 305 | new Class[] { transitionsType }, 306 | pushTransition); 307 | return pendingTransition; 308 | } 309 | 310 | /** 311 | * Set the Context of the FSM 312 | */ 313 | public void setContext(Context context) { 314 | this.context = context; 315 | } 316 | 317 | /** 318 | * Set the Logger for this Fsm. 319 | * 320 | * @param log - the Logger of this Fsm 321 | */ 322 | public void setLog(Logger log) { 323 | this.log = log; 324 | } 325 | 326 | public void setName(String name) { 327 | this.name = name; 328 | } 329 | 330 | public R synchonizeOnState(Callable call) throws Exception { 331 | return locked(call); 332 | } 333 | 334 | public void synchonizeOnState(Runnable call) { 335 | locked(() -> { 336 | call.run(); 337 | return null; 338 | }); 339 | } 340 | 341 | @Override 342 | public String toString() { 343 | return String.format("Fsm [name = %s, current=%s, previous=%s, transition=%s]", name, prettyPrint(current), 344 | prettyPrint(previous), getTransition()); 345 | } 346 | 347 | private void executeEntryAction() { 348 | for (Method action : current.getClass().getDeclaredMethods()) { 349 | if (action.isAnnotationPresent(Entry.class)) { 350 | action.setAccessible(true); 351 | if (log.isTraceEnabled()) { 352 | log.trace(String.format("[%s] Entry action: %s.%s", name, prettyPrint(current), 353 | prettyPrint(action))); 354 | } 355 | try { 356 | // For entry actions with parameters, inject the context 357 | if (action.getParameterTypes().length > 0) 358 | action.invoke(current, getContext()); 359 | else 360 | action.invoke(current, new Object[] {}); 361 | return; 362 | } catch (IllegalAccessException | IllegalArgumentException e) { 363 | throw new IllegalStateException(e); 364 | } catch (InvocationTargetException e) { 365 | Throwable targetException = e.getTargetException(); 366 | if (targetException instanceof RuntimeException) { 367 | throw (RuntimeException) targetException; 368 | } 369 | throw new IllegalStateException(targetException); 370 | } 371 | } 372 | } 373 | } 374 | 375 | private void executeExitAction() { 376 | for (Method action : current.getClass().getDeclaredMethods()) { 377 | if (action.isAnnotationPresent(Exit.class)) { 378 | action.setAccessible(true); 379 | if (log.isTraceEnabled()) { 380 | log.trace(String.format("[%s] Exit action: %s.%s", name, prettyPrint(current), 381 | prettyPrint(action))); 382 | } 383 | try { 384 | // For exit action with parameters, inject the context 385 | if (action.getParameterTypes().length > 0) 386 | action.invoke(current, getContext()); 387 | else 388 | action.invoke(current, new Object[] {}); 389 | return; 390 | } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 391 | throw new IllegalStateException(e); 392 | } 393 | } 394 | } 395 | } 396 | 397 | /** 398 | * The Jesus Nut 399 | * 400 | * @param t - the transition to fire 401 | * @param arguments - the transition arguments 402 | * @return 403 | */ 404 | private Object fire(Method t, Object[] arguments) { 405 | if (t == null) { 406 | return null; 407 | } 408 | Fsm previousFsm = thisFsm.get(); 409 | thisFsm.set(this); 410 | previous = current; 411 | if (!transitionsType.isAssignableFrom(t.getReturnType())) { 412 | try { 413 | return t.invoke(current, arguments); 414 | } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 415 | throw new IllegalStateException(e); 416 | } 417 | } 418 | 419 | try { 420 | transition = prettyPrint(t); 421 | Transitions nextState; 422 | Transitions pinned = current; 423 | try { 424 | nextState = fireTransition(lookupTransition(t), arguments); 425 | } catch (InvalidTransition e) { 426 | nextState = fireTransition(lookupDefaultTransition(e, t), arguments); 427 | } 428 | if (pinned == current) { 429 | transitionTo(nextState); 430 | } else { 431 | if (nextState != null && log.isTraceEnabled()) { 432 | log.trace(String.format("[%s] Eliding Transition %s -> %s, pinned state: %s", name, 433 | prettyPrint(current), prettyPrint(nextState), prettyPrint(pinned))); 434 | } 435 | } 436 | return null; 437 | } finally { 438 | thisFsm.set(previousFsm); 439 | } 440 | } 441 | 442 | /** 443 | * Fire the concrete transition of the current state 444 | * 445 | * @param stateTransition - the transition method to execute 446 | * @param arguments - the arguments of the method 447 | * 448 | * @return the next state 449 | */ 450 | @SuppressWarnings("unchecked") 451 | private Transitions fireTransition(Method stateTransition, Object[] arguments) { 452 | if (stateTransition.isAnnotationPresent(Default.class)) { 453 | if (log.isTraceEnabled()) { 454 | log.trace(String.format("[%s] Default transition: %s.%s", prettyPrint(current)), getTransition(), name); 455 | } 456 | try { 457 | return (Transitions) stateTransition.invoke(current, (Object[]) null); 458 | } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { 459 | throw new IllegalStateException(String.format("Unable to invoke transition %s,%s", prettyPrint(current), 460 | prettyPrint(stateTransition)), 461 | e); 462 | } 463 | } 464 | if (log.isTraceEnabled()) { 465 | log.trace(String.format("[%s] Transition: %s.%s", name, prettyPrint(current), getTransition())); 466 | } 467 | try { 468 | return (Transitions) stateTransition.invoke(current, arguments); 469 | } catch (IllegalAccessException | IllegalArgumentException e) { 470 | throw new IllegalStateException(String.format("Unable to invoke transition %s.%s", prettyPrint(current), 471 | prettyPrint(stateTransition)), 472 | e.getCause()); 473 | } catch (InvocationTargetException e) { 474 | if (e.getTargetException() instanceof InvalidTransition) { 475 | if (log.isTraceEnabled()) { 476 | log.trace(String.format("[%s] Invalid transition %s.%s", name, prettyPrint(current), 477 | getTransition())); 478 | } 479 | throw (InvalidTransition) e.getTargetException(); 480 | } 481 | if (e.getTargetException() instanceof RuntimeException) { 482 | throw (RuntimeException) e.getTargetException(); 483 | } 484 | throw new IllegalStateException(String.format("[%s] Unable to invoke transition %s.%s", name, 485 | prettyPrint(current), prettyPrint(stateTransition)), 486 | e.getTargetException()); 487 | } 488 | } 489 | 490 | private T locked(Callable call) { 491 | final Lock lock = sync; 492 | if (lock != null) { 493 | lock.lock(); 494 | } 495 | try { 496 | return call.call(); 497 | } catch (RuntimeException e) { 498 | throw e; 499 | } catch (Exception e) { 500 | throw new IllegalStateException(e); 501 | } finally { 502 | if (lock != null) { 503 | lock.unlock(); 504 | } 505 | } 506 | } 507 | 508 | private Method lookupDefaultTransition(InvalidTransition previousException, Method t) { 509 | // look for a @Default transition for the state singleton 510 | for (Method defaultTransition : current.getClass().getDeclaredMethods()) { 511 | if (defaultTransition.isAnnotationPresent(Default.class)) { 512 | defaultTransition.setAccessible(true); 513 | return defaultTransition; 514 | } 515 | } 516 | // look for a @Default transition for the state on the enclosing enum class 517 | for (Method defaultTransition : current.getClass().getMethods()) { 518 | if (defaultTransition.isAnnotationPresent(Default.class)) { 519 | defaultTransition.setAccessible(true); 520 | return defaultTransition; 521 | } 522 | } 523 | if (previousException == null) { 524 | throw new InvalidTransition(String.format(prettyPrint(t))); 525 | } else { 526 | throw previousException; 527 | } 528 | } 529 | 530 | /** 531 | * Lookup the transition. 532 | * 533 | * @param t - the transition defined in the interface 534 | * @return the transition Method for the current state matching the interface 535 | * definition 536 | */ 537 | private Method lookupTransition(Method t) { 538 | Method stateTransition = null; 539 | try { 540 | // First we try declared methods on the state 541 | stateTransition = current.getClass().getMethod(t.getName(), t.getParameterTypes()); 542 | } catch (NoSuchMethodException | SecurityException e1) { 543 | throw new IllegalStateException( 544 | String.format("Inconcievable! The state %s does not implement the transition %s", 545 | prettyPrint(current), prettyPrint(t))); 546 | } 547 | stateTransition.setAccessible(true); 548 | return stateTransition; 549 | } 550 | 551 | /** 552 | * Ye olde tyme state transition 553 | * 554 | * @param nextState - the next state of the Fsm 555 | */ 556 | private void normalTransition(Transitions nextState) { 557 | if (nextState == null) { // internal loopback transition 558 | if (log.isTraceEnabled()) { 559 | log.trace(String.format("[%s] Internal loopback: %s", name, prettyPrint(current))); 560 | } 561 | return; 562 | } 563 | executeExitAction(); 564 | if (log.isTraceEnabled()) { 565 | log.trace(String.format("[%s] State transition: %s -> %s", name, prettyPrint(current), 566 | prettyPrint(nextState))); 567 | } 568 | current = nextState; 569 | executeEntryAction(); 570 | } 571 | 572 | /** 573 | * Execute the exit action of the current state. Set current state to popped 574 | * state of the stack. Execute any pending transition on the current state. 575 | */ 576 | private void popTransition() { 577 | pendingPop = false; 578 | previous = current; 579 | State pop = stack.pop(); 580 | PendingTransition pendingTransition = popTransition; 581 | popTransition = null; 582 | 583 | executeExitAction(); 584 | if (log.isTraceEnabled()) { 585 | log.trace(String.format("[%s] State transition: %s -> %s - Popping(%s)", name, prettyPrint(previous), 586 | prettyPrint(pop), stack.size() + 1)); 587 | } 588 | current = pop.transitions; 589 | if (pop.context != null) { 590 | setContext(pop.context); 591 | } 592 | if (pendingTransition != null) { 593 | if (log.isTraceEnabled()) { 594 | log.trace(String.format("[%s] Pop transition: %s.%s", name, prettyPrint(current), 595 | prettyPrint(pendingTransition.method))); 596 | } 597 | fire(pendingTransition.method, pendingTransition.args); 598 | } 599 | } 600 | 601 | private String prettyPrint(State state) { 602 | return prettyPrint(state.transitions) + " [" + state.context == null ? "<>: " + getContext() 603 | : state.context + "]"; 604 | } 605 | 606 | private String prettyPrint(Method transition) { 607 | StringBuilder builder = new StringBuilder(); 608 | if (transition != null) { 609 | builder.append(transition.getName()); 610 | builder.append('('); 611 | Class[] parameters = transition.getParameterTypes(); 612 | for (int i = 0; i < parameters.length; i++) { 613 | builder.append(parameters[i].getSimpleName()); 614 | if (i != parameters.length - 1) { 615 | builder.append(", "); 616 | } 617 | } 618 | builder.append(')'); 619 | } else { 620 | builder.append("loopback"); 621 | } 622 | return builder.toString(); 623 | } 624 | 625 | /** 626 | * Push the current state of the Fsm to the stack, with the supplied context as 627 | * the new current context of the FSM, if non null. Transition the Fsm to the 628 | * nextState, execute the entry action of that state. Set the current state of 629 | * the Fsm to the pending push state, executing the entry action on that state 630 | * 631 | * @param nextState 632 | */ 633 | private void pushTransition(Transitions nextState) { 634 | State pushed = pendingPush; 635 | pendingPush = null; 636 | normalTransition(nextState); 637 | stack.push(new State<>(context, current)); 638 | if (log.isTraceEnabled()) { 639 | log.trace(String.format("[%s] State transition: %s -> %s - Pushing(%s)", name, prettyPrint(current), 640 | prettyPrint(pushed), stack.size())); 641 | } 642 | current = pushed.transitions; 643 | if (pushed.context != null) { 644 | setContext(pushed.context); 645 | } 646 | Transitions pinned = current; 647 | PendingTransition pushTrns = pushTransition; 648 | pushTransition = null; 649 | executeEntryAction(); 650 | if (pushTrns != null) { 651 | if (current != pinned) { 652 | log.trace(String.format("[%s] Eliding push transition %s.%s pinned: %s", name, prettyPrint(current), 653 | prettyPrint(pushTrns.method), prettyPrint(pinned))); 654 | } else { 655 | if (log.isTraceEnabled()) { 656 | log.trace(String.format("[%s] Push transition: %s.%s", name, prettyPrint(current), 657 | prettyPrint(pushTrns.method))); 658 | } 659 | fire(pushTrns.method, pushTrns.args); 660 | } 661 | } 662 | } 663 | 664 | private InvocationHandler transitionsHandler() { 665 | return new InvocationHandler() { 666 | @Override 667 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 668 | return locked(() -> fire(method, args)); 669 | } 670 | }; 671 | } 672 | 673 | /** 674 | * Transition to the next state 675 | * 676 | * @param nextState 677 | */ 678 | private void transitionTo(Transitions nextState) { 679 | if (pendingPush != null) { 680 | pushTransition(nextState); 681 | } else if (pendingPop) { 682 | popTransition(); 683 | } else { 684 | normalTransition(nextState); 685 | } 686 | } 687 | } 688 | --------------------------------------------------------------------------------