├── .gitignore ├── .idea └── codeStyleSettings.xml ├── .travis.yml ├── README.md ├── assets └── readme-overview.png ├── build.gradle ├── gradle ├── plugins │ └── maven-simple.gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── janet ├── build.gradle └── src │ ├── main │ └── java │ │ └── io │ │ └── techery │ │ └── janet │ │ ├── ActionHolder.java │ │ ├── ActionPipe.java │ │ ├── ActionService.java │ │ ├── ActionServiceWrapper.java │ │ ├── ActionState.java │ │ ├── CachedPipelines.java │ │ ├── CallbackWrapper.java │ │ ├── CancelException.java │ │ ├── Janet.java │ │ ├── JanetException.java │ │ ├── JanetInternalException.java │ │ ├── ReadActionPipe.java │ │ ├── ReadOnlyActionPipe.java │ │ ├── Replays.java │ │ ├── WriteActionPipe.java │ │ ├── helper │ │ ├── ActionStateSubscriber.java │ │ ├── ActionStateToActionTransformer.java │ │ └── JanetActionException.java │ │ └── internal │ │ └── TypeToken.java │ └── test │ └── java │ └── io │ └── techery │ └── janet │ ├── AssertUtil.java │ ├── BaseTest.java │ ├── TestPipeOperations.java │ ├── TestServiceWrapper.java │ ├── model │ ├── MockAction.java │ └── TestAction.java │ └── util │ ├── FakeExecutor.java │ └── StubServiceWrapper.java └── settings.gradle /.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 | # Log Files 26 | *.log 27 | 28 | # Archives 29 | *.gz 30 | *.tar 31 | *.zip 32 | 33 | # Eclipse 34 | *.metadata 35 | *.settings 36 | 37 | # Android Studio 38 | .idea/* 39 | **/*/.idea 40 | !.idea/codeStyleSettings.xml 41 | .gradle 42 | */local.properties 43 | */out 44 | *.iml 45 | *.iws 46 | *.ipr 47 | *~ 48 | *.swp 49 | 50 | # OS specific 51 | .DS_Store 52 | 53 | # svn 54 | *.svn* 55 | 56 | #node.js 57 | node_modules 58 | -------------------------------------------------------------------------------- /.idea/codeStyleSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 258 | 260 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: oraclejdk8 3 | 4 | ### Misc. configurations 5 | 6 | sudo: false 7 | before_cache: 8 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 9 | cache: 10 | directories: 11 | - $HOME/.m2 12 | - $HOME/.gradle/caches/ 13 | - $HOME/.gradle/wrapper/ 14 | 15 | ### The Job 16 | 17 | script: 18 | - ./gradlew --info clean build check -PdisablePreDex -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Janet 2 | Build a command-based architecture in a reactive manner 3 | 4 | ・︎︎ [![Join the chat at https://gitter.im/janet-java/Lobby](https://badges.gitter.im/janet-java/Lobby.svg)](https://gitter.im/janet-java/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 5 | ## Introduction 6 | Janet provides the infrastructure to build a flexible, scalable and resilient 7 | architecture based on actions, `RxJava`-powered pipes and the services to execute these actions. 8 | You can learn more about our framework from this [presentation](https://speakerdeck.com/dirong/janet-build-command-based-architecture-in-reactive-manner) 9 | 10 | Let's walk through a common flow for the http request: 11 | 12 | ![overview](/assets/readme-overview.png) 13 | 14 | * Janet is equipped with services to deal with different actions: 15 | ```java 16 | Janet janet = new Janet.Builder() 17 | .addService(new HttpActionService(API_URL, httpClient, converter)) 18 | .addService(new SqlActionService(...)) 19 | .addService(new XyzActionService(...)) 20 | .build(); 21 | ``` 22 | 23 | 1. Our request is described with the `SampleHttpAction` class: 24 | ```java 25 | @HttpAction(value = "/demo", method = GET) 26 | public class SampleHttpAction { 27 | @Response SampleData responseData; 28 | } 29 | ``` 30 | 31 | The request is sent/observed with its `ActionPipe`: 32 | ```java 33 | // create pipe for action class with janet 34 | ActionPipe actionPipe = janet.createPipe(SampleHttpAction.class); 35 | 36 | // register request result observer 37 | actionPipe.observe().subscribe(new ActionStateSubscriber() 38 | .onStart(action -> System.out.println("Request is being sent " + action)) 39 | .onProgress((action, progress) -> System.out.println("Request in progress: " + progress)) 40 | .onSuccess(action -> System.out.println("Request finished " + action)) 41 | .onFail((action, throwable) -> System.err.println("Request failed " + throwable)) 42 | ); 43 | 44 | // send request 45 | actionPipe.send(new SampleHttpAction()); 46 | // or actionPipe.createObservable(new SampleHttpAction()).subscribe(...) 47 | ``` 48 | 2. The request is forwarded to the `Janet` instance upon the `send` or `subscribe` call; 49 | 3. `Janet` finds a suitable service to execute an action and routes to it; 50 | 4. `ActionService` knows how to deal with the action and sets up the `progress`/`success`/`fail` statuses; 51 | 5. The resulting action is brought back to the `Janet` instance; 52 | 6. `Janet` routes the resulting action with a status to the dedicated pipes; 53 | 7. The dedicated `ActionPipe` notifies all its observers of the action wrapped with a current status; 54 | 55 | So the `Janet` instance itself stands for routing and delegating the diff job to other components: 56 | 57 | * `ActionPipe` – an action operator, the only way to send an action and receive a result; 58 | * `ActionService` – knows how to deal with an action; 59 | * `ActionServiceWrapper` – a decorator to add additional logic to the underlying service; 60 | 61 | ### Available services 62 | The Janet abilities depend on services. 63 | Currently, they include the following: 64 | 65 | * [HttpActionService](https://github.com/techery/janet-http) to enable the HTTP/HTTPS request execution; 66 | * [AsyncActionService](https://github.com/techery/janet-async) to provide support for async protocols, e.g. [socket.io](http://socket.io/); 67 | * [CommandActionService](https://github.com/techery/janet-command) to delegate any job back to the command `action`; 68 | * [AnalyticsActionSerivce](https://github.com/techery/janet-analytics) to extract analytics out of business/view logic; 69 | 70 | Possible solutions: 71 | `SqlActionService`, `LocationActionService`, `BillingActionService`, etc. 72 | 73 | ## Components 74 | ### ActionPipe 75 | The only way to operate with `action`is via its `ActionPipe`. 76 | It's created for a particular action class: 77 | ```java 78 | // Pipe for users list request 79 | ActionPipe usersPipe = janet.createPipe(GetUsersAction.class); 80 | // Pipe for repositories list request 81 | ActionPipe repositoriesPipe = janet.createPipe(GetReposAction.class); 82 | ``` 83 | 84 | An action result is provided via the `ActionState` observable: 85 | ```java 86 | Observable> usersObservable = usersPipe.observe(); 87 | ``` 88 | 89 | `ActionState` includes: 90 | * state – `start`/`progress`/`success`/`fail`; 91 | * action instance itself; 92 | * progress value; 93 | * exception for the `fail` status 94 | 95 | To send a new action for execution: 96 | ```java 97 | usersPipe.send(new GetUsersAction()); 98 | ``` 99 | 100 | To combine sending and observing at once: 101 | ```java 102 | usersPipe.createObservable(new GetUsersAction()).subscribe(...); 103 | // every other pipe's observer will get result too 104 | ``` 105 | 106 | Other capabilities: 107 | * observe the latest cached result (aka replay(1)) or clear cache; 108 | * observe success-only results; 109 | * observe base parent actions; 110 | * cancel an action execution; 111 | * create safe read-only pipe forks to listen to results only; 112 | 113 | ### ActionService 114 | `ActionService` is responsible for execution of particular actions. 115 | It defines what actions it's able to process, so `janet` knows where to route 'em. 116 | 117 | Every service should override 3 methods: 118 | * `getSupportedAnnotationType()` - defines what actions are processed by their class annotation; 119 | * ` void sendInternal(ActionHolder holder)` – is called when a new action is sent to the pipe; 120 | * ` void cancel(ActionHolder holder)` – is called when an action in the pipe is canceled; 121 | 122 | There are several services to look at: 123 | * Simple impl. -> [CommandActionService](https://github.com/techery/janet-command); 124 | * Complex impl. -> [HttpActionService](https://github.com/techery/janet-http). 125 | 126 | ### ActionServiceWrapper 127 | A decorator for `ActionService` is used to listen to the action status or add the additional intercepting logic. 128 | 129 | Abilities: 130 | * Listen to the action flow by statuses; 131 | * Intercept sending completely; 132 | * Intercept the fail status to start a retry; 133 | 134 | A simple logging wrapper: 135 | ```java 136 | public class LoggingWrapper extends ActionServiceWrapper { 137 | 138 | public LoggingWrapper(ActionService actionService) { 139 | super(actionService); 140 | } 141 | 142 | @Override protected boolean onInterceptSend(ActionHolder holder) { 143 | System.out.println("send " + holder.action()); 144 | return false; 145 | } 146 | 147 | @Override protected void onInterceptCancel(ActionHolder holder) { 148 | System.out.println("cancel " + holder.action()); 149 | } 150 | 151 | @Override protected void onInterceptStart(ActionHolder holder) { 152 | System.out.println("onStart " + holder.action()); 153 | } 154 | 155 | @Override protected void onInterceptProgress(ActionHolder holder, int progress) { 156 | System.out.println("onProgress " + holder.action() + ", progress " + progress); 157 | } 158 | 159 | @Override protected void onInterceptSuccess(ActionHolder holder) { 160 | System.out.println("onSuccess " + holder.action()); 161 | } 162 | 163 | @Override protected void onInterceptFail(ActionHolder holder, JanetException e) { 164 | System.out.println("onFail " + holder.action()); 165 | e.printStackTrace(); 166 | } 167 | } 168 | 169 | @Provides Janet createJanet() { 170 | return new Janet.Builder() 171 | .addService(new LoggingWrapper(new HttpActionService(API_URL, httpClient, converter))) 172 | .build(); 173 | } 174 | ``` 175 | 176 | Examples: 177 | * Authorize requests via [AuthWrapper](https://github.com/techery/janet-architecture-sample/blob/master/app/src/main/java/io/techery/sample/service/AuthServiceWrapper.java) 178 | * Log requests via [TimberWrapper](https://gist.github.com/almozavr/ccf620b4c0041552a8b8dbb2204254cb) 179 | 180 | Possible solutions: caching middleware, the `Dagger` injector, retry policy maker, etc. 181 | 182 | ## Samples 183 | * [Simple Android app](https://github.com/techery/janet-http-android-sample) 184 | * [Advanced Android app](https://github.com/techery/janet-architecture-sample) 185 | * [Flux-like Android app](https://github.com/techery/janet-flux-todo) 186 | 187 | ## Janet benefits 188 | 1. Flexibility and scalability. Scale functionality using [services](/janet/src/main/java/io/techery/janet/ActionService.java); 189 | 2. Reactive approach for actions interaction by [RXJava](https://github.com/ReactiveX/RxJava); 190 | 3. Throw-safety architecture. 191 | 192 | ## Download 193 | [![](https://jitpack.io/v/techery/janet.svg)](https://jitpack.io/#techery/janet) 194 | [![Build Status](https://travis-ci.org/techery/janet.svg?branch=master)](https://travis-ci.org/techery/janet) 195 | 196 | Grab via Maven 197 | ```xml 198 | 199 | 200 | jitpack.io 201 | https://jitpack.io 202 | 203 | 204 | 205 | 206 | com.github.techery 207 | janet 208 | latestVersion 209 | 210 | ``` 211 | or Gradle: 212 | ```groovy 213 | repositories { 214 | ... 215 | maven { url "https://jitpack.io" } 216 | } 217 | dependencies { 218 | compile 'com.github.techery:janet:latestVersion' 219 | } 220 | ``` 221 | 222 | ## License 223 | 224 | Copyright (c) 2018 Techery 225 | 226 | Licensed under the Apache License, Version 2.0 (the "License"); 227 | you may not use this file except in compliance with the License. 228 | You may obtain a copy of the License at 229 | 230 | http://www.apache.org/licenses/LICENSE-2.0 231 | 232 | Unless required by applicable law or agreed to in writing, software 233 | distributed under the License is distributed on an "AS IS" BASIS, 234 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 235 | See the License for the specific language governing permissions and 236 | limitations under the License. 237 | -------------------------------------------------------------------------------- /assets/readme-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techery/janet/3caec67619acf37bc937450f45e57da837b83335/assets/readme-overview.png -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | jcenter() 4 | } 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/plugins/maven-simple.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven' 2 | 3 | task sourcesJar(type: Jar, dependsOn: classes) { 4 | classifier = 'sources' 5 | from sourceSets.main.allSource 6 | } 7 | 8 | task javadocJar(type: Jar, dependsOn: javadoc) { 9 | classifier = 'javadoc' 10 | from javadoc.destinationDir 11 | } 12 | 13 | artifacts { 14 | archives sourcesJar 15 | archives javadocJar 16 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/techery/janet/3caec67619acf37bc937450f45e57da837b83335/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /janet/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | apply from: rootProject.file('gradle/plugins/maven-simple.gradle') 3 | 4 | compileJava { 5 | sourceCompatibility = 1.6 6 | targetCompatibility = 1.6 7 | } 8 | 9 | dependencies { 10 | compile 'io.reactivex:rxjava:1.1.3' 11 | // 12 | testCompile 'junit:junit:4.12' 13 | testCompile 'org.powermock:powermock-api-mockito:1.6.4' 14 | testCompile 'org.powermock:powermock-module-junit4:1.6.4' 15 | testCompile 'org.assertj:assertj-core:3.4.1' 16 | } 17 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ActionHolder.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | final public class ActionHolder { 4 | 5 | private final A origin; 6 | private A action; 7 | 8 | private ActionHolder(A action) { 9 | this.origin = action; 10 | this.action = action; 11 | } 12 | 13 | public static ActionHolder create(A action) { 14 | return new ActionHolder(action); 15 | } 16 | 17 | public A action() { 18 | return action; 19 | } 20 | 21 | public ActionHolder newAction(A action) { 22 | if (action == null) { 23 | throw new IllegalArgumentException("action == null"); 24 | } 25 | this.action = action; 26 | return this; 27 | } 28 | 29 | boolean isOrigin(Object action) { 30 | return origin == action; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ActionPipe.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import io.techery.janet.ActionState.Status; 4 | import io.techery.janet.helper.ActionStateToActionTransformer; 5 | import rx.Observable; 6 | import rx.Scheduler; 7 | import rx.functions.Action1; 8 | import rx.functions.Func0; 9 | import rx.functions.Func1; 10 | 11 | /** 12 | * End tool for sending and receiving actions with specific type using RXJava. 13 | * ActionPipe can work with actions synchronously or asynchronously. 14 | * Create instances using method {@linkplain Janet#createPipe(Class)}. 15 | *

