├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── build.gradle ├── gradle-mvn-push.gradle ├── gradle.properties ├── settings.gradle └── tinymachine ├── build.gradle ├── gradle.properties ├── proguard-rules.pro └── src ├── androidTest └── java │ └── de │ └── halfbit │ └── tinymachine │ ├── Callbacks.java │ ├── OnEntry.java │ ├── OnExit.java │ ├── StacklessProcessingTest.java │ └── TinyMachineTest.java └── main ├── AndroidManifest.xml └── java └── de └── halfbit └── tinymachine ├── StateHandler.java └── TinyMachine.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Eclipse 26 | .settings/ 27 | .project 28 | .classpath 29 | 30 | # Android Studio 31 | *.iml 32 | 33 | # OSX 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | android: 3 | components: 4 | # latest versions 5 | - platform-tools 6 | - tools 7 | - build-tools 8 | - android-21 9 | 10 | before_script: 11 | - echo no | android create avd --force -n test -t android-19 --abi armeabi-v7a 12 | - emulator -avd test -no-skin -no-audio -no-window & 13 | - android-wait-for-emulator 14 | - adb shell input keyevent 82 & 15 | 16 | script: gradle build connectedCheck -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![maintenance-status](https://img.shields.io/badge/maintenance-as--is-yellow.svg) 2 | 3 | TinyMachine is fun to use [finite-state machine][1]. It helps you to write compact and clearly structured code when used in [event-driver programming][3]. No deep if-else or switch-case statements, no static state transition tables, no complex, hard to understand models. Just clearly structured state handling and transition logic. This is what TinyMachine is. 4 | 5 | In you are searching for a Kotlin coroutine alternative, you might want to have a look at [comachine](https://github.com/beworker/comachine). 6 | 7 | # TinyMachine is 8 | - extra small (~ 5K jar) 9 | - easy to use 10 | - well tested 11 | - annotation based (no requiremens on method names, no interfaces to implement) 12 | 13 | # TinyMachine API in a nutshell 14 | - `@StateHandler(state=STATE_A, type=Type.OnEntry)` annotates handler methods receiving `OnEntry` event in `STATE_A`. 15 | - `@StateHandler(state=STATE_A, type=Type.OnExit)` annotates handler methods receiving `OnExit` event in `STATE_A`. 16 | - `@StateHandler(state=STATE_A, type=Type.OnEvent)` annotates handler methods receiving custom events in `STATE_A`. 17 | - `TinyMachine.fireEvent(Object event)` forwards given event to the corresponding handler method. 18 | - `TinyMachine.transitionTo(int state)` transtions the state machine into a new state. 19 | - `TinyMachine.getCurrentState()` returns current machine's state 20 | - `TinyMachine.setTraceTag(String tag)` enables or disables state change and event handling traces in LogCat using given tag. 21 | 22 | # Usage example 23 | ```java 24 | public class Example { 25 | 26 | // Define states 27 | private static final int STATE_A = 0; 28 | private static final int STATE_B = 1; 29 | 30 | // Define state handler class with handler methods 31 | public static class TinyHandler { 32 | 33 | // Handlers for ANY_STATE 34 | @StateHandler(state = StateHandler.STATE_ANY, type = Type.OnEntry) 35 | public void onEntryStateA() { 36 | // This method is called when machine enters any new state 37 | } 38 | 39 | // Handlers for STATE_A 40 | 41 | @StateHandler(state = STATE_A, type = Type.OnEntry) 42 | public void onEntryStateA() { 43 | // This method is called when machine enters STATE_A 44 | } 45 | 46 | @StateHandler(state = STATE_A, type = Type.OnExit) 47 | public void onExitStateA() { 48 | // This method is called when machine exits STATE_A 49 | } 50 | 51 | @StateHandler(state = STATE_A) 52 | public void onEventStateA(String event, TinyMachine tm) { 53 | // It's called when an event of type String is fired while machine is in STATE_A 54 | 55 | // As an example, let's transition into STATE_B when event "DONE" is received 56 | if ("DONE".equals(event)) { 57 | tm.transitionTo(STATE_B); 58 | // Since now all further events will be received by STATE_B handlers 59 | } 60 | } 61 | 62 | // Handlers for STATE_B 63 | 64 | @StateHandler(state = STATE_B, type = Type.OnEntry) 65 | public void onEntryStateB() { 66 | // This method is called when machine enters STATE_B 67 | } 68 | 69 | // and so on 70 | } 71 | 72 | private TinyMachine mTinyMachine; 73 | 74 | public Example() { 75 | // Create state machine with TinyHandler and put it into initial STATE_A state 76 | mTinyMachine = new TinyMachine(new TinyHandler(), STATE_A); 77 | } 78 | 79 | // Now, when we receive events we just need to forward them to TinyMachine instance. 80 | // The machine is responsible to deliver them to the right handler depending on event 81 | // type and the current state. 82 | 83 | @Subscribe 84 | public void onEvent(String event) { 85 | // E.g. forward this event coming from TinyBus to the state machine for processing. 86 | mTinyMachine.fireEvent(event); 87 | } 88 | 89 | // etc ... We can forward more events in the same way. 90 | } 91 | ``` 92 | 93 | Gradle dependencies 94 | ======= 95 | ``` 96 | dependencies { 97 | compile 'de.halfbit:tinymachine:1.1.+' 98 | } 99 | ``` 100 | 101 | # ProGuard configuration 102 | 103 | If you use Gradle build, then you don't need to configure anything, because it will use proper configuration already delivered with Android library archive. Otherwise you can use the configuration below: 104 | ``` 105 | -keepclassmembers, allowobfuscation class ** { 106 | @de.halfbit.tinymachine.StateHandler public *; 107 | } 108 | ``` 109 | 110 | Used in 111 | ======= 112 | - [Settings Extended][2] 113 | 114 | License 115 | ======= 116 | 117 | Copyright (c) 2015 Sergej Shafarenka, halfbit.de 118 | 119 | Licensed under the Apache License, Version 2.0 (the "License"); 120 | you may not use this file except in compliance with the License. 121 | You may obtain a copy of the License at 122 | 123 | http://www.apache.org/licenses/LICENSE-2.0 124 | 125 | Unless required by applicable law or agreed to in writing, software 126 | distributed under the License is distributed on an "AS IS" BASIS, 127 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 128 | See the License for the specific language governing permissions and 129 | limitations under the License. 130 | 131 | [1]: http://en.wikipedia.org/wiki/Finite-state_machine 132 | [2]: https://play.google.com/store/apps/details?id=com.hb.settings 133 | [3]: http://en.wikipedia.org/wiki/Event-driven_programming 134 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.0.+' 8 | } 9 | } 10 | 11 | def isReleaseBuild() { 12 | return version.contains("SNAPSHOT") == false 13 | } 14 | 15 | allprojects { 16 | version = VERSION_NAME 17 | group = GROUP 18 | 19 | repositories { 20 | mavenCentral() 21 | } 22 | } -------------------------------------------------------------------------------- /gradle-mvn-push.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Chris Banes 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 | apply plugin: 'maven' 18 | apply plugin: 'signing' 19 | 20 | def isReleaseBuild() { 21 | return VERSION_NAME.contains("SNAPSHOT") == false 22 | } 23 | 24 | def getReleaseRepositoryUrl() { 25 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL 26 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" 27 | } 28 | 29 | def getSnapshotRepositoryUrl() { 30 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL 31 | : "https://oss.sonatype.org/content/repositories/snapshots/" 32 | } 33 | 34 | def getRepositoryUsername() { 35 | return hasProperty('NEXUS_USERNAME') ? NEXUS_USERNAME : "" 36 | } 37 | 38 | def getRepositoryPassword() { 39 | return hasProperty('NEXUS_PASSWORD') ? NEXUS_PASSWORD : "" 40 | } 41 | 42 | afterEvaluate { project -> 43 | uploadArchives { 44 | repositories { 45 | mavenDeployer { 46 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 47 | 48 | pom.groupId = GROUP 49 | pom.artifactId = POM_ARTIFACT_ID 50 | pom.version = VERSION_NAME 51 | 52 | repository(url: getReleaseRepositoryUrl()) { 53 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 54 | } 55 | snapshotRepository(url: getSnapshotRepositoryUrl()) { 56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) 57 | } 58 | 59 | pom.project { 60 | name POM_NAME 61 | packaging POM_PACKAGING 62 | description POM_DESCRIPTION 63 | url POM_URL 64 | 65 | scm { 66 | url POM_SCM_URL 67 | connection POM_SCM_CONNECTION 68 | developerConnection POM_SCM_DEV_CONNECTION 69 | } 70 | 71 | licenses { 72 | license { 73 | name POM_LICENCE_NAME 74 | url POM_LICENCE_URL 75 | distribution POM_LICENCE_DIST 76 | } 77 | } 78 | 79 | developers { 80 | developer { 81 | id POM_DEVELOPER_ID 82 | name POM_DEVELOPER_NAME 83 | } 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | signing { 91 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } 92 | sign configurations.archives 93 | } 94 | 95 | task androidJavadocs(type: Javadoc) { 96 | source = android.sourceSets.main.java.srcDirs 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | } 99 | 100 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { 101 | classifier = 'javadoc' 102 | from androidJavadocs.destinationDir 103 | } 104 | 105 | task androidSourcesJar(type: Jar) { 106 | classifier = 'sources' 107 | from android.sourceSets.main.java.sourceFiles 108 | } 109 | 110 | artifacts { 111 | archives androidSourcesJar 112 | archives androidJavadocsJar 113 | } 114 | } 115 | 116 | if (JavaVersion.current().isJava8Compatible()) { 117 | tasks.withType(Javadoc) { 118 | // disable the crazy super-strict doclint tool in Java 8 119 | options.addStringOption('Xdoclint:none', '-quiet') 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | VERSION_NAME=1.1.0 2 | VERSION_CODE=110 3 | GROUP=de.halfbit 4 | 5 | POM_DESCRIPTION=Fun to write and easy to read FSM tailored for Android 6 | POM_URL=https://github.com/beworker/tinymachine 7 | POM_SCM_URL=https://github.com/beworker/tinymachine 8 | POM_SCM_CONNECTION=scm:git@github.com:beworker/tinymachine.git 9 | POM_SCM_DEV_CONNECTION=scm:git@github.com:beworker/tinymachine.git 10 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 11 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 12 | POM_LICENCE_DIST=repo 13 | POM_DEVELOPER_ID=beworker 14 | POM_DEVELOPER_NAME=Sergej Shafarenka 15 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':tinymachine' 2 | -------------------------------------------------------------------------------- /tinymachine/build.gradle: -------------------------------------------------------------------------------- 1 | //apply from: '../gradle-mvn-push.gradle' 2 | apply plugin: 'com.android.library' 3 | 4 | android { 5 | compileSdkVersion 21 6 | buildToolsVersion '21.1.2' 7 | 8 | defaultConfig { 9 | minSdkVersion 9 10 | targetSdkVersion 21 11 | 12 | testInstrumentationRunner "android.test.InstrumentationTestRunner" 13 | testHandleProfiling false 14 | testFunctionalTest true 15 | 16 | consumerProguardFiles 'proguard-rules.pro' 17 | } 18 | } -------------------------------------------------------------------------------- /tinymachine/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=TinyMachine Library 2 | POM_ARTIFACT_ID=tinymachine 3 | POM_PACKAGING=aar 4 | -------------------------------------------------------------------------------- /tinymachine/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keepclassmembers, allowobfuscation class ** { 2 | @de.halfbit.tinymachine.StateHandler public *; 3 | } -------------------------------------------------------------------------------- /tinymachine/src/androidTest/java/de/halfbit/tinymachine/Callbacks.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Sergej Shafarenka, halfbit.de 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 de.halfbit.tinymachine; 17 | 18 | import junit.framework.Assert; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | 23 | public abstract class Callbacks { 24 | 25 | public static interface EventIterator { 26 | void onEvent(Object event); 27 | } 28 | 29 | private final ArrayList mEvents = new ArrayList(); 30 | 31 | protected synchronized void onCallback(Object event) { 32 | mEvents.add(event); 33 | } 34 | 35 | public synchronized void clearEvents() { 36 | mEvents.clear(); 37 | } 38 | 39 | public synchronized int getEventsCount() { 40 | return mEvents.size(); 41 | } 42 | 43 | public void iterate(EventIterator iterator) { 44 | for (Object event : mEvents) { 45 | iterator.onEvent(event); 46 | } 47 | } 48 | 49 | public void assertNullEvent() { 50 | Assert.assertEquals(1, mEvents.size()); 51 | Assert.assertSame(null, mEvents.get(0)); 52 | } 53 | 54 | public void assertSameEventsList(ArrayList expectedEvents) { 55 | Assert.assertEquals(expectedEvents.size(), mEvents.size()); 56 | for(int i=0; i events = new ArrayList(Arrays.asList(expectedEvents)); 78 | 79 | for(int i=0; i INITIAL -> FINAL -> event -> INITIAL -> FINAL 102 | 103 | mTinyMachine.transitionTo(STATE_INITIAL); 104 | mTinyMachine.fireEvent("event1"); 105 | 106 | mTinyHandler.assertEqualEvents( 107 | new OnEntry(StateHandler.STATE_ANY), 108 | new OnEntry(STATE_INITIAL), 109 | new OnExit(STATE_INITIAL), 110 | new OnEntry(StateHandler.STATE_ANY), 111 | new OnEntry(STATE_FINAL), 112 | "event1", 113 | new OnExit(STATE_FINAL), 114 | new OnEntry(StateHandler.STATE_ANY), 115 | new OnEntry(STATE_INITIAL), 116 | new OnExit(STATE_INITIAL), 117 | new OnEntry(StateHandler.STATE_ANY), 118 | new OnEntry(STATE_FINAL) 119 | ); 120 | 121 | 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /tinymachine/src/androidTest/java/de/halfbit/tinymachine/TinyMachineTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Sergej Shafarenka, halfbit.de 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 de.halfbit.tinymachine; 17 | 18 | import android.test.AndroidTestCase; 19 | 20 | import de.halfbit.tinymachine.StateHandler.Type; 21 | 22 | public class TinyMachineTest extends AndroidTestCase { 23 | 24 | private static final int STATE_INITIAL = 0; 25 | private static final int STATE_INTERMEDIATE = 1; 26 | private static final int STATE_FINAL = 2; 27 | 28 | private class TinyHandler extends Callbacks { 29 | 30 | //-- STATE_INITIAL 31 | 32 | @StateHandler(state = STATE_INITIAL, type = Type.OnEntry) 33 | public void onInitialEnter(TinyMachine tm) { 34 | onCallback(new OnEntry(tm.getCurrentState())); 35 | } 36 | @StateHandler(state = STATE_INITIAL) 37 | public void onInitialStringEvent(String event) { 38 | onCallback(event); 39 | } 40 | 41 | @StateHandler(state = STATE_INITIAL, type = Type.OnExit) 42 | public void onInitialExit(TinyMachine tm) { 43 | onCallback(new OnExit(tm.getCurrentState())); 44 | } 45 | 46 | //-- STATE_INTERMEDIATE 47 | 48 | @StateHandler(state = STATE_INTERMEDIATE, type = Type.OnExit) 49 | public void onIntermediateExit(TinyMachine tm) { 50 | onCallback(new OnExit(tm.getCurrentState())); 51 | } 52 | 53 | //-- STATE_FINAL 54 | 55 | @StateHandler(state = STATE_FINAL, type = Type.OnEntry) 56 | public void onFinalEnter(TinyMachine tm) { 57 | onCallback(new OnEntry(tm.getCurrentState())); 58 | } 59 | @StateHandler(state = STATE_FINAL) 60 | public void onFinalStringEvent(String event, TinyMachine tm) { 61 | onCallback(event); 62 | } 63 | 64 | @StateHandler(state = STATE_FINAL, type = Type.OnExit) 65 | public void onFinalExit(TinyMachine tm) { 66 | onCallback(new OnExit(tm.getCurrentState())); 67 | } 68 | 69 | //-- STATE_ANY 70 | 71 | @StateHandler(state = StateHandler.STATE_ANY, type = Type.OnEntry) 72 | public void onEntryAny(TinyMachine tm) { 73 | onCallback(new OnEntry(StateHandler.STATE_ANY)); 74 | } 75 | 76 | @StateHandler(state = StateHandler.STATE_ANY) 77 | public void onEventAny(Integer event) { 78 | onCallback(event); 79 | } 80 | 81 | @StateHandler(state = StateHandler.STATE_ANY, type = Type.OnExit) 82 | public void onExitAny(TinyMachine tm) { 83 | onCallback(new OnExit(StateHandler.STATE_ANY)); 84 | } 85 | 86 | } 87 | 88 | private TinyMachine mTinyMachine; 89 | private TinyHandler mTinyHandler; 90 | 91 | @Override 92 | protected void setUp() throws Exception { 93 | super.setUp(); 94 | mTinyHandler = new TinyHandler(); 95 | mTinyMachine = new TinyMachine(mTinyHandler, STATE_INITIAL); 96 | mTinyMachine.setTraceTag("tinymachine-test"); 97 | } 98 | 99 | @Override 100 | protected void tearDown() throws Exception { 101 | mTinyMachine = null; 102 | mTinyHandler = null; 103 | super.tearDown(); 104 | } 105 | 106 | public void testAnyStateHandler() { 107 | mTinyMachine.fireEvent("event1"); 108 | mTinyMachine.fireEvent(new Integer(20)); 109 | mTinyMachine.transitionTo(STATE_FINAL); 110 | mTinyMachine.fireEvent(new Integer(25)); 111 | 112 | mTinyHandler.assertEqualEvents( 113 | "event1", 114 | new Integer(20), 115 | new OnExit(StateHandler.STATE_ANY), 116 | new OnExit(STATE_INITIAL), 117 | new OnEntry(StateHandler.STATE_ANY), 118 | new OnEntry(STATE_FINAL), 119 | new Integer(25) 120 | ); 121 | } 122 | 123 | public void testAllHandlersPresent() { 124 | mTinyMachine.fireEvent("event1"); 125 | mTinyMachine.fireEvent("event2"); 126 | mTinyMachine.transitionTo(STATE_FINAL); 127 | mTinyMachine.fireEvent("event3"); 128 | mTinyMachine.transitionTo(STATE_INITIAL); 129 | 130 | mTinyHandler.assertEqualEvents( 131 | "event1", 132 | "event2", 133 | new OnExit(StateHandler.STATE_ANY), 134 | new OnExit(STATE_INITIAL), 135 | new OnEntry(StateHandler.STATE_ANY), 136 | new OnEntry(STATE_FINAL), 137 | "event3", 138 | new OnExit(StateHandler.STATE_ANY), 139 | new OnExit(STATE_FINAL), 140 | new OnEntry(StateHandler.STATE_ANY), 141 | new OnEntry(STATE_INITIAL) 142 | ); 143 | } 144 | 145 | public void testSomeHandlersMissing() { 146 | mTinyMachine.fireEvent("event1"); 147 | mTinyMachine.transitionTo(STATE_INTERMEDIATE); 148 | mTinyMachine.fireEvent("event2"); 149 | mTinyMachine.fireEvent("event3"); 150 | mTinyMachine.transitionTo(STATE_FINAL); 151 | mTinyMachine.fireEvent("event4"); 152 | 153 | mTinyHandler.assertEqualEvents( 154 | "event1", 155 | new OnExit(StateHandler.STATE_ANY), 156 | new OnExit(STATE_INITIAL), 157 | new OnEntry(StateHandler.STATE_ANY), 158 | new OnExit(StateHandler.STATE_ANY), 159 | new OnExit(STATE_INTERMEDIATE), 160 | new OnEntry(StateHandler.STATE_ANY), 161 | new OnEntry(STATE_FINAL), 162 | "event4" 163 | ); 164 | } 165 | 166 | public void testOnDuplicateEnterHandler() { 167 | try { 168 | TinyMachine tinyMachine = new TinyMachine(new Object() { 169 | @StateHandler(state = 0, type = Type.OnEntry) public void onEvent1() { } 170 | @StateHandler(state = 0, type = Type.OnEntry) public void onEvent2() { } 171 | }, 0); 172 | fail("IllegalArgumentException is expected"); 173 | } catch (IllegalArgumentException e) { 174 | // OK 175 | } 176 | } 177 | 178 | public void testOnDuplicateExitHandler() { 179 | try { 180 | TinyMachine tinyMachine = new TinyMachine(new Object() { 181 | @StateHandler(state = 0, type = Type.OnExit) public void onEvent1() { } 182 | @StateHandler(state = 0, type = Type.OnExit) public void onEvent2() { } 183 | }, 0); 184 | fail("IllegalArgumentException is expected"); 185 | } catch (IllegalArgumentException e) { 186 | // OK 187 | } 188 | } 189 | 190 | public void testOnDuplicateEventHandler() { 191 | try { 192 | TinyMachine tinyMachine = new TinyMachine(new Object() { 193 | @StateHandler(state = 0, type = Type.OnEvent) 194 | public void onEvent1(String event) { } 195 | 196 | @StateHandler(state = 0, type = Type.OnEvent) 197 | public void onEvent2(String event) { } 198 | }, 0); 199 | fail("IllegalArgumentException is expected"); 200 | } catch (IllegalArgumentException e) { 201 | // OK 202 | } 203 | } 204 | 205 | public void testNullEvent() { 206 | try { 207 | mTinyMachine.fireEvent(null); 208 | fail("IllegalArgumentException is expected"); 209 | } catch (IllegalArgumentException e) { 210 | // OK 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /tinymachine/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /tinymachine/src/main/java/de/halfbit/tinymachine/StateHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Sergej Shafarenka, halfbit.de 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 de.halfbit.tinymachine; 17 | 18 | import java.lang.annotation.ElementType; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import java.lang.annotation.Target; 22 | 23 | /** 24 | * Apply this annotation to public methods responsible for handling 25 | * certain events in certain states. 26 | *