16 | * For example, 17 | *

{@code
 18 |  * ActionPipe usersPipe = janet.createPipe(UsersAction.class);}
 19 |  * 
20 | */ 21 | public final class ActionPipe
implements ReadActionPipe, WriteActionPipe { 22 | 23 | private final Func1>> syncObservableFactory; 24 | private final Observable> pipeline; 25 | private final Action1 cancelFunc; 26 | private final Scheduler defaultSubscribeOn; 27 | 28 | private final CachedPipelines cachedPipelines; 29 | private final ActiveStream activeStream; 30 | 31 | ActionPipe(Func1>> syncObservableFactory, 32 | Func0>> pipelineFactory, 33 | final Action1 cancelFunc, 34 | Scheduler defaultSubscribeOn) { 35 | this.syncObservableFactory = syncObservableFactory; 36 | this.pipeline = pipelineFactory.call(); 37 | this.cancelFunc = cancelFunc; 38 | this.defaultSubscribeOn = defaultSubscribeOn; 39 | this.cachedPipelines = new CachedPipelines(this); 40 | this.activeStream = new ActiveStream(this); 41 | } 42 | 43 | /** {@inheritDoc} */ 44 | @Override public Observable> observe() { 45 | return pipeline; 46 | } 47 | 48 | /** {@inheritDoc} */ 49 | @Override public Observable> observeWithReplay() { 50 | return cachedPipelines.observeWithReplay(); 51 | } 52 | 53 | /** {@inheritDoc} */ 54 | @Override public Observable observeSuccess() { 55 | return observe().compose(new ActionSuccessOnlyTransformer()); 56 | } 57 | 58 | /** {@inheritDoc} */ 59 | @Override public Observable observeSuccessWithReplay() { 60 | return cachedPipelines.observeSuccessWithReplay(); 61 | } 62 | 63 | /** {@inheritDoc} */ 64 | @Override public void clearReplays() { 65 | cachedPipelines.clearReplays(); 66 | } 67 | 68 | /** {@inheritDoc} */ 69 | @Override public void send(A action) { 70 | send(action, defaultSubscribeOn); 71 | } 72 | 73 | /** {@inheritDoc} */ 74 | @Override public void send(A action, Scheduler subscribeOn) { 75 | createObservable(action, subscribeOn).subscribe(); 76 | } 77 | 78 | /** {@inheritDoc} */ 79 | @Override public void cancel(A action) { 80 | cancelFunc.call(action); 81 | } 82 | 83 | /** {@inheritDoc} */ 84 | @Override public void cancelLatest() { 85 | Observable.just(activeStream.action()) 86 | .filter(new Func1() { 87 | @Override public Boolean call(A a) { 88 | return a != null; 89 | } 90 | }) 91 | .subscribe(new Action1() { 92 | @Override public void call(A a) { 93 | cancel(a); 94 | } 95 | }); 96 | } 97 | 98 | /** {@inheritDoc} */ 99 | @Override public Observable> createObservable(A action) { 100 | return createObservable(action, defaultSubscribeOn); 101 | } 102 | 103 | private Observable> createObservable(A action, Scheduler scheduler) { 104 | activeStream.put(action); 105 | Observable observable = syncObservableFactory.call(action); 106 | if (scheduler != null) { 107 | observable = observable.subscribeOn(scheduler); 108 | } 109 | return observable; 110 | } 111 | 112 | /** {@inheritDoc} */ 113 | @Override public Observable createObservableResult(A action) { 114 | return createObservable(action).compose(new ActionStateToActionTransformer()); 115 | } 116 | 117 | /** 118 | * Returns a presentation of the ActionPipe with read only mod 119 | * 120 | * @return {@linkplain ReadOnlyActionPipe} 121 | */ 122 | public ReadOnlyActionPipe asReadOnly() { 123 | return new ReadOnlyActionPipe(this); 124 | } 125 | 126 | /** {@inheritDoc} */ 127 | @Override public ReadActionPipe filter(Func1 predicate) { 128 | return new ReadOnlyActionPipe(this, predicate); 129 | } 130 | 131 | /////////////////////////////////////////////////////////////////////////// 132 | // Helpers & Delegates 133 | /////////////////////////////////////////////////////////////////////////// 134 | 135 | private static final class ActionSuccessOnlyTransformer implements Observable.Transformer, T> { 136 | @Override public Observable call(Observable> actionStateObservable) { 137 | return actionStateObservable 138 | .filter(new Func1, Boolean>() { 139 | @Override public Boolean call(ActionState actionState) { 140 | return actionState.status == Status.SUCCESS; 141 | } 142 | }) 143 | .map(new Func1, T>() { 144 | @Override public T call(ActionState actionState) { 145 | return actionState.action; 146 | } 147 | }); 148 | } 149 | } 150 | 151 | private static final class ActiveStream { 152 | private volatile A action; 153 | 154 | public ActiveStream(ReadActionPipe pipe) { 155 | connectPipe(pipe); 156 | } 157 | 158 | private void connectPipe(ReadActionPipe pipe) { 159 | pipe.observe().doOnNext(new Action1>() { 160 | @Override public void call(ActionState as) { 161 | if (as.status == Status.START || as.status == Status.PROGRESS) { 162 | put(as.action); // update cache with latest active action 163 | } else if (as.action == action) { 164 | put(null); // cleanup cache if latest action is finished 165 | } 166 | } 167 | }).subscribe(); 168 | } 169 | 170 | public void put(A action) { 171 | this.action = action; 172 | } 173 | 174 | /** Connect to non-finished actions cache (get latest) */ 175 | public A action() { 176 | return action; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ActionService.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | /** 4 | * Base class that needs to extend to create a new service. 5 | * Service processes Janet operations for supported action type with annotation 6 | * defined in {@linkplain #getSupportedAnnotationType()} 7 | */ 8 | public abstract class ActionService { 9 | 10 | protected Callback callback; 11 | 12 | final void send(ActionHolder holder) { 13 | try { 14 | sendInternal(holder); 15 | } catch (JanetException e) { 16 | this.callback.onFail(holder, e); 17 | } 18 | } 19 | 20 | /** 21 | * Action sending 22 | */ 23 | abstract protected void sendInternal(ActionHolder holder) throws JanetException; 24 | 25 | /** 26 | * Action cancellation 27 | */ 28 | abstract protected void cancel(ActionHolder holder); 29 | 30 | /** 31 | * Getting action annotation type for using to create supported action. 32 | * Actions with this annotation will be processed by the {@linkplain ActionService}. 33 | */ 34 | abstract protected Class getSupportedAnnotationType(); 35 | 36 | void setCallback(Callback callback) { 37 | this.callback = callback; 38 | } 39 | 40 | interface Callback { 41 | void onStart(ActionHolder action); 42 | void onProgress(ActionHolder action, int progress); 43 | void onSuccess(ActionHolder action); 44 | void onFail(ActionHolder action, JanetException e); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ActionServiceWrapper.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | /** 4 | * Wrapper for interception lifecycle of delegated {@link ActionService} 5 | */ 6 | public abstract class ActionServiceWrapper extends ActionService { 7 | 8 | private final ActionService actionService; 9 | 10 | public ActionServiceWrapper(ActionService actionService) { 11 | this.actionService = actionService; 12 | } 13 | 14 | /** 15 | * Called before action sending 16 | * 17 | * @param holder action holder for intercepting 18 | * @return if {@code true} action is finished with status {@linkplain ActionState.Status#SUCCESS SUCCESS} and won't be processed by decorated service 19 | * @throws JanetException to finish action with status {@linkplain ActionState.Status#FAIL FAIL} 20 | */ 21 | protected abstract boolean onInterceptSend(ActionHolder holder) throws JanetException; 22 | 23 | /** 24 | * Called before action cancellation 25 | * 26 | * @param holder action holder for intercepting 27 | */ 28 | protected abstract void onInterceptCancel(ActionHolder holder); 29 | 30 | /** 31 | * Called from service callback before changing action status to {@linkplain ActionState.Status#START START} 32 | * 33 | * 34 | * @param holder action holder for intercepting 35 | */ 36 | protected abstract void onInterceptStart(ActionHolder holder); 37 | 38 | /** 39 | * Called from service callback before changing action status to {@linkplain ActionState.Status#PROGRESS PROGRESS} 40 | * 41 | * @param holder action holder for intercepting 42 | */ 43 | protected abstract void onInterceptProgress(ActionHolder holder, int progress); 44 | 45 | /** 46 | * Called from service callback before changing action status to {@linkplain ActionState.Status#SUCCESS SUCCESS} 47 | * 48 | * @param holder action holder for intercepting 49 | */ 50 | protected abstract void onInterceptSuccess(ActionHolder holder); 51 | 52 | /** 53 | * Called from service callback before changing action status to {@linkplain ActionState.Status#FAIL FAIL} 54 | * 55 | * @param holder action holder for intercepting 56 | * @return if {@code true} action will be sent again. Should be careful with it because there is possibility to create an infinite loop of action sending 57 | */ 58 | protected abstract boolean onInterceptFail(ActionHolder holder, JanetException e); 59 | 60 | /** 61 | * {@inheritDoc} 62 | */ 63 | @Override protected void sendInternal(ActionHolder holder) throws JanetException { 64 | if (onInterceptSend(holder)) callback.onSuccess(holder); 65 | else try { 66 | actionService.sendInternal(holder); 67 | } catch (JanetException e) { 68 | this.callback.onFail(holder, e); 69 | } 70 | } 71 | 72 | /** 73 | * {@inheritDoc} 74 | */ 75 | @Override protected void cancel(ActionHolder holder) { 76 | onInterceptCancel(holder); 77 | actionService.cancel(holder); 78 | } 79 | 80 | /** 81 | * {@inheritDoc} 82 | */ 83 | @Override protected Class getSupportedAnnotationType() { 84 | return actionService.getSupportedAnnotationType(); 85 | } 86 | 87 | /** 88 | * {@inheritDoc} 89 | */ 90 | @Override void setCallback(Callback callback) { 91 | callback = new CallbackWrapper(callback, interceptor); 92 | super.setCallback(callback); 93 | actionService.setCallback(callback); 94 | } 95 | 96 | private final CallbackWrapper.Interceptor interceptor = new CallbackWrapper.Interceptor() { 97 | @Override public void interceptStart(ActionHolder holder) { 98 | onInterceptStart(holder); 99 | } 100 | 101 | @Override public void interceptProgress(ActionHolder holder, int progress) { 102 | onInterceptProgress(holder, progress); 103 | } 104 | 105 | @Override public void interceptSuccess(ActionHolder holder) { 106 | onInterceptSuccess(holder); 107 | } 108 | 109 | @Override public boolean interceptFail(ActionHolder holder, JanetException e) { 110 | boolean resend = onInterceptFail(holder, e); 111 | if (resend) actionService.send(holder); 112 | return resend; 113 | } 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ActionState.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | /** 4 | * Use to get action state 5 | * 6 | * @param 7 | */ 8 | public final class ActionState { 9 | 10 | /** Action status **/ 11 | public enum Status { 12 | /** 13 | * Action is just started to send 14 | **/ 15 | START, 16 | /** 17 | * Sending progress. 18 | * Get the percentage of progress from {@link ActionState#progress} 19 | **/ 20 | PROGRESS, 21 | /** 22 | * Action is finished without errors 23 | * Get result from {@link ActionState#action} 24 | **/ 25 | SUCCESS, 26 | /** 27 | * Action is fault. 28 | * See {@link ActionState#exception} 29 | **/ 30 | FAIL 31 | } 32 | 33 | public final A action; 34 | public final Status status; 35 | public JanetException exception; 36 | public int progress; 37 | 38 | private ActionState(A action, Status status) { 39 | this.action = action; 40 | this.status = status; 41 | } 42 | 43 | static ActionState start(A action) { 44 | return new ActionState(action, Status.START); 45 | } 46 | 47 | static ActionState progress(A action, int progress) { 48 | return new ActionState(action, Status.PROGRESS).progress(progress); 49 | } 50 | 51 | static ActionState success(A action) { 52 | return new ActionState(action, Status.SUCCESS); 53 | } 54 | 55 | static ActionState fail(A action, JanetException e) { 56 | return new ActionState(action, Status.FAIL).exception(e); 57 | } 58 | 59 | private ActionState exception(JanetException throwable) { 60 | this.exception = throwable; 61 | return this; 62 | } 63 | 64 | private ActionState progress(int progress) { 65 | this.progress = progress; 66 | return this; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object o) { 71 | if (this == o) return true; 72 | if (o == null || getClass() != o.getClass()) return false; 73 | 74 | ActionState that = (ActionState) o; 75 | 76 | if (action != null ? !action.equals(that.action) : that.action != null) return false; 77 | if (exception != null ? !exception.equals(that.exception) : that.exception != null) return false; 78 | return status == that.status; 79 | 80 | } 81 | 82 | @Override 83 | public int hashCode() { 84 | int result = action != null ? action.hashCode() : 0; 85 | result = 31 * result + (exception != null ? exception.hashCode() : 0); 86 | result = 31 * result + (status != null ? status.hashCode() : 0); 87 | return result; 88 | } 89 | 90 | @Override 91 | public String toString() { 92 | return "ActionState{" + 93 | "action=" + action + 94 | ", exception=" + exception + 95 | ", status=" + status + 96 | '}'; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/CachedPipelines.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import rx.Observable; 4 | import rx.functions.Func1; 5 | import rx.subjects.PublishSubject; 6 | 7 | class CachedPipelines implements Replays { 8 | 9 | private final Observable> source; 10 | private final Observable sourceSuccess; 11 | 12 | private Observable> cachedPipeline; 13 | private Observable cachedSuccessPipeline; 14 | 15 | private final PublishSubject clearingStream; 16 | 17 | CachedPipelines(ReadActionPipe actionPipe) { 18 | this.source = actionPipe.observe(); 19 | this.sourceSuccess = actionPipe.observeSuccess(); 20 | this.clearingStream = PublishSubject.create(); 21 | createCachedPipeline(); 22 | createCachedSuccessPipeline(); 23 | } 24 | 25 | private void createCachedPipeline() { 26 | this.cachedPipeline = createPipeline(source); 27 | this.cachedPipeline.subscribe(); 28 | } 29 | 30 | private void createCachedSuccessPipeline() { 31 | this.cachedSuccessPipeline = createPipeline(sourceSuccess); 32 | this.cachedSuccessPipeline.subscribe(); 33 | } 34 | 35 | private Observable createPipeline(Observable source) { 36 | return source.mergeWith(clearingStream).replay(1).autoConnect(); 37 | } 38 | 39 | @Override public Observable> observeWithReplay() { 40 | return cachedPipeline.compose(NullFilter.>instance()); 41 | } 42 | 43 | @Override public Observable observeSuccessWithReplay() { 44 | return cachedSuccessPipeline.compose(NullFilter.instance()); 45 | } 46 | 47 | @Override public void clearReplays() { 48 | clearingStream.onNext(null); 49 | } 50 | 51 | private static class NullFilter implements Observable.Transformer { 52 | 53 | private static final NullFilter INSTANCE = new NullFilter(); 54 | 55 | public static NullFilter instance() { 56 | return (NullFilter) INSTANCE; 57 | } 58 | 59 | @Override public Observable call(Observable source) { 60 | return source.filter(new Func1() { 61 | @Override public Boolean call(Object obj) { 62 | return obj != null; 63 | } 64 | }); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/CallbackWrapper.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | final class CallbackWrapper implements ActionService.Callback { 4 | 5 | private final ActionService.Callback callback; 6 | private final Interceptor interceptor; 7 | 8 | CallbackWrapper(ActionService.Callback callback, Interceptor interceptor) { 9 | this.callback = callback; 10 | this.interceptor = interceptor; 11 | } 12 | 13 | @Override public void onStart(ActionHolder holder) { 14 | interceptor.interceptStart(holder); 15 | callback.onStart(holder); 16 | } 17 | 18 | @Override public void onProgress(ActionHolder holder, int progress) { 19 | interceptor.interceptProgress(holder, progress); 20 | callback.onProgress(holder, progress); 21 | } 22 | 23 | @Override public void onSuccess(ActionHolder holder) { 24 | interceptor.interceptSuccess(holder); 25 | callback.onSuccess(holder); 26 | } 27 | 28 | @Override public void onFail(ActionHolder holder, JanetException e) { 29 | boolean intercept = interceptor.interceptFail(holder, e); 30 | if (intercept) return; 31 | callback.onFail(holder, e); 32 | } 33 | 34 | interface Interceptor { 35 | void interceptStart(ActionHolder holder); 36 | void interceptProgress(ActionHolder holder, int progress); 37 | void interceptSuccess(ActionHolder holder); 38 | boolean interceptFail(ActionHolder holder, JanetException e); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/CancelException.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | public final class CancelException extends JanetException { 4 | 5 | public CancelException() {} 6 | } 7 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/Janet.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import io.techery.janet.internal.TypeToken; 7 | import rx.Observable; 8 | import rx.Scheduler; 9 | import rx.functions.Action0; 10 | import rx.functions.Action1; 11 | import rx.functions.Func0; 12 | import rx.functions.Func1; 13 | import rx.subjects.PublishSubject; 14 | 15 | /** 16 | * Action router that can send and receive actions using added {@linkplain ActionService services} that know 17 | * what do with theirs. Each action must to have an annotation that is defined 18 | * in {@linkplain ActionService#getSupportedAnnotationType()} and after that Janet will be able to process them. 19 | * Create instances using {@linkplain Builder the builder} where it's possible to add the necessary services 20 | * using {@linkplain Builder#addService(ActionService)} 21 | *

22 | * For example, 23 | *

{@code
 24 |  * Janet janet = new Janet.Builder()
 25 |  *         .addService(new HttpActionService(API_URL, new OkClient(), new GsonConverter(new Gson())))
 26 |  *         .build();}
 27 |  * 
28 | */ 29 | public final class Janet { 30 | 31 | private final List services; 32 | private final PublishSubject pipeline; 33 | 34 | private Janet(Builder builder) { 35 | this.services = builder.services; 36 | this.pipeline = PublishSubject.create(); 37 | connectPipeline(); 38 | } 39 | 40 | private void connectPipeline() { 41 | for (ActionService service : services) { 42 | service.setCallback(new ActionService.Callback() { 43 | @Override public void onStart(ActionHolder holder) { 44 | pipeline.onNext(new ActionPair(holder, ActionState.start(holder.action()))); 45 | } 46 | 47 | @Override public void onProgress(ActionHolder holder, int progress) { 48 | pipeline.onNext(new ActionPair(holder, ActionState.progress(holder.action(), progress))); 49 | } 50 | 51 | @Override public void onSuccess(ActionHolder holder) { 52 | pipeline.onNext(new ActionPair(holder, ActionState.success(holder.action()))); 53 | } 54 | 55 | @Override public void onFail(ActionHolder holder, JanetException e) { 56 | pipeline.onNext(new ActionPair(holder, ActionState.fail(holder.action(), e))); 57 | } 58 | }); 59 | } 60 | } 61 | 62 | /** 63 | * Create an {@linkplain ActionPipe} for working with specific actions 64 | * 65 | * @param actionClass type of action 66 | */ 67 | public
ActionPipe createPipe(Class actionClass) { 68 | return createPipe(actionClass, null); 69 | } 70 | 71 | /** 72 | * Create an {@linkplain ActionPipe} for working with specific actions 73 | * 74 | * @param actionClass type of action 75 | * @param defaultSubscribeOn default {@linkplain Scheduler} to do {@linkplain Observable#subscribeOn(Scheduler) subcribeOn} of created Observable in this ActionPipe 76 | */ 77 | public ActionPipe createPipe(final Class actionClass, Scheduler defaultSubscribeOn) { 78 | return new ActionPipe(new Func1>>() { 79 | @Override public Observable> call(A action) { 80 | return send(action); 81 | } 82 | }, new Func0>>() { 83 | @Override public Observable> call() { 84 | return pipeline.asObservable() 85 | .onBackpressureBuffer() 86 | .map(new Func1() { 87 | @Override public ActionState call(ActionPair pair) { 88 | return pair.state; 89 | } 90 | }) 91 | .filter(new Func1() { 92 | @Override public Boolean call(ActionState actionState) { 93 | return actionClass.isInstance(actionState.action); 94 | } 95 | }).compose(new CastToState()); 96 | } 97 | }, new Action1() { 98 | @Override public void call(A a) { 99 | doCancel(a); 100 | } 101 | }, defaultSubscribeOn); 102 | } 103 | 104 | private Observable> send(final A action) { 105 | return pipeline.asObservable() 106 | .filter(new Func1() { 107 | @Override public Boolean call(ActionPair pair) { 108 | return pair.holder.isOrigin(action); 109 | } 110 | }) 111 | .map(new Func1() { 112 | @Override public ActionState call(ActionPair pair) { 113 | return pair.state; 114 | } 115 | }) 116 | .compose(new CastToState()) 117 | .mergeWith(Observable.>empty() 118 | .doOnSubscribe(new Action0() { 119 | @Override public void call() { 120 | doSend(action); 121 | } 122 | })) 123 | .takeUntil(new Func1() { 124 | @Override public Boolean call(ActionState actionState) { 125 | return actionState.status == ActionState.Status.SUCCESS 126 | || actionState.status == ActionState.Status.FAIL; 127 | } 128 | }); 129 | } 130 | 131 | private void doSend(A action) { 132 | ActionService service = findService(action.getClass()); 133 | service.send(ActionHolder.create(action)); 134 | } 135 | 136 | private void doCancel(A action) { 137 | ActionHolder holder = ActionHolder.create(action); 138 | pipeline.onNext(new ActionPair(holder, ActionState.fail(action, new CancelException()))); 139 | ActionService service = findService(action.getClass()); 140 | service.cancel(holder); 141 | } 142 | 143 | private ActionService findService(Class actionClass) { 144 | for (ActionService service : services) { 145 | if (actionClass.getAnnotation(service.getSupportedAnnotationType()) != null) { 146 | return service; 147 | } 148 | } 149 | throw new JanetInternalException("Action class should be annotated by any supported annotation or check dependence of any service"); 150 | } 151 | 152 | /** 153 | * Builds an instance of {@linkplain Janet}. 154 | */ 155 | public static final class Builder { 156 | 157 | private List services = new ArrayList(); 158 | 159 | /** 160 | * Add an service for action processing 161 | */ 162 | public Builder addService(ActionService service) { 163 | if (service == null) { 164 | throw new IllegalArgumentException("ActionService may not be null."); 165 | } 166 | if (service.getSupportedAnnotationType() == null) { 167 | throw new IllegalArgumentException("the ActionService doesn't support any actions"); 168 | } 169 | services.add(service); 170 | return this; 171 | } 172 | 173 | /** 174 | * Create the {@linkplain Janet} instance using added services. 175 | */ 176 | public Janet build() { 177 | return new Janet(this); 178 | } 179 | } 180 | 181 | private final static class ActionPair { 182 | private final ActionHolder holder; 183 | private final ActionState state; 184 | 185 | private ActionPair(ActionHolder holder, ActionState state) { 186 | this.holder = holder; 187 | this.state = state; 188 | } 189 | } 190 | 191 | private final static class CastToState implements Observable.Transformer> { 192 | 193 | private final Class> type; 194 | 195 | @SuppressWarnings("unchecked") public CastToState() { 196 | type = (Class>) new TypeToken>() {}.getRawType(); 197 | } 198 | 199 | @Override public Observable> call(Observable source) { 200 | return source.cast(type); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/JanetException.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | /** 4 | * Superclass for all errors and exceptions in Janet 5 | */ 6 | public class JanetException extends Throwable { 7 | 8 | JanetException() {} 9 | 10 | public JanetException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | 14 | public JanetException(Throwable cause) { 15 | super(cause); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/JanetInternalException.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | /** 4 | * Internal exception. Something went wrong in Janet internal core 5 | */ 6 | public class JanetInternalException extends RuntimeException { 7 | 8 | public JanetInternalException() { 9 | } 10 | 11 | public JanetInternalException(String message) { 12 | super(message); 13 | } 14 | 15 | public JanetInternalException(Throwable cause) { 16 | super(cause); 17 | } 18 | 19 | public JanetInternalException(String message, Throwable cause) { 20 | super(message, cause); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ReadActionPipe.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import rx.Observable; 4 | import rx.functions.Func1; 5 | 6 | public interface ReadActionPipe extends Replays { 7 | 8 | /** Observe all states of specified action type */ 9 | Observable> observe(); 10 | 11 | /** 12 | * Observe actions with success status only. 13 | *

Use {@link #observe()} to track other statuses and exceptions. 14 | */ 15 | Observable observeSuccess(); 16 | 17 | /** Returns a presentation of the {@link ReadActionPipe} with applied predicate */ 18 | ReadActionPipe filter(Func1 predicate); 19 | } 20 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/ReadOnlyActionPipe.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import rx.Observable; 4 | import rx.functions.Func1; 5 | import rx.internal.util.UtilityFunctions; 6 | 7 | /** 8 | * Read only type of {@linkplain ActionPipe} 9 | */ 10 | public final class ReadOnlyActionPipe implements ReadActionPipe { 11 | 12 | private final ReadActionPipe actionPipe; 13 | private final Func1 filter; 14 | 15 | private final CachedPipelines cachedPipelines; 16 | 17 | public ReadOnlyActionPipe(ReadActionPipe actionPipe) { 18 | this(actionPipe, UtilityFunctions.alwaysTrue()); 19 | } 20 | 21 | public ReadOnlyActionPipe(ReadActionPipe actionPipe, Func1 filter) { 22 | this.actionPipe = actionPipe; 23 | this.filter = filter; 24 | cachedPipelines = new CachedPipelines(this); 25 | } 26 | 27 | /** {@inheritDoc} */ 28 | @Override public Observable> observe() { 29 | return actionPipe.observe().filter(new FilterStateDecorator(filter)); 30 | } 31 | 32 | /** {@inheritDoc} */ 33 | @Override public Observable> observeWithReplay() { 34 | return cachedPipelines.observeWithReplay(); 35 | } 36 | 37 | /** {@inheritDoc} */ 38 | @Override public Observable observeSuccess() { 39 | return actionPipe.observeSuccess().filter(filter); 40 | } 41 | 42 | /** {@inheritDoc} */ 43 | @Override public Observable observeSuccessWithReplay() { 44 | return cachedPipelines.observeSuccessWithReplay(); 45 | } 46 | 47 | /** {@inheritDoc} */ 48 | @Override public void clearReplays() { 49 | cachedPipelines.clearReplays(); 50 | } 51 | 52 | /** {@inheritDoc} */ 53 | @Override public ReadOnlyActionPipe filter(Func1 predicate) { 54 | return new ReadOnlyActionPipe(this, predicate); 55 | } 56 | 57 | private static class FilterStateDecorator implements Func1, Boolean> { 58 | 59 | private final Func1 filter; 60 | 61 | private FilterStateDecorator(Func1 filter) { 62 | this.filter = filter; 63 | } 64 | 65 | @Override public Boolean call(ActionState state) { 66 | return filter.call(state.action); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/Replays.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import rx.Observable; 4 | 5 | interface Replays { 6 | /** 7 | * Observe all states of specified action type with cache. 8 | * Last action state will be emitted immediately after subscribe. 9 | * 10 | * @see Observable#replay(int) 11 | */ 12 | Observable> observeWithReplay(); 13 | 14 | 15 | /** 16 | * Observe action result with cache. 17 | * Emmit the latest result, if exist, immediately after subscribe. 18 | * 19 | * @see Observable#replay(int) 20 | * @see ActionPipe#observeSuccess() 21 | */ 22 | Observable observeSuccessWithReplay(); 23 | 24 | /** 25 | * Clear cached action 26 | */ 27 | void clearReplays(); 28 | } 29 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/WriteActionPipe.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import rx.Observable; 4 | import rx.Scheduler; 5 | import rx.Subscriber; 6 | 7 | public interface WriteActionPipe { 8 | 9 | /** 10 | * Send action to {@linkplain Janet}. 11 | * 12 | * @param action prepared action for sending 13 | */ 14 | void send(A action); 15 | 16 | /** 17 | * Send action to {@linkplain Janet}. 18 | * 19 | * @param action prepared action for sending 20 | * @param subscribeOn {@linkplain Scheduler} to do {@linkplain Observable#subscribeOn(Scheduler) subcribeOn} of created Observable. 21 | */ 22 | void send(A action, Scheduler subscribeOn); 23 | 24 | /** 25 | * Cancel running action. 26 | * Action cancellation defines in relative service {@linkplain ActionService#cancel(ActionHolder)} 27 | * 28 | * @param action prepared action for cancellation 29 | */ 30 | void cancel(A action); 31 | 32 | /** 33 | * Cancel latest sent or received non-finished action 34 | */ 35 | void cancelLatest(); 36 | 37 | /** 38 | * Create {@linkplain Observable observable} to send action and receive result 39 | * in the form of action {@linkplain ActionState states} synchronously 40 | * 41 | * @param action prepared action to send 42 | */ 43 | Observable> createObservable(A action); 44 | 45 | /** 46 | * Create {@linkplain Observable observable} to send action and receive action with result synchronously 47 | *

48 | * To catch errors use {@linkplain Subscriber#onError(Throwable)} 49 | * 50 | * @param action prepared action to send 51 | */ 52 | Observable createObservableResult(A action); 53 | } 54 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/helper/ActionStateSubscriber.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.helper; 2 | 3 | import io.techery.janet.ActionState; 4 | import rx.Subscriber; 5 | import rx.exceptions.OnErrorNotImplementedException; 6 | import rx.functions.Action1; 7 | import rx.functions.Action2; 8 | 9 | import static io.techery.janet.ActionState.Status.FAIL; 10 | import static io.techery.janet.ActionState.Status.SUCCESS; 11 | 12 | /** 13 | * Subscriber that helps to handle states by status using callback 14 | */ 15 | public class ActionStateSubscriber extends Subscriber> { 16 | 17 | private Action1 onSuccess; 18 | private Action2 onFail; 19 | private Action1 onStart; 20 | private Action2 onProgress; 21 | private Action1> beforeEach; 22 | private Action1> afterEach; 23 | private Action1 onFinish; 24 | 25 | public ActionStateSubscriber onSuccess(Action1 onSuccess) { 26 | this.onSuccess = onSuccess; 27 | return this; 28 | } 29 | 30 | public ActionStateSubscriber onFail(Action2 onError) { 31 | this.onFail = onError; 32 | return this; 33 | } 34 | 35 | public ActionStateSubscriber onFinish(Action1 onFinish) { 36 | this.onFinish = onFinish; 37 | return this; 38 | } 39 | 40 | public ActionStateSubscriber onStart(Action1 onStart) { 41 | this.onStart = onStart; 42 | return this; 43 | } 44 | 45 | public ActionStateSubscriber onProgress(Action2 onProgress) { 46 | this.onProgress = onProgress; 47 | return this; 48 | } 49 | 50 | public ActionStateSubscriber beforeEach(Action1> onEach) { 51 | this.beforeEach = onEach; 52 | return this; 53 | } 54 | 55 | public ActionStateSubscriber afterEach(Action1> afterEach) { 56 | this.afterEach = afterEach; 57 | return this; 58 | } 59 | 60 | @Override public void onNext(ActionState state) { 61 | if (beforeEach != null) beforeEach.call(state); 62 | switch (state.status) { 63 | case START: 64 | if (onStart != null) onStart.call(state.action); 65 | break; 66 | case PROGRESS: 67 | if (onProgress != null) onProgress.call(state.action, state.progress); 68 | break; 69 | case SUCCESS: 70 | if (onSuccess != null) onSuccess.call(state.action); 71 | break; 72 | case FAIL: 73 | if (onFail != null) onFail.call(state.action, state.exception); 74 | break; 75 | } 76 | if (onFinish != null && (state.status == SUCCESS || state.status == FAIL)) { 77 | onFinish.call(state.action); 78 | } 79 | if (afterEach != null) afterEach.call(state); 80 | } 81 | 82 | @Override public void onCompleted() { } 83 | 84 | @Override public void onError(Throwable e) { 85 | throw new OnErrorNotImplementedException(e); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/helper/ActionStateToActionTransformer.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.helper; 2 | 3 | import io.techery.janet.ActionState; 4 | import rx.Observable; 5 | import rx.Subscriber; 6 | import rx.functions.Func1; 7 | 8 | /** 9 | * To transform {@link ActionState} to action. 10 | *

11 |  *     START - nothing
12 |  *     PROGRESS - nothing
13 |  *     SUCCESS - action with result
14 |  *     FAIL - error. it's necessary to handle it using {@link Subscriber#onError(Throwable)}
15 |  * 
16 | */ 17 | public final class ActionStateToActionTransformer implements Observable.Transformer, A> { 18 | 19 | @Override 20 | public Observable call(final Observable> observable) { 21 | return observable.flatMap(new Func1, Observable>() { 22 | @Override 23 | public Observable call(ActionState state) { 24 | switch (state.status) { 25 | case START: 26 | return Observable.empty(); 27 | case PROGRESS: 28 | return Observable.empty(); 29 | case SUCCESS: 30 | return Observable.just(state.action); 31 | case FAIL: 32 | return Observable.error(new JanetActionException(state.exception, state.action)); 33 | default: 34 | throw new IllegalArgumentException("Action status is unknown"); 35 | } 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/helper/JanetActionException.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.helper; 2 | 3 | import io.techery.janet.JanetException; 4 | 5 | /** 6 | * JanetException with an action inside 7 | */ 8 | public class JanetActionException extends JanetException { 9 | 10 | private final Object action; 11 | 12 | JanetActionException(Throwable cause, Object action) { 13 | super(cause); 14 | this.action = action; 15 | } 16 | 17 | public Object getAction() { 18 | return action; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /janet/src/main/java/io/techery/janet/internal/TypeToken.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 Google Inc. 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 io.techery.janet.internal; 18 | 19 | import java.lang.reflect.Array; 20 | import java.lang.reflect.GenericArrayType; 21 | import java.lang.reflect.ParameterizedType; 22 | import java.lang.reflect.Type; 23 | import java.lang.reflect.TypeVariable; 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | /** 28 | * Represents a generic type {@code T}. 29 | * 30 | * You can use this class to get the generic type for a class. For example, 31 | * to get the generic type for Collection<Foo>, you can use: 32 | *

33 | * Type typeOfCollectionOfFoo = new TypeToken<Collection<Foo>>(){}.getType() 34 | * 35 | * 36 | *

Assumes {@code Type} implements {@code equals()} and {@code hashCode()} 37 | * as a value (as opposed to identity) comparison. 38 | * 39 | * Also implements {@link #isAssignableFrom(Type)} to check type-safe 40 | * assignability. 41 | * 42 | * @author Bob Lee 43 | * @author Sven Mawson 44 | */ 45 | public abstract class TypeToken { 46 | 47 | final Class rawType; 48 | final Type type; 49 | 50 | /** 51 | * Constructs a new type token. Derives represented class from type 52 | * parameter. 53 | * 54 | *

Clients create an empty anonymous subclass. Doing so embeds the type 55 | * parameter in the anonymous class's type hierarchy so we can reconstitute 56 | * it at runtime despite erasure. 57 | * 58 | *

For example: 59 | * 60 | * {@literal TypeToken> t = new TypeToken>}(){} 61 | * 62 | */ 63 | @SuppressWarnings("unchecked") 64 | protected TypeToken() { 65 | this.type = getSuperclassTypeParameter(getClass()); 66 | this.rawType = (Class) getRawType(type); 67 | } 68 | 69 | /** 70 | * Unsafe. Constructs a type token manually. 71 | */ 72 | @SuppressWarnings({"unchecked"}) 73 | private TypeToken(Type type) { 74 | this.rawType = (Class) getRawType(nonNull(type, "type")); 75 | this.type = type; 76 | } 77 | 78 | private static T nonNull(T o, String message) { 79 | if (o == null) { 80 | throw new NullPointerException(message); 81 | } 82 | return o; 83 | } 84 | 85 | /** 86 | * Gets type from super class's type parameter. 87 | */ 88 | static Type getSuperclassTypeParameter(Class subclass) { 89 | Type superclass = subclass.getGenericSuperclass(); 90 | if (superclass instanceof Class) { 91 | throw new RuntimeException("Missing type parameter."); 92 | } 93 | return ((ParameterizedType) superclass).getActualTypeArguments()[0]; 94 | } 95 | 96 | /** 97 | * Gets type token from super class's type parameter. 98 | */ 99 | static TypeToken fromSuperclassTypeParameter(Class subclass) { 100 | return new SimpleTypeToken(subclass); 101 | } 102 | 103 | private static Class getRawType(Type type) { 104 | if (type instanceof Class) { 105 | // type is a normal class. 106 | return (Class) type; 107 | } else if (type instanceof ParameterizedType) { 108 | ParameterizedType parameterizedType = (ParameterizedType) type; 109 | 110 | // I'm not exactly sure why getRawType() returns Type instead of Class. 111 | // Neal isn't either but suspects some pathological case related 112 | // to nested classes exists. 113 | Type rawType = parameterizedType.getRawType(); 114 | if (rawType instanceof Class) { 115 | return (Class) rawType; 116 | } 117 | throw buildUnexpectedTypeError(rawType, Class.class); 118 | } else if (type instanceof GenericArrayType) { 119 | GenericArrayType genericArrayType = (GenericArrayType) type; 120 | 121 | // TODO(jleitch): This is not the most efficient way to handle generic 122 | // arrays, but is there another way to extract the array class in a 123 | // non-hacky way (i.e. using String value class names- "[L...")? 124 | Object rawArrayType = Array.newInstance( 125 | getRawType(genericArrayType.getGenericComponentType()), 0); 126 | return rawArrayType.getClass(); 127 | } else { 128 | throw buildUnexpectedTypeError( 129 | type, ParameterizedType.class, GenericArrayType.class); 130 | } 131 | } 132 | 133 | /** 134 | * Gets the raw type. 135 | */ 136 | public Class getRawType() { 137 | return rawType; 138 | } 139 | 140 | /** 141 | * Gets underlying {@code Type} instance. 142 | */ 143 | public Type getType() { 144 | return type; 145 | } 146 | 147 | /** 148 | * Check if this type is assignable from the given class object. 149 | */ 150 | public boolean isAssignableFrom(Class cls) { 151 | return isAssignableFrom((Type) cls); 152 | } 153 | 154 | /** 155 | * Check if this type is assignable from the given Type. 156 | */ 157 | public boolean isAssignableFrom(Type from) { 158 | if (from == null) { 159 | return false; 160 | } 161 | 162 | if (type.equals(from)) { 163 | return true; 164 | } 165 | 166 | if (type instanceof Class) { 167 | return rawType.isAssignableFrom(getRawType(from)); 168 | } else if (type instanceof ParameterizedType) { 169 | return isAssignableFrom(from, (ParameterizedType) type, 170 | new HashMap()); 171 | } else if (type instanceof GenericArrayType) { 172 | return rawType.isAssignableFrom(getRawType(from)) 173 | && isAssignableFrom(from, (GenericArrayType) type); 174 | } else { 175 | throw buildUnexpectedTypeError( 176 | type, Class.class, ParameterizedType.class, GenericArrayType.class); 177 | } 178 | } 179 | 180 | /** 181 | * Check if this type is assignable from the given type token. 182 | */ 183 | public boolean isAssignableFrom(TypeToken token) { 184 | return isAssignableFrom(token.getType()); 185 | } 186 | 187 | /** 188 | * Private helper function that performs some assignability checks for 189 | * the provided GenericArrayType. 190 | */ 191 | private static boolean isAssignableFrom(Type from, GenericArrayType to) { 192 | Type toGenericComponentType = to.getGenericComponentType(); 193 | if (toGenericComponentType instanceof ParameterizedType) { 194 | Type t = from; 195 | if (from instanceof GenericArrayType) { 196 | t = ((GenericArrayType) from).getGenericComponentType(); 197 | } else if (from instanceof Class) { 198 | Class classType = (Class) from; 199 | while (classType.isArray()) { 200 | classType = classType.getComponentType(); 201 | } 202 | t = classType; 203 | } 204 | return isAssignableFrom(t, (ParameterizedType) toGenericComponentType, 205 | new HashMap()); 206 | } 207 | // No generic defined on "to"; therefore, return true and let other 208 | // checks determine assignability 209 | return true; 210 | } 211 | 212 | /** 213 | * Private recursive helper function to actually do the type-safe checking 214 | * of assignability. 215 | */ 216 | private static boolean isAssignableFrom(Type from, ParameterizedType to, 217 | Map typeVarMap) { 218 | 219 | if (from == null) { 220 | return false; 221 | } 222 | 223 | if (to.equals(from)) { 224 | return true; 225 | } 226 | 227 | // First figure out the class and any type information. 228 | Class clazz = getRawType(from); 229 | ParameterizedType ptype = null; 230 | if (from instanceof ParameterizedType) { 231 | ptype = (ParameterizedType) from; 232 | } 233 | 234 | // Load up parameterized variable info if it was parameterized. 235 | if (ptype != null) { 236 | Type[] tArgs = ptype.getActualTypeArguments(); 237 | TypeVariable[] tParams = clazz.getTypeParameters(); 238 | for (int i = 0; i < tArgs.length; i++) { 239 | Type arg = tArgs[i]; 240 | TypeVariable var = tParams[i]; 241 | while (arg instanceof TypeVariable) { 242 | TypeVariable v = (TypeVariable) arg; 243 | arg = typeVarMap.get(v.getName()); 244 | } 245 | typeVarMap.put(var.getName(), arg); 246 | } 247 | 248 | // check if they are equivalent under our current mapping. 249 | if (typeEquals(ptype, to, typeVarMap)) { 250 | return true; 251 | } 252 | } 253 | 254 | for (Type itype : clazz.getGenericInterfaces()) { 255 | if (isAssignableFrom(itype, to, new HashMap(typeVarMap))) { 256 | return true; 257 | } 258 | } 259 | 260 | // Interfaces didn't work, try the superclass. 261 | Type sType = clazz.getGenericSuperclass(); 262 | if (isAssignableFrom(sType, to, new HashMap(typeVarMap))) { 263 | return true; 264 | } 265 | 266 | return false; 267 | } 268 | 269 | /** 270 | * Checks if two parameterized types are exactly equal, under the variable 271 | * replacement described in the typeVarMap. 272 | */ 273 | private static boolean typeEquals(ParameterizedType from, 274 | ParameterizedType to, Map typeVarMap) { 275 | if (from.getRawType().equals(to.getRawType())) { 276 | Type[] fromArgs = from.getActualTypeArguments(); 277 | Type[] toArgs = to.getActualTypeArguments(); 278 | for (int i = 0; i < fromArgs.length; i++) { 279 | if (!matches(fromArgs[i], toArgs[i], typeVarMap)) { 280 | return false; 281 | } 282 | } 283 | return true; 284 | } 285 | return false; 286 | } 287 | 288 | /** 289 | * Checks if two types are the same or are equivalent under a variable mapping 290 | * given in the type map that was provided. 291 | */ 292 | private static boolean matches(Type from, Type to, 293 | Map typeMap) { 294 | if (to.equals(from)) return true; 295 | 296 | if (from instanceof TypeVariable) { 297 | return to.equals(typeMap.get(((TypeVariable)from).getName())); 298 | } 299 | 300 | return false; 301 | } 302 | 303 | /** 304 | * Hashcode for this object. 305 | * @return hashcode for this object. 306 | */ 307 | @Override public int hashCode() { 308 | return type.hashCode(); 309 | } 310 | 311 | /** 312 | * Method to test equality. 313 | * 314 | * @return true if this object is logically equal to the specified object, false otherwise. 315 | */ 316 | @Override public boolean equals(Object o) { 317 | if (o == this) { 318 | return true; 319 | } 320 | if (!(o instanceof TypeToken)) { 321 | return false; 322 | } 323 | TypeToken t = (TypeToken) o; 324 | return type.equals(t.type); 325 | } 326 | 327 | /** 328 | * Returns a string representation of this object. 329 | * @return a string representation of this object. 330 | */ 331 | @Override public String toString() { 332 | return type instanceof Class 333 | ? ((Class) type).getName() 334 | : type.toString(); 335 | } 336 | 337 | private static AssertionError buildUnexpectedTypeError( 338 | Type token, Class... expected) { 339 | 340 | // Build exception message 341 | StringBuilder exceptionMessage = 342 | new StringBuilder("Unexpected type. Expected one of: "); 343 | for (Class clazz : expected) { 344 | exceptionMessage.append(clazz.getName()).append(", "); 345 | } 346 | exceptionMessage.append("but got: ").append(token.getClass().getName()) 347 | .append(", for type token: ").append(token.toString()).append('.'); 348 | 349 | return new AssertionError(exceptionMessage.toString()); 350 | } 351 | 352 | /** 353 | * Gets type token for the given {@code Type} instance. 354 | */ 355 | public static TypeToken get(Type type) { 356 | return new SimpleTypeToken(type); 357 | } 358 | 359 | /** 360 | * Gets type token for the given {@code Class} instance. 361 | */ 362 | public static TypeToken get(Class type) { 363 | return new SimpleTypeToken(type); 364 | } 365 | 366 | /** 367 | * Private static class to not create more anonymous classes than 368 | * necessary. 369 | */ 370 | private static class SimpleTypeToken extends TypeToken { 371 | public SimpleTypeToken(Type type) { 372 | super(type); 373 | } 374 | } 375 | } -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/AssertUtil.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import org.junit.Assert; 4 | import org.mockito.invocation.InvocationOnMock; 5 | import org.mockito.stubbing.Answer; 6 | 7 | import rx.observers.TestSubscriber; 8 | 9 | import static org.hamcrest.core.IsInstanceOf.instanceOf; 10 | 11 | public final class AssertUtil { 12 | 13 | private AssertUtil() { 14 | } 15 | 16 | public static void assertSubscriberWithSingleValue(TestSubscriber subscriber) { 17 | subscriber.assertNoErrors(); 18 | subscriber.assertValueCount(1); 19 | subscriber.assertUnsubscribed(); 20 | } 21 | 22 | public static void assertSubscriberWithoutValues(TestSubscriber subscriber) { 23 | subscriber.assertNoErrors(); 24 | subscriber.assertNoValues(); 25 | subscriber.assertUnsubscribed(); 26 | } 27 | 28 | public static void assertCanceled(TestSubscriber> subscriber) { 29 | subscriber.assertNoErrors(); 30 | subscriber.assertUnsubscribed(); 31 | AssertUtil.assertStatusCount(subscriber, ActionState.Status.START, 1); 32 | AssertUtil.assertStatusCount(subscriber, ActionState.Status.FAIL, 1); 33 | Assert.assertThat(subscriber.getOnNextEvents().get(1).exception, instanceOf(CancelException.class)); 34 | } 35 | 36 | public static void assertStatusCount(TestSubscriber> subscriber, ActionState.Status status, int count) { 37 | int i = 0; 38 | for (ActionState state : subscriber.getOnNextEvents()) { 39 | if (status == state.status) { 40 | i++; 41 | } 42 | } 43 | if (i != count) { 44 | throw new AssertionError("Number of events with status " + status + " differ; expected: " + count + ", actual: " + i); 45 | } 46 | } 47 | 48 | public static class SuccessAnswer implements Answer { 49 | 50 | private final ActionService service; 51 | 52 | public SuccessAnswer(ActionService service) { 53 | this.service = service; 54 | } 55 | 56 | @Override public Void answer(InvocationOnMock invocation) throws Throwable { 57 | ActionHolder holder = (ActionHolder) invocation.getArguments()[0]; 58 | service.callback.onStart(holder); 59 | service.callback.onProgress(holder, 1); 60 | service.callback.onProgress(holder, 99); 61 | service.callback.onSuccess(holder); 62 | return null; 63 | } 64 | 65 | public static void assertAllStatuses(TestSubscriber> subscriber) { 66 | subscriber.assertNoErrors(); 67 | subscriber.assertValueCount(4); 68 | subscriber.assertUnsubscribed(); 69 | assertStatusCount(subscriber, ActionState.Status.START, 1); 70 | assertStatusCount(subscriber, ActionState.Status.PROGRESS, 2); 71 | assertStatusCount(subscriber, ActionState.Status.SUCCESS, 1); 72 | } 73 | 74 | public static void assertNoStatuses(TestSubscriber> subscriber) { 75 | subscriber.assertNoErrors(); 76 | subscriber.assertValueCount(0); 77 | subscriber.assertUnsubscribed(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/BaseTest.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import org.junit.Before; 4 | 5 | import io.techery.janet.model.MockAction; 6 | import io.techery.janet.model.TestAction; 7 | import rx.Scheduler; 8 | 9 | import static org.mockito.Matchers.any; 10 | import static org.mockito.Mockito.doAnswer; 11 | import static org.mockito.Mockito.spy; 12 | import static org.mockito.Mockito.when; 13 | 14 | public abstract class BaseTest { 15 | 16 | protected Janet janet; 17 | protected ActionService service; 18 | protected ActionPipe actionPipe; 19 | 20 | @Before 21 | public void setup() throws JanetException { 22 | service = provideService(); 23 | janet = provideJanet(service); 24 | actionPipe = providePipe(janet); 25 | } 26 | 27 | protected ActionService provideService() throws JanetException { 28 | ActionService service = spy(ActionService.class); 29 | when(service.getSupportedAnnotationType()).thenReturn(MockAction.class); 30 | doAnswer(new AssertUtil.SuccessAnswer(service)).when(service).sendInternal(any(ActionHolder.class)); 31 | return service; 32 | } 33 | 34 | protected Janet provideJanet(ActionService service) { 35 | return new Janet.Builder().addService(service).build(); 36 | } 37 | 38 | protected ActionPipe providePipe(Janet janet) { 39 | return providePipe(janet, null); 40 | } 41 | 42 | protected ActionPipe providePipe(Janet janet, Scheduler scheduler) { 43 | return janet.createPipe(TestAction.class, scheduler); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/TestPipeOperations.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.concurrent.Executor; 6 | 7 | import io.techery.janet.helper.ActionStateSubscriber; 8 | import io.techery.janet.model.TestAction; 9 | import io.techery.janet.util.FakeExecutor; 10 | import rx.Observable; 11 | import rx.functions.Action1; 12 | import rx.functions.Func1; 13 | import rx.observers.TestSubscriber; 14 | import rx.schedulers.Schedulers; 15 | 16 | import static org.mockito.Matchers.any; 17 | import static org.mockito.Mockito.doThrow; 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.never; 20 | import static org.mockito.Mockito.spy; 21 | import static org.mockito.Mockito.times; 22 | import static org.mockito.Mockito.verify; 23 | 24 | public class TestPipeOperations extends BaseTest { 25 | 26 | 27 | @Test 28 | public void testBackpressure() { 29 | TestSubscriber subscriber = new TestSubscriber(); 30 | Observable.range(0, 10) 31 | .map(new Func1>>() { 32 | @Override 33 | public Observable> call(Integer integer) { 34 | return actionPipe.observe(); 35 | } 36 | }) 37 | .flatMap(new Func1>, Observable>>() { 38 | @Override 39 | public Observable> call(Observable> observable) { 40 | return observable.mergeWith(actionPipe.observe()); 41 | } 42 | }) 43 | .map(new Func1, String>() { 44 | @Override 45 | public String call(ActionState state) { 46 | return String.valueOf(state); 47 | } 48 | }) 49 | .distinct() //reduce pressure for subscriber 50 | .subscribe(subscriber); 51 | int magicLoopSize = 100; 52 | Observable.range(0, magicLoopSize) 53 | .observeOn(Schedulers.io()) 54 | .subscribe(new Action1() { 55 | @Override 56 | public void call(Integer integer) { 57 | actionPipe.send(new TestAction()); 58 | } 59 | }); 60 | Observable.range(0, magicLoopSize) 61 | .subscribe(new Action1() { 62 | @Override 63 | public void call(Integer integer) { 64 | actionPipe.send(new TestAction()); 65 | } 66 | }); 67 | subscriber.unsubscribe(); 68 | subscriber.assertNotCompleted(); 69 | subscriber.assertNoErrors(); 70 | } 71 | 72 | @Test 73 | public void createObservable() { 74 | TestSubscriber> subscriber = new TestSubscriber>(); 75 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 76 | AssertUtil.SuccessAnswer.assertAllStatuses(subscriber); 77 | } 78 | 79 | @Test 80 | public void sendWithObserve() { 81 | TestSubscriber> subscriber = new TestSubscriber>(); 82 | actionPipe.observe().subscribe(subscriber); 83 | actionPipe.send(new TestAction()); 84 | subscriber.unsubscribe(); 85 | AssertUtil.SuccessAnswer.assertAllStatuses(subscriber); 86 | } 87 | 88 | @Test 89 | public void sendWithObserveWithReplay() { 90 | TestSubscriber> subscriber = new TestSubscriber>(); 91 | actionPipe.send(new TestAction()); 92 | actionPipe.observeWithReplay().subscribe(subscriber); 93 | subscriber.unsubscribe(); 94 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 95 | 96 | subscriber = new TestSubscriber>(); 97 | actionPipe.observeWithReplay().subscribe(subscriber); 98 | subscriber.unsubscribe(); 99 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 100 | } 101 | 102 | @Test 103 | public void createObservableSuccess() { 104 | TestSubscriber subscriber = new TestSubscriber(); 105 | TestAction action = new TestAction(); 106 | actionPipe.createObservableResult(action).subscribe(subscriber); 107 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 108 | subscriber.assertValue(action); 109 | } 110 | 111 | @Test 112 | public void sendWithObserveSuccess() { 113 | TestSubscriber subscriber = new TestSubscriber(); 114 | TestAction action = new TestAction(); 115 | actionPipe.observeSuccess().subscribe(subscriber); 116 | actionPipe.send(action); 117 | subscriber.unsubscribe(); 118 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 119 | subscriber.assertValue(action); 120 | } 121 | 122 | @Test 123 | public void sendWithObserveSuccessWithReplay() { 124 | TestSubscriber subscriber = new TestSubscriber(); 125 | TestAction action = new TestAction(); 126 | actionPipe.send(action); 127 | actionPipe.observeSuccessWithReplay().subscribe(subscriber); 128 | subscriber.unsubscribe(); 129 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 130 | subscriber.assertValue(action); 131 | 132 | subscriber = new TestSubscriber(); 133 | actionPipe.observeSuccessWithReplay().subscribe(subscriber); 134 | subscriber.unsubscribe(); 135 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 136 | subscriber.assertValue(action); 137 | } 138 | 139 | @Test 140 | public void sendWithDefaultScheduler() { 141 | Executor fakeExecutor = spy(new FakeExecutor()); 142 | // 143 | ActionPipe actionPipe = providePipe(janet, Schedulers.from(fakeExecutor)); 144 | TestSubscriber> subscriber = new TestSubscriber>(); 145 | actionPipe.observe().subscribe(subscriber); 146 | actionPipe.send(new TestAction()); 147 | subscriber.unsubscribe(); 148 | AssertUtil.SuccessAnswer.assertAllStatuses(subscriber); 149 | verify(fakeExecutor, times(1)).execute(any(Runnable.class)); 150 | } 151 | 152 | @Test 153 | public void sendWithOverridenScheduler() { 154 | Executor defaultExecutor = spy(new FakeExecutor()); 155 | Executor newExecutor = spy(new FakeExecutor()); 156 | // 157 | ActionPipe actionPipe = providePipe(janet, Schedulers.from(defaultExecutor)); 158 | TestSubscriber> subscriber = new TestSubscriber>(); 159 | actionPipe.observe().subscribe(subscriber); 160 | actionPipe.send(new TestAction(), Schedulers.from(newExecutor)); 161 | subscriber.unsubscribe(); 162 | AssertUtil.SuccessAnswer.assertAllStatuses(subscriber); 163 | verify(defaultExecutor, never()).execute(any(Runnable.class)); 164 | verify(newExecutor, times(1)).execute(any(Runnable.class)); 165 | } 166 | 167 | @Test 168 | public void cancelActionAfterStart() { 169 | final TestAction action = new TestAction(); 170 | TestSubscriber> subscriber = new TestSubscriber>( 171 | new ActionStateSubscriber().onStart(new Action1() { 172 | @Override public void call(TestAction testAction) { 173 | actionPipe.cancel(action); 174 | } 175 | }) 176 | ); 177 | actionPipe.createObservable(action).subscribe(subscriber); 178 | AssertUtil.assertCanceled(subscriber); 179 | verify(service, times(1)).cancel(any(ActionHolder.class)); 180 | } 181 | 182 | @Test 183 | public void cancelLatestAfterStart() { 184 | final TestAction action = new TestAction(); 185 | TestSubscriber> subscriber = new TestSubscriber>( 186 | new ActionStateSubscriber().onStart(new Action1() { 187 | @Override public void call(TestAction testAction) { 188 | actionPipe.cancelLatest(); 189 | } 190 | }) 191 | ); 192 | actionPipe.createObservable(action).subscribe(subscriber); 193 | subscriber.unsubscribe(); 194 | AssertUtil.assertCanceled(subscriber); 195 | verify(service, times(1)).cancel(any(ActionHolder.class)); 196 | } 197 | 198 | @Test 199 | public void clearReplays() { 200 | actionPipe.send(new TestAction()); 201 | actionPipe.clearReplays(); 202 | TestSubscriber> subscriber = new TestSubscriber>(); 203 | actionPipe.observeWithReplay().subscribe(subscriber); 204 | subscriber.unsubscribe(); 205 | AssertUtil.assertSubscriberWithoutValues(subscriber); 206 | } 207 | 208 | @Test 209 | public void clearReplaysSuccess() { 210 | actionPipe.send(new TestAction()); 211 | actionPipe.clearReplays(); 212 | TestSubscriber subscriber = new TestSubscriber(); 213 | actionPipe.observeSuccessWithReplay().subscribe(subscriber); 214 | subscriber.unsubscribe(); 215 | AssertUtil.assertSubscriberWithoutValues(subscriber); 216 | } 217 | 218 | @Test 219 | public void statusFail() throws JanetException { 220 | TestSubscriber> subscriber = new TestSubscriber>(); 221 | doThrow(JanetException.class).when(service).sendInternal(any(ActionHolder.class)); 222 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 223 | subscriber.unsubscribe(); 224 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 225 | AssertUtil.assertStatusCount(subscriber, ActionState.Status.FAIL, 1); 226 | } 227 | 228 | @Test 229 | public void statusFailFinish() throws JanetException { 230 | ActionStateSubscriber subscriber = new ActionStateSubscriber(); 231 | Action1 onFinish = mock(Action1.class); 232 | subscriber.onFinish(onFinish); 233 | 234 | actionPipe.clearReplays(); 235 | doThrow(JanetException.class).when(service).sendInternal(any(ActionHolder.class)); 236 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 237 | subscriber.unsubscribe(); 238 | verify(onFinish, times(1)).call(any(TestAction.class)); 239 | } 240 | 241 | @Test 242 | public void statusSuccessFinish() { 243 | ActionStateSubscriber subscriber = new ActionStateSubscriber(); 244 | Action1 onFinish = mock(Action1.class); 245 | subscriber.onFinish(onFinish); 246 | 247 | actionPipe.clearReplays(); 248 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 249 | verify(onFinish, times(1)).call(any(TestAction.class)); 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/TestServiceWrapper.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.mockito.invocation.InvocationOnMock; 6 | import org.mockito.stubbing.Answer; 7 | import org.powermock.core.classloader.annotations.PrepareForTest; 8 | import org.powermock.modules.junit4.PowerMockRunner; 9 | 10 | import java.util.LinkedList; 11 | 12 | import io.techery.janet.model.TestAction; 13 | import io.techery.janet.util.StubServiceWrapper; 14 | import rx.observers.TestSubscriber; 15 | 16 | import static io.techery.janet.AssertUtil.assertStatusCount; 17 | import static org.mockito.Matchers.any; 18 | import static org.mockito.Matchers.anyInt; 19 | import static org.mockito.Mockito.doAnswer; 20 | import static org.mockito.Mockito.never; 21 | import static org.mockito.Mockito.spy; 22 | import static org.mockito.Mockito.times; 23 | import static org.mockito.Mockito.verify; 24 | 25 | @RunWith(PowerMockRunner.class) 26 | @PrepareForTest({ActionServiceWrapper.class, ActionService.class, CallbackWrapper.class}) 27 | public class TestServiceWrapper extends BaseTest { 28 | 29 | @Test public void intercept() throws JanetException { 30 | ActionService actionService = provideService(); 31 | StubServiceWrapper wrapperService = spy(new StubServiceWrapper(actionService) { 32 | @Override protected boolean onInterceptSend(ActionHolder holder) { 33 | return true; 34 | } 35 | }); 36 | Janet janet = provideJanet(wrapperService); 37 | ActionService.Callback callback = spy(actionService.callback); 38 | wrapperService.callback = callback; 39 | actionService.callback = callback; 40 | ActionPipe actionPipe = providePipe(janet); 41 | // 42 | TestSubscriber> subscriber = new TestSubscriber>(); 43 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 44 | subscriber.unsubscribe(); 45 | // 46 | verify(wrapperService, times(1)).sendInternal(any(ActionHolder.class)); 47 | verify(actionService, never()).sendInternal(any(ActionHolder.class)); 48 | verify(callback, never()).onStart(any(ActionHolder.class)); 49 | AssertUtil.assertSubscriberWithSingleValue(subscriber); 50 | } 51 | 52 | @Test public void passThrough() throws JanetException, Exception { 53 | ActionService actionService = provideService(); 54 | StubServiceWrapper wrapperService = spy(new StubServiceWrapper(actionService) { 55 | @Override protected boolean onInterceptSend(ActionHolder holder) { 56 | return false; 57 | } 58 | }); 59 | Janet janet = provideJanet(wrapperService); 60 | ActionService.Callback callback = spy(wrapperService.callback); 61 | wrapperService.callback = callback; 62 | actionService.callback = callback; 63 | ActionPipe actionPipe = providePipe(janet); 64 | // 65 | TestSubscriber> subscriber = new TestSubscriber>(); 66 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 67 | subscriber.unsubscribe(); 68 | // 69 | verify(wrapperService, times(1)).sendInternal(any(ActionHolder.class)); 70 | verify(actionService, times(1)).sendInternal(any(ActionHolder.class)); 71 | verify(callback, times(1)).onStart(any(ActionHolder.class)); 72 | AssertUtil.SuccessAnswer.assertAllStatuses(subscriber); 73 | } 74 | 75 | @Test public void multiWrapping() throws JanetException, Exception { 76 | ActionService actionService = provideService(); 77 | // 78 | doAnswer(new Answer() { 79 | @Override public Object answer(InvocationOnMock invocation) throws Throwable { 80 | ActionHolder holder = (ActionHolder) invocation.getArguments()[0]; 81 | ActionService service = (ActionService) invocation.getMock(); 82 | service.callback.onStart(holder); 83 | service.callback.onProgress(holder, 50); 84 | throw new JanetException(); 85 | } 86 | }).when(actionService).sendInternal(any(ActionHolder.class)); 87 | // 88 | LinkedList wrappers = new LinkedList(); 89 | 90 | for (int i = 0; i < 3; i++) { 91 | StubServiceWrapper wrapper = new StubServiceWrapper( 92 | wrappers.isEmpty() ? actionService : wrappers.getLast() 93 | ); 94 | wrapper.setStubCallback(spy(StubServiceWrapper.StubCallback.class)); 95 | wrappers.add(wrapper); 96 | } 97 | // 98 | Janet janet = provideJanet(wrappers.getLast()); 99 | // 100 | ActionPipe actionPipe = providePipe(janet); 101 | // 102 | TestSubscriber> subscriber = new TestSubscriber>(); 103 | actionPipe.createObservable(new TestAction()).subscribe(subscriber); 104 | subscriber.unsubscribe(); 105 | // 106 | verify(actionService, times(1)).sendInternal(any(ActionHolder.class)); 107 | for (StubServiceWrapper wrapper : wrappers) { 108 | verify(wrapper.getStubCallback(), times(1)).onInterceptSend(any(ActionHolder.class)); 109 | verify(wrapper.getStubCallback(), times(1)).onInterceptStart(any(ActionHolder.class)); 110 | verify(wrapper.getStubCallback(), times(1)).onInterceptProgress(any(ActionHolder.class), anyInt()); 111 | verify(wrapper.getStubCallback(), times(1)).onInterceptFail(any(ActionHolder.class), any(JanetException.class)); 112 | } 113 | // 114 | subscriber.assertNoErrors(); 115 | subscriber.assertValueCount(3); 116 | subscriber.assertUnsubscribed(); 117 | assertStatusCount(subscriber, ActionState.Status.START, 1); 118 | assertStatusCount(subscriber, ActionState.Status.PROGRESS, 1); 119 | assertStatusCount(subscriber, ActionState.Status.FAIL, 1); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/model/MockAction.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.model; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.Target; 5 | 6 | import static java.lang.annotation.ElementType.TYPE; 7 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 8 | 9 | @Target(TYPE) 10 | @Retention(RUNTIME) 11 | public @interface MockAction {} 12 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/model/TestAction.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.model; 2 | 3 | @MockAction 4 | public class TestAction {} 5 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/util/FakeExecutor.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.util; 2 | 3 | import java.util.concurrent.Executor; 4 | 5 | public class FakeExecutor implements Executor { 6 | @Override public void execute(Runnable command) { 7 | command.run(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /janet/src/test/java/io/techery/janet/util/StubServiceWrapper.java: -------------------------------------------------------------------------------- 1 | package io.techery.janet.util; 2 | 3 | import io.techery.janet.ActionHolder; 4 | import io.techery.janet.ActionService; 5 | import io.techery.janet.ActionServiceWrapper; 6 | import io.techery.janet.JanetException; 7 | 8 | public class StubServiceWrapper extends ActionServiceWrapper { 9 | 10 | private StubCallback stubCallback; 11 | 12 | public StubServiceWrapper(ActionService actionService) { 13 | super(actionService); 14 | } 15 | 16 | public void setStubCallback(StubCallback stubCallback) { 17 | this.stubCallback = stubCallback; 18 | } 19 | 20 | public StubCallback getStubCallback() { 21 | return stubCallback; 22 | } 23 | 24 | @Override protected boolean onInterceptSend(ActionHolder holder) { 25 | if (stubCallback != null) { 26 | stubCallback.onInterceptSend(holder); 27 | } 28 | return false; 29 | } 30 | 31 | @Override protected void onInterceptCancel(ActionHolder holder) { 32 | if (stubCallback != null) { 33 | stubCallback.onInterceptCancel(holder); 34 | } 35 | } 36 | 37 | @Override protected void onInterceptStart(ActionHolder holder) { 38 | if (stubCallback != null) { 39 | stubCallback.onInterceptStart(holder); 40 | } 41 | } 42 | 43 | @Override protected void onInterceptProgress(ActionHolder holder, int progress) { 44 | if (stubCallback != null) { 45 | stubCallback.onInterceptProgress(holder, progress); 46 | } 47 | } 48 | 49 | @Override protected void onInterceptSuccess(ActionHolder holder) { 50 | if (stubCallback != null) { 51 | stubCallback.onInterceptSuccess(holder); 52 | } 53 | } 54 | 55 | @Override protected boolean onInterceptFail(ActionHolder holder, JanetException e) { 56 | if (stubCallback != null) { 57 | stubCallback.onInterceptFail(holder, e); 58 | } 59 | return false; 60 | } 61 | 62 | public interface StubCallback { 63 | void onInterceptSend(ActionHolder holder); 64 | 65 | void onInterceptCancel(ActionHolder holder); 66 | 67 | void onInterceptStart(ActionHolder holder); 68 | 69 | void onInterceptProgress(ActionHolder holder, int progress); 70 | 71 | void onInterceptSuccess(ActionHolder holder); 72 | 73 | void onInterceptFail(ActionHolder holder, JanetException e); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':janet' --------------------------------------------------------------------------------