27 | * There is three types of events: 28 | *

  • OnEntry gets called when machine enters given state
  • 29 | *
  • OnExit gets called when machine leaves given state
  • 30 | *
  • OnEvent (default) gets called when machine receives 31 | * given event in given state
  • 32 | */ 33 | @Retention(RetentionPolicy.RUNTIME) 34 | @Target(ElementType.METHOD) 35 | public @interface StateHandler { 36 | 37 | public static final int STATE_ANY = Integer.MIN_VALUE; 38 | 39 | public static class Type { 40 | public static final int OnEntry = 0; 41 | public static final int OnEvent = 1; 42 | public static final int OnExit = 2; 43 | } 44 | 45 | /** 46 | * Defines in which state this handler must be active 47 | * @return a state constant 48 | */ 49 | int state(); 50 | 51 | /** 52 | * Defines type of event this handler processes 53 | * @return the type of event 54 | */ 55 | int type() default Type.OnEvent; 56 | 57 | } 58 | -------------------------------------------------------------------------------- /tinymachine/src/main/java/de/halfbit/tinymachine/TinyMachine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Sergej Shafarenka, halfbit.de 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 de.halfbit.tinymachine; 17 | 18 | import android.util.Log; 19 | import android.util.SparseArray; 20 | 21 | import java.lang.reflect.Method; 22 | import java.util.HashMap; 23 | 24 | import de.halfbit.tinymachine.StateHandler.Type; 25 | 26 | /** 27 | * Finite-state machine implementation class. 28 | *

    29 | * Once created TinyMachine is capable to accept events 30 | * and deliver them to a handler instance given in constructor. Handler 31 | * instance defines public methods with {@link de.halfbit.tinymachine.StateHandler} 32 | * annotation. Every method is intended to handler certain events in certain 33 | * state. You can have as many handler methods as you need. 34 | * 35 | *

    36 | * When you receive an event, you just need to forward it to a TinyMachine 37 | * instance and it will deliver it to the right handler method. Simply call 38 | * {@link #fireEvent(Object)} for this. If there is no handler method for given event 39 | * in given state, TinyMachine will silently ignore it. 40 | * 41 | *

    42 | * There is no static configuration for transitions between states. You need to program 43 | * is inside your handler methods. Call {@link #transitionTo(int)} methods to move your 44 | * state machine into a new state. TinyMachine will automatically fire 45 | * Type.OnExit event for the current state and Type.OnEntry event 46 | * for the new state. If there are handlers for this events, then they will be called. 47 | * 48 | * @author Sergej Shafarenka 49 | */ 50 | public class TinyMachine { 51 | 52 | private final Object mHandler; 53 | private final SparseArray, Method>> mCallbacks; 54 | private final TaskQueue mTaskQueue; 55 | 56 | private String mTraceTag; 57 | private int mCurrentState; 58 | private boolean mQueueProcessed; 59 | 60 | /** 61 | * Creates new instance of FSM machine and assigns handler class 62 | * with public methods handling state transitions and events. Handler 63 | * methods must have {@link de.halfbit.tinymachine.StateHandler} 64 | * annotation describing which event in which state it will handle. 65 | * 66 | * @param handler instance with handler methods 67 | * @param initialState initial state to put state machine into. 68 | * Type.OnEntry event is not reported 69 | * for the initial state. 70 | */ 71 | public TinyMachine(Object handler, int initialState) { 72 | mHandler = handler; 73 | mCurrentState = initialState; 74 | mCallbacks = new SparseArray<>(); 75 | mTaskQueue = new TaskQueue(); 76 | 77 | final Method[] methods = handler.getClass().getMethods(); 78 | Class[] params; 79 | StateHandler ann; 80 | Class eventType; 81 | for (Method method : methods) { 82 | if (method.isBridge() || method.isSynthetic()) { 83 | continue; 84 | } 85 | ann = method.getAnnotation(StateHandler.class); 86 | if (ann != null) { 87 | switch (ann.type()) { 88 | case Type.OnEntry: { 89 | eventType = OnEntry.class; 90 | break; 91 | } 92 | case Type.OnExit: { 93 | eventType = OnExit.class; 94 | break; 95 | } 96 | case Type.OnEvent: { 97 | params = method.getParameterTypes(); 98 | if (params.length < 1) { 99 | throw new IllegalArgumentException( 100 | "Expect event parameter in @StateEventHandler method: " 101 | + method.getName()); 102 | } 103 | eventType = params[0]; 104 | break; 105 | } 106 | default: 107 | throw new IllegalArgumentException( 108 | "Unsupported event type: " + ann.type()); 109 | } 110 | 111 | final int state = ann.state(); 112 | HashMap, Method> callbacks = mCallbacks.get(state); 113 | if (callbacks == null) { 114 | callbacks = new HashMap<>(); 115 | mCallbacks.put(state, callbacks); 116 | } 117 | if (callbacks.put(eventType, method) != null) { 118 | throw new IllegalArgumentException("Duplicate handler methods not allowed" + 119 | ", method: " + method.getName()); 120 | } 121 | } 122 | } 123 | } 124 | 125 | //-- public api 126 | 127 | /** 128 | * Forwards an event into state machine. State machine will deliver the event to a 129 | * handler methods responsible for its processing. If there is no handler method found, 130 | * then event gets silently ignored and this call has no effect. 131 | * 132 | * @param event event to be delivered to a handler method 133 | */ 134 | public void fireEvent(Object event) { 135 | if (event == null) { 136 | throw new IllegalArgumentException("Event must not be null."); 137 | } 138 | mTaskQueue.offer(Task.obtainTask(Task.CODE_FIRE_EVENT, event, -1)); 139 | if (!mQueueProcessed) processTaskQueue(); 140 | } 141 | 142 | /** 143 | * Moves state machine in a new given state. If state machine is already in that state, 144 | * then this method has no effect. Otherwise, if exists, Type.OnExit event 145 | * handler for the current state is called first and then Type.OnEntry event 146 | * handler for new state is called. 147 | * 148 | * @param state new state to put state machine into 149 | */ 150 | public void transitionTo(int state) { 151 | mTaskQueue.offer(Task.obtainTask(Task.CODE_TRANSITION, null, state)); 152 | if (!mQueueProcessed) processTaskQueue(); 153 | } 154 | 155 | /** 156 | * Enables traces and sets tag to be used for Log.d() output. 157 | * TinyMachine will trace all processed events and state transitions including 158 | * events for which handlers in current state are missed. 159 | * 160 | * @param tag the name of tag to be used in Log.d() 161 | * @return this instance 162 | */ 163 | public TinyMachine setTraceTag(String tag) { 164 | mTraceTag = tag; 165 | if (mTraceTag != null) { 166 | log("current state", null); 167 | } 168 | return this; 169 | } 170 | 171 | /** 172 | * Returns current machine state. 173 | * 174 | * @return current machine state 175 | */ 176 | public int getCurrentState() { 177 | return mCurrentState; 178 | } 179 | 180 | //-- implementation 181 | 182 | private void processTaskQueue() { 183 | mQueueProcessed = true; 184 | try { 185 | Task task; 186 | while((task = mTaskQueue.poll()) != null) { 187 | switch (task.code) { 188 | 189 | case Task.CODE_FIRE_EVENT: { 190 | Object event = task.event; 191 | final Class clazz = event.getClass(); 192 | fire(clazz, event, StateHandler.STATE_ANY); 193 | fire(clazz, event, mCurrentState); 194 | break; 195 | } 196 | 197 | case Task.CODE_TRANSITION: { 198 | int state = task.state; 199 | if (mCurrentState != state) { 200 | fire(OnExit.class, null, StateHandler.STATE_ANY); 201 | fire(OnExit.class, null, mCurrentState); 202 | mCurrentState = state; 203 | if (mTraceTag != null) { 204 | log("new state", null); 205 | } 206 | fire(OnEntry.class, null, StateHandler.STATE_ANY); 207 | fire(OnEntry.class, null, mCurrentState); 208 | } 209 | break; 210 | } 211 | 212 | default: throw new IllegalStateException("wrong code: " + task.code); 213 | } 214 | task.recycle(); 215 | } 216 | } finally { 217 | mQueueProcessed = false; 218 | } 219 | } 220 | 221 | private void fire(Class handlerType, Object event, int state) { 222 | Method method = null; 223 | HashMap, Method> callbacks = mCallbacks.get(state); 224 | if (callbacks != null) { 225 | method = callbacks.get(handlerType); 226 | } 227 | 228 | if (method == null) { 229 | if (mTraceTag != null && state != StateHandler.STATE_ANY) { 230 | // log missing handler method 231 | if (handlerType == OnEntry.class) { 232 | log("OnEntry", "no handler method"); 233 | } else if (handlerType == OnExit.class) { 234 | log("OnExit", "no handler method"); 235 | } else { 236 | log("OnEvent", "no handler method, event=" + event); 237 | } 238 | } 239 | return; // no handler, exit 240 | } 241 | 242 | try { 243 | int paramsCount = method.getParameterTypes().length; 244 | if (event == null) { 245 | if (mTraceTag != null) { 246 | if (handlerType == OnEntry.class) { 247 | log("OnEntry", null); 248 | } else { 249 | log("OnExit", null); 250 | } 251 | } 252 | switch (paramsCount) { 253 | case 0: 254 | method.invoke(mHandler); 255 | break; 256 | case 1: 257 | method.invoke(mHandler, this); 258 | break; 259 | default: 260 | throw new IllegalArgumentException( 261 | "@StateEventHandler method must have 0 or 1 parameters"); 262 | } 263 | } else { 264 | if (mTraceTag != null) { 265 | log("OnEvent", "OnEvent, event=" + event); 266 | } 267 | switch (paramsCount) { 268 | case 1: 269 | method.invoke(mHandler, event); 270 | break; 271 | case 2: 272 | method.invoke(mHandler, event, this); 273 | break; 274 | default: 275 | throw new IllegalArgumentException( 276 | "@StateEventHandler method must have 1 or 2 parameters"); 277 | } 278 | } 279 | } catch (Exception e) { 280 | if (e instanceof RuntimeException) { 281 | throw (RuntimeException) e; 282 | } else { 283 | throw new IllegalStateException("Exception in @StateEventHandler method. " 284 | + "See stack trace for more details", e); 285 | } 286 | } 287 | } 288 | 289 | private void log(String eventType, String message) { 290 | Log.d(mTraceTag, " [" + mCurrentState + "] " 291 | + eventType + (message == null ? "" : ", " + message)); 292 | } 293 | 294 | //region Inner classes (same as in tinybus) 295 | 296 | private static class OnEntry {} 297 | private static class OnExit {} 298 | 299 | private static class Task { 300 | 301 | private static final TaskPool POOL = new TaskPool(6); 302 | 303 | public static final int CODE_FIRE_EVENT = 0; 304 | public static final int CODE_TRANSITION = 1; 305 | 306 | // task as linked list item 307 | public Task prev; 308 | 309 | // general purpose 310 | public int code; 311 | public Object event; 312 | public int state; 313 | 314 | private Task() { } 315 | 316 | public static Task obtainTask(int code, Object event, int state) { 317 | Task task; 318 | synchronized (POOL) { 319 | task = POOL.acquire(); 320 | } 321 | task.code = code; 322 | task.event = event; 323 | task.state = state; 324 | task.prev = null; 325 | return task; 326 | } 327 | 328 | public void recycle() { 329 | event = null; 330 | synchronized (POOL) { 331 | POOL.release(this); 332 | } 333 | } 334 | 335 | } 336 | 337 | /** Task pool for better reuse of task instances */ 338 | private static class TaskPool { 339 | 340 | private final int mMaxSize; 341 | private int mSize; 342 | private Task tail; 343 | 344 | public TaskPool(int maxSize) { 345 | mMaxSize = maxSize; 346 | } 347 | 348 | Task acquire() { 349 | Task acquired = tail; 350 | if (acquired == null) { 351 | acquired = new Task(); 352 | } else { 353 | tail = acquired.prev; 354 | mSize--; 355 | } 356 | return acquired; 357 | } 358 | 359 | void release(Task task) { 360 | if (mSize < mMaxSize) { 361 | task.prev = tail; 362 | tail = task; 363 | mSize++; 364 | } 365 | } 366 | } 367 | 368 | private static class TaskQueue { 369 | 370 | protected Task head; 371 | protected Task tail; 372 | 373 | public void offer(Task task) { 374 | if (tail == null) { 375 | tail = head = task; 376 | } else { 377 | tail.prev = task; 378 | tail = task; 379 | } 380 | } 381 | 382 | public Task poll() { 383 | if (head == null) { 384 | return null; 385 | } else { 386 | Task task = head; 387 | head = head.prev; 388 | if (head == null) { 389 | tail = null; 390 | } 391 | return task; 392 | } 393 | } 394 | } 395 | 396 | //endregion 397 | 398 | } 399 | --------------------------------------------------------------------------------