├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main └── java └── retrofit2 ├── BehaviorDelegate.java ├── BuiltInConverters.java ├── Call.java ├── CallAdapter.java ├── Callback.java ├── Converter.java ├── DefaultCallAdapterFactory.java ├── ExecutorCallAdapterFactory.java ├── FieldGenerator.java ├── FieldMapGenerator.java ├── MockHttpCall.java ├── OkHttpCall.java ├── ParameterHandler.java ├── Platform.java ├── RequestBuilder.java ├── Response.java ├── Retrofit.java ├── ServiceMethod.java ├── Utils.java ├── adapter └── rxjava │ ├── CompletableHelper.java │ ├── HttpException.java │ ├── OperatorMapResponseToBodyOrError.java │ ├── Result.java │ ├── RxJavaCallAdapterFactory.java │ └── SingleHelper.java ├── converter └── gson │ ├── GsonConverterFactory.java │ ├── GsonRequestBodyConverter.java │ └── GsonResponseBodyConverter.java ├── entity ├── MediaTypes.java ├── TypedFile.java ├── TypedFileMultiPartBodyConverter.java └── TypedFileMultiPartBodyConverterFactory.java ├── http ├── Body.java ├── DELETE.java ├── Field.java ├── FieldMap.java ├── FixedField.java ├── FormUrlEncoded.java ├── GET.java ├── GeneratedField.java ├── GeneratedFieldMap.java ├── HEAD.java ├── HTTP.java ├── Header.java ├── HeaderMap.java ├── Headers.java ├── Multipart.java ├── OPTIONS.java ├── PATCH.java ├── POST.java ├── PUT.java ├── Part.java ├── PartMap.java ├── Path.java ├── Query.java ├── QueryMap.java ├── Streaming.java ├── Url.java └── package-info.java ├── mock ├── BehaviorCall.java ├── Calls.java ├── MockRetrofit.java └── NetworkBehavior.java └── package-info.java /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | .idea 4 | *.iml 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HackRetrofit 2 | A hacked edition of Retrofit 2.0.2. 3 | 4 | # Introductions 5 | 6 | This is kind of for fun... 7 | 8 | 1. I added two annotations for type and method and apis of retrofit can share the annotations annotated a type: 9 | 10 | @FixField: Provide a fixed field to be posted with other dynamically passed in fields. 11 | @GeneratedField: Generated a field when a post request will be made. 12 | 13 | 2. Add new support of Mock Server. You can simply use the raw response data to mock a server response like this: 14 | 15 | ``` java 16 | @Override public Call> contributors(String owner, String repo) { 17 | return delegate.returningRawResponse(getJsonResponse()).contributors(owner, repo); 18 | } 19 | private String getJsonResponse(){ 20 | // Eg. [{"login":"Jake", "contributions":1234},{"login":"John", "contributions":1234}] 21 | return ...; 22 | } 23 | ``` 24 | 25 | 3. Add support of TypedFile. So when you want to upload a file, simply do it like this: 26 | 27 | ```java 28 | public interface FileUploadService { 29 | @Multipart 30 | @POST("upload") 31 | Call upload(@Part("description") RequestBody description, 32 | @Part TypedFile typedFile); 33 | } 34 | 35 | ... 36 | 37 | Retrofit retrofit = new Retrofit.Builder() 38 | .baseUrl("http://www.println.net/") 39 | .addConverterFactory(new TypedFileMultiPartBodyConverterFactory()) 40 | .addConverterFactory(GsonConverterFactory.create()) 41 | .build(); 42 | 43 | FileUploadService service = retrofit.create(FileUploadService.class); 44 | TypedFile typedFile = new TypedFile("aFile", filename); 45 | String descriptionString = "This is a description"; 46 | RequestBody description = 47 | RequestBody.create( 48 | MediaType.parse("multipart/form-data"), descriptionString); 49 | 50 | Call call = service.upload(description, typedFile); 51 | call.enqueue(...); 52 | ``` 53 | 54 | Name of the file to be uploaded will be used to retrieve the MediaType if you don't explictly set it. The retrieve of MediaType is still uncompleted. 55 | 56 | 57 | # More Infomations: 58 | 59 | [Android 下午茶:Hack Retrofit 之 增强参数](http://www.println.net/post/Android-Hack-Retrofit). 60 | 61 | [Android 下午茶:Hack Retrofit (2) 之 Mock Server](http://www.println.net/post/Android-Hack-Retrofit-Mock-Server) 62 | 63 | [深入浅出 Retrofit,这么牛逼的框架你们还不来看看?](http://www.println.net/post/deep-in-retrofit) 64 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | group 'net.println.gradle' 2 | version '1.0-SNAPSHOT' 3 | 4 | apply plugin: 'java' 5 | 6 | sourceCompatibility = 1.5 7 | 8 | repositories { 9 | jcenter() 10 | } 11 | 12 | dependencies { 13 | testCompile group: 'junit', name: 'junit', version: '4.11' 14 | compile fileTree(include: ['*'], dir: 'libs') 15 | compile 'com.squareup.okhttp3:okhttp:3.2.0' 16 | compile 'com.google.code.gson:gson:2.6.1' 17 | compile 'io.reactivex:rxjava:1.1.1' 18 | } 19 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bennyhuo/HackRetrofit/238aeb16a7cdddf8a63ca6026291f1ceb789b528/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 09 15:14:54 CST 2016 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.5-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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'HackRetrofit' 2 | 3 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/BehaviorDelegate.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.reflect.InvocationHandler; 20 | import java.lang.reflect.Method; 21 | import java.lang.reflect.Proxy; 22 | import java.lang.reflect.Type; 23 | import java.util.concurrent.ExecutorService; 24 | 25 | import retrofit2.mock.BehaviorCall; 26 | import retrofit2.mock.Calls; 27 | import retrofit2.mock.MockRetrofit; 28 | import retrofit2.mock.NetworkBehavior; 29 | 30 | /** 31 | * Applies {@linkplain NetworkBehavior behavior} to responses and adapts them into the appropriate 32 | * return type using the {@linkplain Retrofit#callAdapterFactories() call adapters} of 33 | * {@link Retrofit}. 34 | * 35 | * @see MockRetrofit#create(Class) 36 | */ 37 | public final class BehaviorDelegate { 38 | final Retrofit retrofit; 39 | private final NetworkBehavior behavior; 40 | private final ExecutorService executor; 41 | private final Class service; 42 | 43 | public BehaviorDelegate(Retrofit retrofit, NetworkBehavior behavior, ExecutorService executor, 44 | Class service) { 45 | this.retrofit = retrofit; 46 | this.behavior = behavior; 47 | this.executor = executor; 48 | this.service = service; 49 | } 50 | 51 | public T returningResponse(Object response) { 52 | return returning(Calls.response(response)); 53 | } 54 | 55 | @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety. 56 | public T returningRawResponse(final String rawResponse) { 57 | return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, 58 | new InvocationHandler() { 59 | @Override 60 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 61 | if (method.getDeclaringClass() == Object.class) { 62 | return method.invoke(this, args); 63 | } 64 | ServiceMethod serviceMethod = retrofit.loadServiceMethod(method); 65 | args = serviceMethod.rebuildArgs(method, args); 66 | MockHttpCall mockHttpCall = new MockHttpCall(serviceMethod, args, behavior, executor, rawResponse); 67 | return serviceMethod.callAdapter.adapt(mockHttpCall); 68 | } 69 | }); 70 | } 71 | 72 | 73 | @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety. 74 | public T returning(Call call) { 75 | final Call behaviorCall = new BehaviorCall<>(behavior, executor, call); 76 | return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[]{service}, 77 | new InvocationHandler() { 78 | @Override 79 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 80 | Type returnType = method.getGenericReturnType(); 81 | Annotation[] methodAnnotations = method.getAnnotations(); 82 | CallAdapter callAdapter = retrofit.callAdapter(returnType, methodAnnotations); 83 | return callAdapter.adapt(behaviorCall); 84 | } 85 | }); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/BuiltInConverters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.Type; 21 | import okhttp3.RequestBody; 22 | import okhttp3.ResponseBody; 23 | import retrofit2.http.Streaming; 24 | 25 | final class BuiltInConverters extends Converter.Factory { 26 | @Override 27 | public Converter responseBodyConverter(Type type, Annotation[] annotations, 28 | Retrofit retrofit) { 29 | if (type == ResponseBody.class) { 30 | if (Utils.isAnnotationPresent(annotations, Streaming.class)) { 31 | return StreamingResponseBodyConverter.INSTANCE; 32 | } 33 | return BufferingResponseBodyConverter.INSTANCE; 34 | } 35 | if (type == Void.class) { 36 | return VoidResponseBodyConverter.INSTANCE; 37 | } 38 | return null; 39 | } 40 | 41 | @Override 42 | public Converter requestBodyConverter(Type type, 43 | Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 44 | if (RequestBody.class.isAssignableFrom(Utils.getRawType(type))) { 45 | return RequestBodyConverter.INSTANCE; 46 | } 47 | return null; 48 | } 49 | 50 | @Override public Converter stringConverter(Type type, Annotation[] annotations, 51 | Retrofit retrofit) { 52 | if (type == String.class) { 53 | return StringConverter.INSTANCE; 54 | } 55 | return null; 56 | } 57 | 58 | static final class StringConverter implements Converter { 59 | static final StringConverter INSTANCE = new StringConverter(); 60 | 61 | @Override public String convert(String value) throws IOException { 62 | return value; 63 | } 64 | } 65 | 66 | static final class VoidResponseBodyConverter implements Converter { 67 | static final VoidResponseBodyConverter INSTANCE = new VoidResponseBodyConverter(); 68 | 69 | @Override public Void convert(ResponseBody value) throws IOException { 70 | value.close(); 71 | return null; 72 | } 73 | } 74 | 75 | static final class RequestBodyConverter implements Converter { 76 | static final RequestBodyConverter INSTANCE = new RequestBodyConverter(); 77 | 78 | @Override public RequestBody convert(RequestBody value) throws IOException { 79 | return value; 80 | } 81 | } 82 | 83 | static final class StreamingResponseBodyConverter 84 | implements Converter { 85 | static final StreamingResponseBodyConverter INSTANCE = new StreamingResponseBodyConverter(); 86 | 87 | @Override public ResponseBody convert(ResponseBody value) throws IOException { 88 | return value; 89 | } 90 | } 91 | 92 | static final class BufferingResponseBodyConverter 93 | implements Converter { 94 | static final BufferingResponseBodyConverter INSTANCE = new BufferingResponseBodyConverter(); 95 | 96 | @Override public ResponseBody convert(ResponseBody value) throws IOException { 97 | try { 98 | // Buffer the entire body to avoid future I/O. 99 | return Utils.buffer(value); 100 | } finally { 101 | value.close(); 102 | } 103 | } 104 | } 105 | 106 | static final class ToStringConverter implements Converter { 107 | static final ToStringConverter INSTANCE = new ToStringConverter(); 108 | 109 | @Override public String convert(Object value) { 110 | return value.toString(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/Call.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import okhttp3.Request; 20 | 21 | /** 22 | * An invocation of a Retrofit method that sends a request to a webserver and returns a response. 23 | * Each call yields its own HTTP request and response pair. Use {@link #clone} to make multiple 24 | * calls with the same parameters to the same webserver; this may be used to implement polling or 25 | * to retry a failed call. 26 | * 27 | *

Calls may be executed synchronously with {@link #execute}, or asynchronously with {@link 28 | * #enqueue}. In either case the call can be canceled at any time with {@link #cancel}. A call that 29 | * is busy writing its request or reading its response may receive a {@link IOException}; this is 30 | * working as designed. 31 | * 32 | * @param Successful response body type. 33 | */ 34 | public interface Call extends Cloneable { 35 | /** 36 | * Synchronously send the request and return its response. 37 | * 38 | * @throws IOException if a problem occurred talking to the server. 39 | * @throws RuntimeException (and subclasses) if an unexpected error occurs creating the request 40 | * or decoding the response. 41 | */ 42 | Response execute() throws IOException; 43 | 44 | /** 45 | * Asynchronously send the request and notify {@code callback} of its response or if an error 46 | * occurred talking to the server, creating the request, or processing the response. 47 | */ 48 | void enqueue(Callback callback); 49 | 50 | /** 51 | * Returns true if this call has been either {@linkplain #execute() executed} or {@linkplain 52 | * #enqueue(Callback) enqueued}. It is an error to execute or enqueue a call more than once. 53 | */ 54 | boolean isExecuted(); 55 | 56 | /** 57 | * Cancel this call. An attempt will be made to cancel in-flight calls, and if the call has not 58 | * yet been executed it never will be. 59 | */ 60 | void cancel(); 61 | 62 | /** True if {@link #cancel()} was called. */ 63 | boolean isCanceled(); 64 | 65 | /** 66 | * Create a new, identical call to this one which can be enqueued or executed even if this call 67 | * has already been. 68 | */ 69 | Call clone(); 70 | 71 | /** The original HTTP request. */ 72 | Request request(); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/CallAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.reflect.ParameterizedType; 20 | import java.lang.reflect.Type; 21 | 22 | /** 23 | * Adapts a {@link Call} into the type of {@code T}. Instances are created by {@linkplain Factory a 24 | * factory} which is {@linkplain Retrofit.Builder#addCallAdapterFactory(Factory) installed} into 25 | * the {@link Retrofit} instance. 26 | */ 27 | public interface CallAdapter { 28 | /** 29 | * Returns the value type that this adapter uses when converting the HTTP response body to a Java 30 | * object. For example, the response type for {@code Call} is {@code Repo}. This type 31 | * is used to prepare the {@code call} passed to {@code #adapt}. 32 | *

33 | * Note: This is typically not the same type as the {@code returnType} provided to this call 34 | * adapter's factory. 35 | */ 36 | Type responseType(); 37 | 38 | /** 39 | * Returns an instance of {@code T} which delegates to {@code call}. 40 | *

41 | * For example, given an instance for a hypothetical utility, {@code Async}, this instance would 42 | * return a new {@code Async} which invoked {@code call} when run. 43 | *


44 |    * @Override
45 |    * public <R> Async<R> adapt(final Call<R> call) {
46 |    *   return Async.create(new Callable<Response<R>>() {
47 |    *     @Override
48 |    *     public Response<R> call() throws Exception {
49 |    *       return call.execute();
50 |    *     }
51 |    *   });
52 |    * }
53 |    * 
54 | */ 55 | T adapt(Call call); 56 | 57 | /** 58 | * Creates {@link CallAdapter} instances based on the return type of {@linkplain 59 | * Retrofit#create(Class) the service interface} methods. 60 | */ 61 | abstract class Factory { 62 | /** 63 | * Returns a call adapter for interface methods that return {@code returnType}, or null if it 64 | * cannot be handled by this factory. 65 | */ 66 | public abstract CallAdapter get(Type returnType, Annotation[] annotations, 67 | Retrofit retrofit); 68 | 69 | /** 70 | * Extract the upper bound of the generic parameter at {@code index} from {@code type}. For 71 | * example, index 1 of {@code Map} returns {@code Runnable}. 72 | */ 73 | protected static Type getParameterUpperBound(int index, ParameterizedType type) { 74 | return Utils.getParameterUpperBound(index, type); 75 | } 76 | 77 | /** 78 | * Extract the raw class type from {@code type}. For example, the type representing 79 | * {@code List} returns {@code List.class}. 80 | */ 81 | protected static Class getRawType(Type type) { 82 | return Utils.getRawType(type); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/Callback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Square, 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 | package retrofit2; 17 | 18 | /** 19 | * Communicates responses from a server or offline requests. One and only one method will be 20 | * invoked in response to a given request. 21 | *

22 | * Callback methods are executed using the {@link Retrofit} callback executor. When none is 23 | * specified, the following defaults are used: 24 | *

    25 | *
  • Android: Callbacks are executed on the application's main (UI) thread.
  • 26 | *
  • JVM: Callbacks are executed on the background thread which performed the request.
  • 27 | *
28 | * 29 | * @param Successful response body type. 30 | */ 31 | public interface Callback { 32 | /** 33 | * Invoked for a received HTTP response. 34 | *

35 | * Note: An HTTP response may still indicate an application-level failure such as a 404 or 500. 36 | * Call {@link Response#isSuccessful()} to determine if the response indicates success. 37 | */ 38 | void onResponse(Call call, Response response); 39 | 40 | /** 41 | * Invoked when a network exception occurred talking to the server or when an unexpected 42 | * exception occurred creating the request or processing the response. 43 | */ 44 | void onFailure(Call call, Throwable t); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/Converter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Square, 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 | package retrofit2; 17 | 18 | import okhttp3.RequestBody; 19 | import okhttp3.ResponseBody; 20 | import retrofit2.http.*; 21 | 22 | import java.io.IOException; 23 | import java.lang.annotation.Annotation; 24 | import java.lang.reflect.Type; 25 | 26 | /** 27 | * Convert objects to and from their representation in HTTP. Instances are created by {@linkplain 28 | * Factory a factory} which is {@linkplain Retrofit.Builder#addConverterFactory(Factory) installed} 29 | * into the {@link Retrofit} instance. 30 | */ 31 | public interface Converter { 32 | T convert(F value) throws IOException; 33 | 34 | /** Creates {@link Converter} instances based on a type and target usage. */ 35 | abstract class Factory { 36 | /** 37 | * Returns a {@link Converter} for converting an HTTP response body to {@code type}, or null if 38 | * {@code type} cannot be handled by this factory. This is used to create converters for 39 | * response types such as {@code SimpleResponse} from a {@code Call} 40 | * declaration. 41 | */ 42 | public Converter responseBodyConverter(Type type, Annotation[] annotations, 43 | Retrofit retrofit) { 44 | return null; 45 | } 46 | 47 | /** 48 | * Returns a {@link Converter} for converting {@code type} to an HTTP request body, or null if 49 | * {@code type} cannot be handled by this factory. This is used to create converters for types 50 | * specified by {@link Body @Body}, {@link Part @Part}, and {@link PartMap @PartMap} 51 | * values. 52 | */ 53 | public Converter requestBodyConverter(Type type, 54 | Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 55 | return null; 56 | } 57 | 58 | /** 59 | * Returns a {@link Converter} for converting {@code type} to a {@link String}, or null if 60 | * {@code type} cannot be handled by this factory. This is used to create converters for types 61 | * specified by {@link Field @Field}, {@link FieldMap @FieldMap} values, 62 | * {@link Header @Header}, {@link @HeaderMap}, {@link Path @Path}, 63 | * {@link Query @Query}, and {@link QueryMap @QueryMap} values. 64 | */ 65 | public Converter stringConverter(Type type, Annotation[] annotations, 66 | Retrofit retrofit) { 67 | return null; 68 | } 69 | 70 | public Converter arbitraryConverter(Type originalType, 71 | Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit){ 72 | return null; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/DefaultCallAdapterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.lang.annotation.Annotation; 19 | import java.lang.reflect.Type; 20 | 21 | /** 22 | * Creates call adapters for that uses the same thread for both I/O and application-level 23 | * callbacks. For synchronous calls this is the application thread making the request; for 24 | * asynchronous calls this is a thread provided by OkHttp's dispatcher. 25 | */ 26 | final class DefaultCallAdapterFactory extends CallAdapter.Factory { 27 | static final CallAdapter.Factory INSTANCE = new DefaultCallAdapterFactory(); 28 | 29 | @Override 30 | public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { 31 | if (getRawType(returnType) != Call.class) { 32 | return null; 33 | } 34 | 35 | final Type responseType = Utils.getCallResponseType(returnType); 36 | return new CallAdapter>() { 37 | @Override public Type responseType() { 38 | return responseType; 39 | } 40 | 41 | @Override public Call adapt(Call call) { 42 | return call; 43 | } 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/ExecutorCallAdapterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.Type; 21 | import java.util.concurrent.Executor; 22 | import okhttp3.Request; 23 | 24 | final class ExecutorCallAdapterFactory extends CallAdapter.Factory { 25 | final Executor callbackExecutor; 26 | 27 | ExecutorCallAdapterFactory(Executor callbackExecutor) { 28 | this.callbackExecutor = callbackExecutor; 29 | } 30 | 31 | @Override 32 | public CallAdapter> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { 33 | if (getRawType(returnType) != Call.class) { 34 | return null; 35 | } 36 | final Type responseType = Utils.getCallResponseType(returnType); 37 | return new CallAdapter>() { 38 | @Override public Type responseType() { 39 | return responseType; 40 | } 41 | 42 | @Override public Call adapt(Call call) { 43 | return new ExecutorCallbackCall<>(callbackExecutor, call); 44 | } 45 | }; 46 | } 47 | 48 | static final class ExecutorCallbackCall implements Call { 49 | final Executor callbackExecutor; 50 | final Call delegate; 51 | 52 | ExecutorCallbackCall(Executor callbackExecutor, Call delegate) { 53 | this.callbackExecutor = callbackExecutor; 54 | this.delegate = delegate; 55 | } 56 | 57 | @Override public void enqueue(final Callback callback) { 58 | if (callback == null) throw new NullPointerException("callback == null"); 59 | 60 | delegate.enqueue(new Callback() { 61 | @Override public void onResponse(Call call, final Response response) { 62 | callbackExecutor.execute(new Runnable() { 63 | @Override public void run() { 64 | if (delegate.isCanceled()) { 65 | // Emulate OkHttp's behavior of throwing/delivering an IOException on cancellation. 66 | callback.onFailure(ExecutorCallbackCall.this, new IOException("Canceled")); 67 | } else { 68 | callback.onResponse(ExecutorCallbackCall.this, response); 69 | } 70 | } 71 | }); 72 | } 73 | 74 | @Override public void onFailure(Call call, final Throwable t) { 75 | callbackExecutor.execute(new Runnable() { 76 | @Override public void run() { 77 | callback.onFailure(ExecutorCallbackCall.this, t); 78 | } 79 | }); 80 | } 81 | }); 82 | } 83 | 84 | @Override public boolean isExecuted() { 85 | return delegate.isExecuted(); 86 | } 87 | 88 | @Override public Response execute() throws IOException { 89 | return delegate.execute(); 90 | } 91 | 92 | @Override public void cancel() { 93 | delegate.cancel(); 94 | } 95 | 96 | @Override public boolean isCanceled() { 97 | return delegate.isCanceled(); 98 | } 99 | 100 | @SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone. 101 | @Override public Call clone() { 102 | return new ExecutorCallbackCall<>(callbackExecutor, delegate.clone()); 103 | } 104 | 105 | @Override public Request request() { 106 | return delegate.request(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/FieldGenerator.java: -------------------------------------------------------------------------------- 1 | package retrofit2; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by benny on 4/30/16. 8 | */ 9 | public interface FieldGenerator { 10 | 11 | public String generate(Method method, Map extendFields, Object... args); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/FieldMapGenerator.java: -------------------------------------------------------------------------------- 1 | package retrofit2; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | 6 | /** 7 | * Created by benny on 4/30/16. 8 | */ 9 | public interface FieldMapGenerator { 10 | Map generate(Method method, Map extendFields, Object... args); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/MockHttpCall.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.Future; 21 | 22 | import okhttp3.MediaType; 23 | import okhttp3.Protocol; 24 | import okhttp3.Request; 25 | import okhttp3.Response.Builder; 26 | import okhttp3.ResponseBody; 27 | import okio.Buffer; 28 | import okio.BufferedSource; 29 | import okio.ForwardingSource; 30 | import okio.Okio; 31 | import retrofit2.mock.NetworkBehavior; 32 | 33 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 34 | 35 | final class MockHttpCall implements Call { 36 | private final ServiceMethod serviceMethod; 37 | private final Object[] args; 38 | private NetworkBehavior behavior; 39 | private String rawResponse; 40 | private ExecutorService executorService; 41 | private volatile Future task; 42 | private volatile boolean canceled; 43 | 44 | // All guarded by this. 45 | private boolean executed; 46 | 47 | MockHttpCall(ServiceMethod serviceMethod, Object[] args, NetworkBehavior behavior,ExecutorService executorService, String rawResponse) { 48 | this.serviceMethod = serviceMethod; 49 | this.args = args; 50 | this.behavior = behavior; 51 | this.rawResponse = rawResponse; 52 | this.executorService = executorService; 53 | } 54 | 55 | @SuppressWarnings("CloneDoesntCallSuperClone") 56 | // We are a final type & this saves clearing state. 57 | @Override 58 | public MockHttpCall clone() { 59 | return new MockHttpCall<>(serviceMethod, args, behavior, executorService, rawResponse); 60 | } 61 | 62 | @Override 63 | public synchronized Request request() { 64 | try { 65 | return serviceMethod.toRequest(args); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | return null; 70 | } 71 | 72 | @Override 73 | public void enqueue(final Callback callback) { 74 | if (callback == null) throw new NullPointerException("callback == null"); 75 | 76 | synchronized (this) { 77 | if (executed) throw new IllegalStateException("Already executed"); 78 | executed = true; 79 | } 80 | task = executorService.submit(new Runnable() { 81 | boolean delaySleep() { 82 | long sleepMs = behavior.calculateDelay(MILLISECONDS); 83 | if (sleepMs > 0) { 84 | try { 85 | Thread.sleep(sleepMs); 86 | } catch (InterruptedException e) { 87 | callback.onFailure(MockHttpCall.this, new IOException("canceled")); 88 | return false; 89 | } 90 | } 91 | return true; 92 | } 93 | 94 | @Override public void run() { 95 | if (canceled) { 96 | callback.onFailure(MockHttpCall.this, new IOException("canceled")); 97 | } else if (behavior.calculateIsFailure()) { 98 | if (delaySleep()) { 99 | callback.onFailure(MockHttpCall.this, behavior.failureException()); 100 | } 101 | } else { 102 | try { 103 | Response response = execute(); 104 | if (delaySleep()) { 105 | callback.onResponse(MockHttpCall.this, response); 106 | } 107 | } catch (Exception e) { 108 | e.printStackTrace(); 109 | if (delaySleep()) { 110 | callback.onFailure(MockHttpCall.this, e); 111 | } 112 | }; 113 | } 114 | } 115 | }); 116 | } 117 | 118 | @Override 119 | public synchronized boolean isExecuted() { 120 | return executed; 121 | } 122 | 123 | @Override 124 | public Response execute() throws IOException { 125 | Builder builder = new Builder(); 126 | builder.body(ResponseBody.create(MediaType.parse("application/json"), rawResponse.getBytes())) 127 | .code(200) 128 | .request(request()) 129 | .protocol(Protocol.HTTP_1_1); 130 | return parseResponse(builder.build()); 131 | } 132 | 133 | Response parseResponse(okhttp3.Response rawResponse) throws IOException { 134 | ResponseBody rawBody = rawResponse.body(); 135 | 136 | // Remove the body's source (the only stateful object) so we can pass the response along. 137 | rawResponse = rawResponse.newBuilder() 138 | .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) 139 | .build(); 140 | 141 | int code = rawResponse.code(); 142 | if (code < 200 || code >= 300) { 143 | try { 144 | // Buffer the entire body to avoid future I/O. 145 | ResponseBody bufferedBody = Utils.buffer(rawBody); 146 | return Response.error(bufferedBody, rawResponse); 147 | } finally { 148 | rawBody.close(); 149 | } 150 | } 151 | 152 | if (code == 204 || code == 205) { 153 | return Response.success(null, rawResponse); 154 | } 155 | 156 | ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody); 157 | try { 158 | T body = serviceMethod.toResponse(catchingBody); 159 | return Response.success(body, rawResponse); 160 | } catch (RuntimeException e) { 161 | // If the underlying source threw an exception, propagate that rather than indicating it was 162 | // a runtime exception. 163 | catchingBody.throwIfCaught(); 164 | throw e; 165 | } 166 | } 167 | 168 | public void cancel() { 169 | canceled = true; 170 | Future task = this.task; 171 | if (task != null) { 172 | task.cancel(true); 173 | } 174 | } 175 | 176 | @Override 177 | public boolean isCanceled() { 178 | return canceled; 179 | } 180 | 181 | static final class NoContentResponseBody extends ResponseBody { 182 | private final MediaType contentType; 183 | private final long contentLength; 184 | 185 | NoContentResponseBody(MediaType contentType, long contentLength) { 186 | this.contentType = contentType; 187 | this.contentLength = contentLength; 188 | } 189 | 190 | @Override 191 | public MediaType contentType() { 192 | return contentType; 193 | } 194 | 195 | @Override 196 | public long contentLength() { 197 | return contentLength; 198 | } 199 | 200 | @Override 201 | public BufferedSource source() { 202 | throw new IllegalStateException("Cannot read raw response body of a converted body."); 203 | } 204 | } 205 | 206 | static final class ExceptionCatchingRequestBody extends ResponseBody { 207 | private final ResponseBody delegate; 208 | IOException thrownException; 209 | 210 | ExceptionCatchingRequestBody(ResponseBody delegate) { 211 | this.delegate = delegate; 212 | } 213 | 214 | @Override 215 | public MediaType contentType() { 216 | return delegate.contentType(); 217 | } 218 | 219 | @Override 220 | public long contentLength() { 221 | return delegate.contentLength(); 222 | } 223 | 224 | @Override 225 | public BufferedSource source() { 226 | return Okio.buffer(new ForwardingSource(delegate.source()) { 227 | @Override 228 | public long read(Buffer sink, long byteCount) throws IOException { 229 | try { 230 | return super.read(sink, byteCount); 231 | } catch (IOException e) { 232 | thrownException = e; 233 | throw e; 234 | } 235 | } 236 | }); 237 | } 238 | 239 | @Override 240 | public void close() { 241 | delegate.close(); 242 | } 243 | 244 | void throwIfCaught() throws IOException { 245 | if (thrownException != null) { 246 | throw thrownException; 247 | } 248 | } 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/OkHttpCall.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import okhttp3.MediaType; 20 | import okhttp3.Request; 21 | import okhttp3.ResponseBody; 22 | import okio.Buffer; 23 | import okio.BufferedSource; 24 | import okio.ForwardingSource; 25 | import okio.Okio; 26 | 27 | final class OkHttpCall implements Call { 28 | private final ServiceMethod serviceMethod; 29 | private final Object[] args; 30 | 31 | private volatile boolean canceled; 32 | 33 | // All guarded by this. 34 | private okhttp3.Call rawCall; 35 | private Throwable creationFailure; // Either a RuntimeException or IOException. 36 | private boolean executed; 37 | 38 | OkHttpCall(ServiceMethod serviceMethod, Object[] args) { 39 | this.serviceMethod = serviceMethod; 40 | this.args = args; 41 | } 42 | 43 | @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state. 44 | @Override public OkHttpCall clone() { 45 | return new OkHttpCall<>(serviceMethod, args); 46 | } 47 | 48 | @Override public synchronized Request request() { 49 | okhttp3.Call call = rawCall; 50 | if (call != null) { 51 | return call.request(); 52 | } 53 | if (creationFailure != null) { 54 | if (creationFailure instanceof IOException) { 55 | throw new RuntimeException("Unable to create request.", creationFailure); 56 | } else { 57 | throw (RuntimeException) creationFailure; 58 | } 59 | } 60 | try { 61 | return (rawCall = createRawCall()).request(); 62 | } catch (RuntimeException e) { 63 | creationFailure = e; 64 | throw e; 65 | } catch (IOException e) { 66 | creationFailure = e; 67 | throw new RuntimeException("Unable to create request.", e); 68 | } 69 | } 70 | 71 | @Override public void enqueue(final Callback callback) { 72 | if (callback == null) throw new NullPointerException("callback == null"); 73 | 74 | okhttp3.Call call; 75 | Throwable failure; 76 | 77 | synchronized (this) { 78 | if (executed) throw new IllegalStateException("Already executed."); 79 | executed = true; 80 | 81 | call = rawCall; 82 | failure = creationFailure; 83 | if (call == null && failure == null) { 84 | try { 85 | call = rawCall = createRawCall(); 86 | } catch (Throwable t) { 87 | failure = creationFailure = t; 88 | } 89 | } 90 | } 91 | 92 | if (failure != null) { 93 | callback.onFailure(this, failure); 94 | return; 95 | } 96 | 97 | if (canceled) { 98 | call.cancel(); 99 | } 100 | 101 | call.enqueue(new okhttp3.Callback() { 102 | @Override public void onResponse(okhttp3.Call call, okhttp3.Response rawResponse) 103 | throws IOException { 104 | Response response; 105 | try { 106 | response = parseResponse(rawResponse); 107 | } catch (Throwable e) { 108 | callFailure(e); 109 | return; 110 | } 111 | callSuccess(response); 112 | } 113 | 114 | @Override public void onFailure(okhttp3.Call call, IOException e) { 115 | try { 116 | callback.onFailure(OkHttpCall.this, e); 117 | } catch (Throwable t) { 118 | t.printStackTrace(); 119 | } 120 | } 121 | 122 | private void callFailure(Throwable e) { 123 | try { 124 | callback.onFailure(OkHttpCall.this, e); 125 | } catch (Throwable t) { 126 | t.printStackTrace(); 127 | } 128 | } 129 | 130 | private void callSuccess(Response response) { 131 | try { 132 | callback.onResponse(OkHttpCall.this, response); 133 | } catch (Throwable t) { 134 | t.printStackTrace(); 135 | } 136 | } 137 | }); 138 | } 139 | 140 | @Override public synchronized boolean isExecuted() { 141 | return executed; 142 | } 143 | 144 | @Override public Response execute() throws IOException { 145 | okhttp3.Call call; 146 | 147 | synchronized (this) { 148 | if (executed) throw new IllegalStateException("Already executed."); 149 | executed = true; 150 | 151 | if (creationFailure != null) { 152 | if (creationFailure instanceof IOException) { 153 | throw (IOException) creationFailure; 154 | } else { 155 | throw (RuntimeException) creationFailure; 156 | } 157 | } 158 | 159 | call = rawCall; 160 | if (call == null) { 161 | try { 162 | call = rawCall = createRawCall(); 163 | } catch (IOException | RuntimeException e) { 164 | creationFailure = e; 165 | throw e; 166 | } 167 | } 168 | } 169 | 170 | if (canceled) { 171 | call.cancel(); 172 | } 173 | 174 | return parseResponse(call.execute()); 175 | } 176 | 177 | private okhttp3.Call createRawCall() throws IOException { 178 | Request request = serviceMethod.toRequest(args); 179 | okhttp3.Call call = serviceMethod.callFactory.newCall(request); 180 | if (call == null) { 181 | throw new NullPointerException("Call.Factory returned null."); 182 | } 183 | return call; 184 | } 185 | 186 | Response parseResponse(okhttp3.Response rawResponse) throws IOException { 187 | ResponseBody rawBody = rawResponse.body(); 188 | 189 | // Remove the body's source (the only stateful object) so we can pass the response along. 190 | rawResponse = rawResponse.newBuilder() 191 | .body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength())) 192 | .build(); 193 | 194 | int code = rawResponse.code(); 195 | if (code < 200 || code >= 300) { 196 | try { 197 | // Buffer the entire body to avoid future I/O. 198 | ResponseBody bufferedBody = Utils.buffer(rawBody); 199 | return Response.error(bufferedBody, rawResponse); 200 | } finally { 201 | rawBody.close(); 202 | } 203 | } 204 | 205 | if (code == 204 || code == 205) { 206 | return Response.success(null, rawResponse); 207 | } 208 | 209 | ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody); 210 | try { 211 | T body = serviceMethod.toResponse(catchingBody); 212 | return Response.success(body, rawResponse); 213 | } catch (RuntimeException e) { 214 | // If the underlying source threw an exception, propagate that rather than indicating it was 215 | // a runtime exception. 216 | catchingBody.throwIfCaught(); 217 | throw e; 218 | } 219 | } 220 | 221 | public void cancel() { 222 | canceled = true; 223 | 224 | okhttp3.Call call; 225 | synchronized (this) { 226 | call = rawCall; 227 | } 228 | if (call != null) { 229 | call.cancel(); 230 | } 231 | } 232 | 233 | @Override public boolean isCanceled() { 234 | return canceled; 235 | } 236 | 237 | static final class NoContentResponseBody extends ResponseBody { 238 | private final MediaType contentType; 239 | private final long contentLength; 240 | 241 | NoContentResponseBody(MediaType contentType, long contentLength) { 242 | this.contentType = contentType; 243 | this.contentLength = contentLength; 244 | } 245 | 246 | @Override public MediaType contentType() { 247 | return contentType; 248 | } 249 | 250 | @Override public long contentLength() { 251 | return contentLength; 252 | } 253 | 254 | @Override public BufferedSource source() { 255 | throw new IllegalStateException("Cannot read raw response body of a converted body."); 256 | } 257 | } 258 | 259 | static final class ExceptionCatchingRequestBody extends ResponseBody { 260 | private final ResponseBody delegate; 261 | IOException thrownException; 262 | 263 | ExceptionCatchingRequestBody(ResponseBody delegate) { 264 | this.delegate = delegate; 265 | } 266 | 267 | @Override public MediaType contentType() { 268 | return delegate.contentType(); 269 | } 270 | 271 | @Override public long contentLength() { 272 | return delegate.contentLength(); 273 | } 274 | 275 | @Override public BufferedSource source() { 276 | return Okio.buffer(new ForwardingSource(delegate.source()) { 277 | @Override public long read(Buffer sink, long byteCount) throws IOException { 278 | try { 279 | return super.read(sink, byteCount); 280 | } catch (IOException e) { 281 | thrownException = e; 282 | throw e; 283 | } 284 | } 285 | }); 286 | } 287 | 288 | @Override public void close() { 289 | delegate.close(); 290 | } 291 | 292 | void throwIfCaught() throws IOException { 293 | if (thrownException != null) { 294 | throw thrownException; 295 | } 296 | } 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/ParameterHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import okhttp3.Headers; 19 | import okhttp3.MultipartBody; 20 | import okhttp3.RequestBody; 21 | import retrofit2.entity.TypedFile; 22 | 23 | import java.io.IOException; 24 | import java.lang.reflect.Array; 25 | import java.util.Map; 26 | 27 | import static retrofit2.Utils.checkNotNull; 28 | 29 | abstract class ParameterHandler { 30 | abstract void apply(RequestBuilder builder, T value) throws IOException; 31 | 32 | final ParameterHandler> iterable() { 33 | return new ParameterHandler>() { 34 | @Override void apply(RequestBuilder builder, Iterable values) throws IOException { 35 | if (values == null) return; // Skip null values. 36 | 37 | for (T value : values) { 38 | ParameterHandler.this.apply(builder, value); 39 | } 40 | } 41 | }; 42 | } 43 | 44 | final ParameterHandler array() { 45 | return new ParameterHandler() { 46 | @Override void apply(RequestBuilder builder, Object values) throws IOException { 47 | if (values == null) return; // Skip null values. 48 | 49 | for (int i = 0, size = Array.getLength(values); i < size; i++) { 50 | //noinspection unchecked 51 | ParameterHandler.this.apply(builder, (T) Array.get(values, i)); 52 | } 53 | } 54 | }; 55 | } 56 | 57 | static final class RelativeUrl extends ParameterHandler { 58 | @Override void apply(RequestBuilder builder, Object value) { 59 | builder.setRelativeUrl(value); 60 | } 61 | } 62 | 63 | static final class Header extends ParameterHandler { 64 | private final String name; 65 | private final Converter valueConverter; 66 | 67 | Header(String name, Converter valueConverter) { 68 | this.name = checkNotNull(name, "name == null"); 69 | this.valueConverter = valueConverter; 70 | } 71 | 72 | @Override void apply(RequestBuilder builder, T value) throws IOException { 73 | if (value == null) return; // Skip null values. 74 | builder.addHeader(name, valueConverter.convert(value)); 75 | } 76 | } 77 | 78 | static final class Path extends ParameterHandler { 79 | private final String name; 80 | private final Converter valueConverter; 81 | private final boolean encoded; 82 | 83 | Path(String name, Converter valueConverter, boolean encoded) { 84 | this.name = checkNotNull(name, "name == null"); 85 | this.valueConverter = valueConverter; 86 | this.encoded = encoded; 87 | } 88 | 89 | @Override void apply(RequestBuilder builder, T value) throws IOException { 90 | if (value == null) { 91 | throw new IllegalArgumentException( 92 | "Path parameter \"" + name + "\" value must not be null."); 93 | } 94 | builder.addPathParam(name, valueConverter.convert(value), encoded); 95 | } 96 | } 97 | 98 | static final class Query extends ParameterHandler { 99 | private final String name; 100 | private final Converter valueConverter; 101 | private final boolean encoded; 102 | 103 | Query(String name, Converter valueConverter, boolean encoded) { 104 | this.name = checkNotNull(name, "name == null"); 105 | this.valueConverter = valueConverter; 106 | this.encoded = encoded; 107 | } 108 | 109 | @Override void apply(RequestBuilder builder, T value) throws IOException { 110 | if (value == null) return; // Skip null values. 111 | builder.addQueryParam(name, valueConverter.convert(value), encoded); 112 | } 113 | } 114 | 115 | static final class QueryMap extends ParameterHandler> { 116 | private final Converter valueConverter; 117 | private final boolean encoded; 118 | 119 | QueryMap(Converter valueConverter, boolean encoded) { 120 | this.valueConverter = valueConverter; 121 | this.encoded = encoded; 122 | } 123 | 124 | @Override void apply(RequestBuilder builder, Map value) throws IOException { 125 | if (value == null) { 126 | throw new IllegalArgumentException("Query map was null."); 127 | } 128 | 129 | for (Map.Entry entry : value.entrySet()) { 130 | String entryKey = entry.getKey(); 131 | if (entryKey == null) { 132 | throw new IllegalArgumentException("Query map contained null key."); 133 | } 134 | T entryValue = entry.getValue(); 135 | if (entryValue == null) { 136 | throw new IllegalArgumentException( 137 | "Query map contained null value for key '" + entryKey + "'."); 138 | } 139 | builder.addQueryParam(entryKey, valueConverter.convert(entryValue), encoded); 140 | } 141 | } 142 | } 143 | 144 | static final class HeaderMap extends ParameterHandler> { 145 | private final Converter valueConverter; 146 | 147 | HeaderMap(Converter valueConverter) { 148 | this.valueConverter = valueConverter; 149 | } 150 | 151 | @Override void apply(RequestBuilder builder, Map value) throws IOException { 152 | if (value == null) { 153 | throw new IllegalArgumentException("Header map was null."); 154 | } 155 | 156 | for (Map.Entry entry : value.entrySet()) { 157 | String headerName = entry.getKey(); 158 | if (headerName == null) { 159 | throw new IllegalArgumentException("Header map contained null key."); 160 | } 161 | T headerValue = entry.getValue(); 162 | if (headerValue == null) { 163 | throw new IllegalArgumentException( 164 | "Header map contained null value for key '" + headerName + "'."); 165 | } 166 | builder.addHeader(headerName, valueConverter.convert(headerValue)); 167 | } 168 | } 169 | } 170 | 171 | static final class Field extends ParameterHandler { 172 | private final String name; 173 | private final Converter valueConverter; 174 | private final boolean encoded; 175 | 176 | Field(String name, Converter valueConverter, boolean encoded) { 177 | this.name = checkNotNull(name, "name == null"); 178 | this.valueConverter = valueConverter; 179 | this.encoded = encoded; 180 | } 181 | 182 | @Override void apply(RequestBuilder builder, T value) throws IOException { 183 | if (value == null) return; // Skip null values. 184 | builder.addFormField(name, valueConverter.convert(value), encoded); 185 | } 186 | } 187 | 188 | static final class FieldMap extends ParameterHandler> { 189 | private final Converter valueConverter; 190 | private final boolean encoded; 191 | 192 | FieldMap(Converter valueConverter, boolean encoded) { 193 | this.valueConverter = valueConverter; 194 | this.encoded = encoded; 195 | } 196 | 197 | @Override void apply(RequestBuilder builder, Map value) throws IOException { 198 | if (value == null) { 199 | throw new IllegalArgumentException("Field map was null."); 200 | } 201 | 202 | for (Map.Entry entry : value.entrySet()) { 203 | String entryKey = entry.getKey(); 204 | if (entryKey == null) { 205 | throw new IllegalArgumentException("Field map contained null key."); 206 | } 207 | T entryValue = entry.getValue(); 208 | if (entryValue == null) { 209 | throw new IllegalArgumentException( 210 | "Field map contained null value for key '" + entryKey + "'."); 211 | } 212 | builder.addFormField(entryKey, valueConverter.convert(entryValue), encoded); 213 | } 214 | } 215 | } 216 | 217 | static final class Part extends ParameterHandler { 218 | private final Headers headers; 219 | private final Converter converter; 220 | 221 | Part(Headers headers, Converter converter) { 222 | this.headers = headers; 223 | this.converter = converter; 224 | } 225 | 226 | @Override void apply(RequestBuilder builder, T value) { 227 | if (value == null) return; // Skip null values. 228 | 229 | RequestBody body; 230 | try { 231 | body = converter.convert(value); 232 | } catch (IOException e) { 233 | throw new RuntimeException("Unable to convert " + value + " to RequestBody", e); 234 | } 235 | builder.addPart(headers, body); 236 | } 237 | } 238 | 239 | static final class RawPart extends ParameterHandler { 240 | static final RawPart INSTANCE = new RawPart(); 241 | 242 | private RawPart() { 243 | } 244 | 245 | @Override void apply(RequestBuilder builder, MultipartBody.Part value) throws IOException { 246 | if (value != null) { // Skip null values. 247 | builder.addPart(value); 248 | } 249 | } 250 | } 251 | 252 | static final class TypedFileHandler extends ParameterHandler{ 253 | 254 | private final Converter converter; 255 | 256 | TypedFileHandler(Converter converter) { 257 | this.converter = converter; 258 | } 259 | 260 | @Override 261 | void apply(RequestBuilder builder, TypedFile value) throws IOException { 262 | if(value != null){ 263 | builder.addPart(converter.convert(value)); 264 | } 265 | } 266 | } 267 | 268 | static final class PartMap extends ParameterHandler> { 269 | private final Converter valueConverter; 270 | private final String transferEncoding; 271 | 272 | PartMap(Converter valueConverter, String transferEncoding) { 273 | this.valueConverter = valueConverter; 274 | this.transferEncoding = transferEncoding; 275 | } 276 | 277 | @Override void apply(RequestBuilder builder, Map value) throws IOException { 278 | if (value == null) { 279 | throw new IllegalArgumentException("Part map was null."); 280 | } 281 | 282 | for (Map.Entry entry : value.entrySet()) { 283 | String entryKey = entry.getKey(); 284 | if (entryKey == null) { 285 | throw new IllegalArgumentException("Part map contained null key."); 286 | } 287 | T entryValue = entry.getValue(); 288 | if (entryValue == null) { 289 | throw new IllegalArgumentException( 290 | "Part map contained null value for key '" + entryKey + "'."); 291 | } 292 | 293 | Headers headers = Headers.of( 294 | "Content-Disposition", "form-data; name=\"" + entryKey + "\"", 295 | "Content-Transfer-Encoding", transferEncoding); 296 | 297 | builder.addPart(headers, valueConverter.convert(entryValue)); 298 | } 299 | } 300 | } 301 | 302 | static final class Body extends ParameterHandler { 303 | private final Converter converter; 304 | 305 | Body(Converter converter) { 306 | this.converter = converter; 307 | } 308 | 309 | @Override void apply(RequestBuilder builder, T value) { 310 | if (value == null) { 311 | throw new IllegalArgumentException("Body parameter value must not be null."); 312 | } 313 | RequestBody body; 314 | try { 315 | body = converter.convert(value); 316 | } catch (IOException e) { 317 | throw new RuntimeException("Unable to convert " + value + " to RequestBody", e); 318 | } 319 | builder.setBody(body); 320 | } 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/Platform.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2; 17 | 18 | import java.lang.reflect.InvocationTargetException; 19 | import java.lang.reflect.Method; 20 | import java.util.concurrent.Executor; 21 | 22 | //import java.lang.invoke.MethodHandles.Lookup; 23 | //import java.lang.reflect.Constructor; 24 | //import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; 25 | 26 | class Platform { 27 | private static final Platform PLATFORM = findPlatform(); 28 | 29 | static Platform get() { 30 | return PLATFORM; 31 | } 32 | 33 | private static Platform findPlatform() { 34 | // try { 35 | // Class.forName("android.os.Build"); 36 | // if (Build.VERSION.SDK_INT != 0) { 37 | // return new Android(); 38 | // } 39 | // } catch (ClassNotFoundException ignored) { 40 | // } 41 | // try { 42 | // Class.forName("java.util.Optional"); 43 | // return new Java8(); 44 | // } catch (ClassNotFoundException ignored) { 45 | // } 46 | try { 47 | Class.forName("org.robovm.apple.foundation.NSObject"); 48 | return new IOS(); 49 | } catch (ClassNotFoundException ignored) { 50 | } 51 | return new Platform(); 52 | } 53 | 54 | Executor defaultCallbackExecutor() { 55 | return null; 56 | } 57 | 58 | CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) { 59 | if (callbackExecutor != null) { 60 | return new ExecutorCallAdapterFactory(callbackExecutor); 61 | } 62 | return DefaultCallAdapterFactory.INSTANCE; 63 | } 64 | 65 | boolean isDefaultMethod(Method method) { 66 | return false; 67 | } 68 | 69 | Object invokeDefaultMethod(Method method, Class declaringClass, Object object, Object... args) 70 | throws Throwable { 71 | throw new UnsupportedOperationException(); 72 | } 73 | 74 | // @IgnoreJRERequirement // Only classloaded and used on Java 8. 75 | // static class Java8 extends Platform { 76 | // @Override boolean isDefaultMethod(Method method) { 77 | // return method.isDefault(); 78 | // } 79 | // 80 | // @Override Object invokeDefaultMethod(Method method, Class declaringClass, Object object, 81 | // Object... args) throws Throwable { 82 | // // Because the service interface might not be public, we need to use a MethodHandle lookup 83 | // // that ignores the visibility of the declaringClass. 84 | // Constructor constructor = Lookup.class.getDeclaredConstructor(Class.class, int.class); 85 | // constructor.setAccessible(true); 86 | // return constructor.newInstance(declaringClass, -1 /* trusted */) 87 | // .unreflectSpecial(method, declaringClass) 88 | // .bindTo(object) 89 | // .invokeWithArguments(args); 90 | // } 91 | // } 92 | 93 | // static class Android extends Platform { 94 | // @Override public Executor defaultCallbackExecutor() { 95 | // return new MainThreadExecutor(); 96 | // } 97 | // 98 | // @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) { 99 | // return new ExecutorCallAdapterFactory(callbackExecutor); 100 | // } 101 | 102 | // static class MainThreadExecutor implements Executor { 103 | // private final Handler handler = new Handler(Looper.getMainLooper()); 104 | // 105 | // @Override public void execute(Runnable r) { 106 | // handler.post(r); 107 | // } 108 | // } 109 | // } 110 | 111 | static class IOS extends Platform { 112 | @Override public Executor defaultCallbackExecutor() { 113 | return new MainThreadExecutor(); 114 | } 115 | 116 | @Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) { 117 | return new ExecutorCallAdapterFactory(callbackExecutor); 118 | } 119 | 120 | static class MainThreadExecutor implements Executor { 121 | private static Object queue; 122 | private static Method addOperation; 123 | 124 | static { 125 | try { 126 | // queue = NSOperationQueue.getMainQueue(); 127 | Class operationQueue = Class.forName("org.robovm.apple.foundation.NSOperationQueue"); 128 | queue = operationQueue.getDeclaredMethod("getMainQueue").invoke(null); 129 | addOperation = operationQueue.getDeclaredMethod("addOperation", Runnable.class); 130 | } catch (Exception e) { 131 | throw new AssertionError(e); 132 | } 133 | } 134 | 135 | @Override public void execute(Runnable r) { 136 | try { 137 | // queue.addOperation(r); 138 | addOperation.invoke(queue, r); 139 | } catch (IllegalArgumentException | IllegalAccessException e) { 140 | throw new AssertionError(e); 141 | } catch (InvocationTargetException e) { 142 | Throwable cause = e.getCause(); 143 | if (cause instanceof RuntimeException) { 144 | throw (RuntimeException) cause; 145 | } else if (cause instanceof Error) { 146 | throw (Error) cause; 147 | } 148 | throw new RuntimeException(cause); 149 | } 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/RequestBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 Square, 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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import okhttp3.FormBody; 20 | import okhttp3.Headers; 21 | import okhttp3.HttpUrl; 22 | import okhttp3.MediaType; 23 | import okhttp3.MultipartBody; 24 | import okhttp3.Request; 25 | import okhttp3.RequestBody; 26 | import okio.Buffer; 27 | import okio.BufferedSink; 28 | 29 | final class RequestBuilder { 30 | private static final char[] HEX_DIGITS = 31 | { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; 32 | private static final String PATH_SEGMENT_ALWAYS_ENCODE_SET = " \"<>^`{}|\\?#"; 33 | 34 | private final String method; 35 | 36 | private final HttpUrl baseUrl; 37 | private String relativeUrl; 38 | private HttpUrl.Builder urlBuilder; 39 | 40 | private final Request.Builder requestBuilder; 41 | private MediaType contentType; 42 | 43 | private final boolean hasBody; 44 | private MultipartBody.Builder multipartBuilder; 45 | private FormBody.Builder formBuilder; 46 | private RequestBody body; 47 | 48 | RequestBuilder(String method, HttpUrl baseUrl, String relativeUrl, Headers headers, 49 | MediaType contentType, boolean hasBody, boolean isFormEncoded, boolean isMultipart) { 50 | this.method = method; 51 | this.baseUrl = baseUrl; 52 | this.relativeUrl = relativeUrl; 53 | this.requestBuilder = new Request.Builder(); 54 | this.contentType = contentType; 55 | this.hasBody = hasBody; 56 | 57 | if (headers != null) { 58 | requestBuilder.headers(headers); 59 | } 60 | 61 | if (isFormEncoded) { 62 | // Will be set to 'body' in 'build'. 63 | formBuilder = new FormBody.Builder(); 64 | } else if (isMultipart) { 65 | // Will be set to 'body' in 'build'. 66 | multipartBuilder = new MultipartBody.Builder(); 67 | multipartBuilder.setType(MultipartBody.FORM); 68 | } 69 | } 70 | 71 | void setRelativeUrl(Object relativeUrl) { 72 | if (relativeUrl == null) throw new NullPointerException("@Url parameter is null."); 73 | this.relativeUrl = relativeUrl.toString(); 74 | } 75 | 76 | void addHeader(String name, String value) { 77 | if ("Content-Type".equalsIgnoreCase(name)) { 78 | contentType = MediaType.parse(value); 79 | } else { 80 | requestBuilder.addHeader(name, value); 81 | } 82 | } 83 | 84 | void addPathParam(String name, String value, boolean encoded) { 85 | if (relativeUrl == null) { 86 | // The relative URL is cleared when the first query parameter is set. 87 | throw new AssertionError(); 88 | } 89 | relativeUrl = relativeUrl.replace("{" + name + "}", canonicalizeForPath(value, encoded)); 90 | } 91 | 92 | private static String canonicalizeForPath(String input, boolean alreadyEncoded) { 93 | int codePoint; 94 | for (int i = 0, limit = input.length(); i < limit; i += Character.charCount(codePoint)) { 95 | codePoint = input.codePointAt(i); 96 | if (codePoint < 0x20 || codePoint >= 0x7f 97 | || PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1 98 | || (!alreadyEncoded && (codePoint == '/' || codePoint == '%'))) { 99 | // Slow path: the character at i requires encoding! 100 | Buffer out = new Buffer(); 101 | out.writeUtf8(input, 0, i); 102 | canonicalizeForPath(out, input, i, limit, alreadyEncoded); 103 | return out.readUtf8(); 104 | } 105 | } 106 | 107 | // Fast path: no characters required encoding. 108 | return input; 109 | } 110 | 111 | private static void canonicalizeForPath(Buffer out, String input, int pos, int limit, 112 | boolean alreadyEncoded) { 113 | Buffer utf8Buffer = null; // Lazily allocated. 114 | int codePoint; 115 | for (int i = pos; i < limit; i += Character.charCount(codePoint)) { 116 | codePoint = input.codePointAt(i); 117 | if (alreadyEncoded 118 | && (codePoint == '\t' || codePoint == '\n' || codePoint == '\f' || codePoint == '\r')) { 119 | // Skip this character. 120 | } else if (codePoint < 0x20 || codePoint >= 0x7f 121 | || PATH_SEGMENT_ALWAYS_ENCODE_SET.indexOf(codePoint) != -1 122 | || (!alreadyEncoded && (codePoint == '/' || codePoint == '%'))) { 123 | // Percent encode this character. 124 | if (utf8Buffer == null) { 125 | utf8Buffer = new Buffer(); 126 | } 127 | utf8Buffer.writeUtf8CodePoint(codePoint); 128 | while (!utf8Buffer.exhausted()) { 129 | int b = utf8Buffer.readByte() & 0xff; 130 | out.writeByte('%'); 131 | out.writeByte(HEX_DIGITS[(b >> 4) & 0xf]); 132 | out.writeByte(HEX_DIGITS[b & 0xf]); 133 | } 134 | } else { 135 | // This character doesn't need encoding. Just copy it over. 136 | out.writeUtf8CodePoint(codePoint); 137 | } 138 | } 139 | } 140 | 141 | void addQueryParam(String name, String value, boolean encoded) { 142 | if (relativeUrl != null) { 143 | // Do a one-time combination of the built relative URL and the base URL. 144 | urlBuilder = baseUrl.newBuilder(relativeUrl); 145 | if (urlBuilder == null) { 146 | throw new IllegalArgumentException( 147 | "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl); 148 | } 149 | relativeUrl = null; 150 | } 151 | 152 | if (encoded) { 153 | urlBuilder.addEncodedQueryParameter(name, value); 154 | } else { 155 | urlBuilder.addQueryParameter(name, value); 156 | } 157 | } 158 | 159 | void addFormField(String name, String value, boolean encoded) { 160 | if (encoded) { 161 | formBuilder.addEncoded(name, value); 162 | } else { 163 | formBuilder.add(name, value); 164 | } 165 | } 166 | 167 | void addPart(Headers headers, RequestBody body) { 168 | multipartBuilder.addPart(headers, body); 169 | } 170 | 171 | void addPart(MultipartBody.Part part) { 172 | multipartBuilder.addPart(part); 173 | } 174 | 175 | void setBody(RequestBody body) { 176 | this.body = body; 177 | } 178 | 179 | Request build() { 180 | HttpUrl url; 181 | HttpUrl.Builder urlBuilder = this.urlBuilder; 182 | if (urlBuilder != null) { 183 | url = urlBuilder.build(); 184 | } else { 185 | // No query parameters triggered builder creation, just combine the relative URL and base URL. 186 | url = baseUrl.resolve(relativeUrl); 187 | if (url == null) { 188 | throw new IllegalArgumentException( 189 | "Malformed URL. Base: " + baseUrl + ", Relative: " + relativeUrl); 190 | } 191 | } 192 | 193 | RequestBody body = this.body; 194 | if (body == null) { 195 | // Try to pull from one of the builders. 196 | if (formBuilder != null) { 197 | body = formBuilder.build(); 198 | } else if (multipartBuilder != null) { 199 | body = multipartBuilder.build(); 200 | } else if (hasBody) { 201 | // Body is absent, make an empty body. 202 | body = RequestBody.create(null, new byte[0]); 203 | } 204 | } 205 | 206 | MediaType contentType = this.contentType; 207 | if (contentType != null) { 208 | if (body != null) { 209 | body = new ContentTypeOverridingRequestBody(body, contentType); 210 | } else { 211 | requestBuilder.addHeader("Content-Type", contentType.toString()); 212 | } 213 | } 214 | 215 | return requestBuilder 216 | .url(url) 217 | .method(method, body) 218 | .build(); 219 | } 220 | 221 | private static class ContentTypeOverridingRequestBody extends RequestBody { 222 | private final RequestBody delegate; 223 | private final MediaType contentType; 224 | 225 | ContentTypeOverridingRequestBody(RequestBody delegate, MediaType contentType) { 226 | this.delegate = delegate; 227 | this.contentType = contentType; 228 | } 229 | 230 | @Override public MediaType contentType() { 231 | return contentType; 232 | } 233 | 234 | @Override public long contentLength() throws IOException { 235 | return delegate.contentLength(); 236 | } 237 | 238 | @Override public void writeTo(BufferedSink sink) throws IOException { 239 | delegate.writeTo(sink); 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/Response.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2; 17 | 18 | import okhttp3.Headers; 19 | import okhttp3.Protocol; 20 | import okhttp3.Request; 21 | import okhttp3.ResponseBody; 22 | 23 | /** An HTTP response. */ 24 | public final class Response { 25 | /** Create a synthetic successful response with {@code body} as the deserialized body. */ 26 | public static Response success(T body) { 27 | return success(body, new okhttp3.Response.Builder() // 28 | .code(200) 29 | .message("OK") 30 | .protocol(Protocol.HTTP_1_1) 31 | .request(new Request.Builder().url("http://localhost/").build()) 32 | .build()); 33 | } 34 | 35 | /** 36 | * Create a synthetic successful response using {@code headers} with {@code body} as the 37 | * deserialized body. 38 | */ 39 | public static Response success(T body, Headers headers) { 40 | if (headers == null) throw new NullPointerException("headers == null"); 41 | return success(body, new okhttp3.Response.Builder() // 42 | .code(200) 43 | .message("OK") 44 | .protocol(Protocol.HTTP_1_1) 45 | .headers(headers) 46 | .request(new Request.Builder().url("http://localhost/").build()) 47 | .build()); 48 | } 49 | 50 | /** 51 | * Create a successful response from {@code rawResponse} with {@code body} as the deserialized 52 | * body. 53 | */ 54 | public static Response success(T body, okhttp3.Response rawResponse) { 55 | if (rawResponse == null) throw new NullPointerException("rawResponse == null"); 56 | if (!rawResponse.isSuccessful()) { 57 | throw new IllegalArgumentException("rawResponse must be successful response"); 58 | } 59 | return new Response<>(rawResponse, body, null); 60 | } 61 | 62 | /** 63 | * Create a synthetic error response with an HTTP status code of {@code code} and {@code body} 64 | * as the error body. 65 | */ 66 | public static Response error(int code, ResponseBody body) { 67 | if (code < 400) throw new IllegalArgumentException("code < 400: " + code); 68 | return error(body, new okhttp3.Response.Builder() // 69 | .code(code) 70 | .protocol(Protocol.HTTP_1_1) 71 | .request(new Request.Builder().url("http://localhost/").build()) 72 | .build()); 73 | } 74 | 75 | /** Create an error response from {@code rawResponse} with {@code body} as the error body. */ 76 | public static Response error(ResponseBody body, okhttp3.Response rawResponse) { 77 | if (body == null) throw new NullPointerException("body == null"); 78 | if (rawResponse == null) throw new NullPointerException("rawResponse == null"); 79 | if (rawResponse.isSuccessful()) { 80 | throw new IllegalArgumentException("rawResponse should not be successful response"); 81 | } 82 | return new Response<>(rawResponse, null, body); 83 | } 84 | 85 | private final okhttp3.Response rawResponse; 86 | private final T body; 87 | private final ResponseBody errorBody; 88 | 89 | private Response(okhttp3.Response rawResponse, T body, ResponseBody errorBody) { 90 | this.rawResponse = rawResponse; 91 | this.body = body; 92 | this.errorBody = errorBody; 93 | } 94 | 95 | /** The raw response from the HTTP client. */ 96 | public okhttp3.Response raw() { 97 | return rawResponse; 98 | } 99 | 100 | /** HTTP status code. */ 101 | public int code() { 102 | return rawResponse.code(); 103 | } 104 | 105 | /** HTTP status message or null if unknown. */ 106 | public String message() { 107 | return rawResponse.message(); 108 | } 109 | 110 | /** HTTP headers. */ 111 | public Headers headers() { 112 | return rawResponse.headers(); 113 | } 114 | 115 | /** Returns true if {@link #code()} is in the range [200..300). */ 116 | public boolean isSuccessful() { 117 | return rawResponse.isSuccessful(); 118 | } 119 | 120 | /** The deserialized response body of a {@linkplain #isSuccessful() successful} response. */ 121 | public T body() { 122 | return body; 123 | } 124 | 125 | /** The raw response body of an {@linkplain #isSuccessful() unsuccessful} response. */ 126 | public ResponseBody errorBody() { 127 | return errorBody; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/Utils.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 | package retrofit2; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.Array; 21 | import java.lang.reflect.GenericArrayType; 22 | import java.lang.reflect.GenericDeclaration; 23 | import java.lang.reflect.ParameterizedType; 24 | import java.lang.reflect.Type; 25 | import java.lang.reflect.TypeVariable; 26 | import java.lang.reflect.WildcardType; 27 | import java.util.Arrays; 28 | import java.util.NoSuchElementException; 29 | import okhttp3.ResponseBody; 30 | import okio.Buffer; 31 | 32 | final class Utils { 33 | static final Type[] EMPTY_TYPE_ARRAY = new Type[0]; 34 | 35 | private Utils() { 36 | // No instances. 37 | } 38 | 39 | static Class getRawType(Type type) { 40 | if (type == null) throw new NullPointerException("type == null"); 41 | 42 | if (type instanceof Class) { 43 | // Type is a normal class. 44 | return (Class) type; 45 | } 46 | if (type instanceof ParameterizedType) { 47 | ParameterizedType parameterizedType = (ParameterizedType) type; 48 | 49 | // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but 50 | // suspects some pathological case related to nested classes exists. 51 | Type rawType = parameterizedType.getRawType(); 52 | if (!(rawType instanceof Class)) throw new IllegalArgumentException(); 53 | return (Class) rawType; 54 | } 55 | if (type instanceof GenericArrayType) { 56 | Type componentType = ((GenericArrayType) type).getGenericComponentType(); 57 | return Array.newInstance(getRawType(componentType), 0).getClass(); 58 | } 59 | if (type instanceof TypeVariable) { 60 | // We could use the variable's bounds, but that won't work if there are multiple. Having a raw 61 | // type that's more general than necessary is okay. 62 | return Object.class; 63 | } 64 | if (type instanceof WildcardType) { 65 | return getRawType(((WildcardType) type).getUpperBounds()[0]); 66 | } 67 | 68 | throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " 69 | + "GenericArrayType, but <" + type + "> is of type " + type.getClass().getName()); 70 | } 71 | 72 | /** Returns true if {@code a} and {@code b} are equal. */ 73 | static boolean equals(Type a, Type b) { 74 | if (a == b) { 75 | return true; // Also handles (a == null && b == null). 76 | 77 | } else if (a instanceof Class) { 78 | return a.equals(b); // Class already specifies equals(). 79 | 80 | } else if (a instanceof ParameterizedType) { 81 | if (!(b instanceof ParameterizedType)) return false; 82 | ParameterizedType pa = (ParameterizedType) a; 83 | ParameterizedType pb = (ParameterizedType) b; 84 | return equal(pa.getOwnerType(), pb.getOwnerType()) 85 | && pa.getRawType().equals(pb.getRawType()) 86 | && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments()); 87 | 88 | } else if (a instanceof GenericArrayType) { 89 | if (!(b instanceof GenericArrayType)) return false; 90 | GenericArrayType ga = (GenericArrayType) a; 91 | GenericArrayType gb = (GenericArrayType) b; 92 | return equals(ga.getGenericComponentType(), gb.getGenericComponentType()); 93 | 94 | } else if (a instanceof WildcardType) { 95 | if (!(b instanceof WildcardType)) return false; 96 | WildcardType wa = (WildcardType) a; 97 | WildcardType wb = (WildcardType) b; 98 | return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) 99 | && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds()); 100 | 101 | } else if (a instanceof TypeVariable) { 102 | if (!(b instanceof TypeVariable)) return false; 103 | TypeVariable va = (TypeVariable) a; 104 | TypeVariable vb = (TypeVariable) b; 105 | return va.getGenericDeclaration() == vb.getGenericDeclaration() 106 | && va.getName().equals(vb.getName()); 107 | 108 | } else { 109 | return false; // This isn't a type we support! 110 | } 111 | } 112 | 113 | /** 114 | * Returns the generic supertype for {@code supertype}. For example, given a class {@code 115 | * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set} and the 116 | * result when the supertype is {@code Collection.class} is {@code Collection}. 117 | */ 118 | static Type getGenericSupertype(Type context, Class rawType, Class toResolve) { 119 | if (toResolve == rawType) return context; 120 | 121 | // We skip searching through interfaces if unknown is an interface. 122 | if (toResolve.isInterface()) { 123 | Class[] interfaces = rawType.getInterfaces(); 124 | for (int i = 0, length = interfaces.length; i < length; i++) { 125 | if (interfaces[i] == toResolve) { 126 | return rawType.getGenericInterfaces()[i]; 127 | } else if (toResolve.isAssignableFrom(interfaces[i])) { 128 | return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve); 129 | } 130 | } 131 | } 132 | 133 | // Check our supertypes. 134 | if (!rawType.isInterface()) { 135 | while (rawType != Object.class) { 136 | Class rawSupertype = rawType.getSuperclass(); 137 | if (rawSupertype == toResolve) { 138 | return rawType.getGenericSuperclass(); 139 | } else if (toResolve.isAssignableFrom(rawSupertype)) { 140 | return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve); 141 | } 142 | rawType = rawSupertype; 143 | } 144 | } 145 | 146 | // We can't resolve this further. 147 | return toResolve; 148 | } 149 | 150 | private static int indexOf(Object[] array, Object toFind) { 151 | for (int i = 0; i < array.length; i++) { 152 | if (toFind.equals(array[i])) return i; 153 | } 154 | throw new NoSuchElementException(); 155 | } 156 | 157 | private static boolean equal(Object a, Object b) { 158 | return a == b || (a != null && a.equals(b)); 159 | } 160 | 161 | static int hashCodeOrZero(Object o) { 162 | return o != null ? o.hashCode() : 0; 163 | } 164 | 165 | static String typeToString(Type type) { 166 | return type instanceof Class ? ((Class) type).getName() : type.toString(); 167 | } 168 | 169 | /** 170 | * Returns the generic form of {@code supertype}. For example, if this is {@code 171 | * ArrayList}, this returns {@code Iterable} given the input {@code 172 | * Iterable.class}. 173 | * 174 | * @param supertype a superclass of, or interface implemented by, this. 175 | */ 176 | static Type getSupertype(Type context, Class contextRawType, Class supertype) { 177 | if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException(); 178 | return resolve(context, contextRawType, 179 | getGenericSupertype(context, contextRawType, supertype)); 180 | } 181 | 182 | static Type resolve(Type context, Class contextRawType, Type toResolve) { 183 | // This implementation is made a little more complicated in an attempt to avoid object-creation. 184 | while (true) { 185 | if (toResolve instanceof TypeVariable) { 186 | TypeVariable typeVariable = (TypeVariable) toResolve; 187 | toResolve = resolveTypeVariable(context, contextRawType, typeVariable); 188 | if (toResolve == typeVariable) { 189 | return toResolve; 190 | } 191 | 192 | } else if (toResolve instanceof Class && ((Class) toResolve).isArray()) { 193 | Class original = (Class) toResolve; 194 | Type componentType = original.getComponentType(); 195 | Type newComponentType = resolve(context, contextRawType, componentType); 196 | return componentType == newComponentType ? original : new GenericArrayTypeImpl( 197 | newComponentType); 198 | 199 | } else if (toResolve instanceof GenericArrayType) { 200 | GenericArrayType original = (GenericArrayType) toResolve; 201 | Type componentType = original.getGenericComponentType(); 202 | Type newComponentType = resolve(context, contextRawType, componentType); 203 | return componentType == newComponentType ? original : new GenericArrayTypeImpl( 204 | newComponentType); 205 | 206 | } else if (toResolve instanceof ParameterizedType) { 207 | ParameterizedType original = (ParameterizedType) toResolve; 208 | Type ownerType = original.getOwnerType(); 209 | Type newOwnerType = resolve(context, contextRawType, ownerType); 210 | boolean changed = newOwnerType != ownerType; 211 | 212 | Type[] args = original.getActualTypeArguments(); 213 | for (int t = 0, length = args.length; t < length; t++) { 214 | Type resolvedTypeArgument = resolve(context, contextRawType, args[t]); 215 | if (resolvedTypeArgument != args[t]) { 216 | if (!changed) { 217 | args = args.clone(); 218 | changed = true; 219 | } 220 | args[t] = resolvedTypeArgument; 221 | } 222 | } 223 | 224 | return changed 225 | ? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args) 226 | : original; 227 | 228 | } else if (toResolve instanceof WildcardType) { 229 | WildcardType original = (WildcardType) toResolve; 230 | Type[] originalLowerBound = original.getLowerBounds(); 231 | Type[] originalUpperBound = original.getUpperBounds(); 232 | 233 | if (originalLowerBound.length == 1) { 234 | Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]); 235 | if (lowerBound != originalLowerBound[0]) { 236 | return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { lowerBound }); 237 | } 238 | } else if (originalUpperBound.length == 1) { 239 | Type upperBound = resolve(context, contextRawType, originalUpperBound[0]); 240 | if (upperBound != originalUpperBound[0]) { 241 | return new WildcardTypeImpl(new Type[] { upperBound }, EMPTY_TYPE_ARRAY); 242 | } 243 | } 244 | return original; 245 | 246 | } else { 247 | return toResolve; 248 | } 249 | } 250 | } 251 | 252 | private static Type resolveTypeVariable( 253 | Type context, Class contextRawType, TypeVariable unknown) { 254 | Class declaredByRaw = declaringClassOf(unknown); 255 | 256 | // We can't reduce this further. 257 | if (declaredByRaw == null) return unknown; 258 | 259 | Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw); 260 | if (declaredBy instanceof ParameterizedType) { 261 | int index = indexOf(declaredByRaw.getTypeParameters(), unknown); 262 | return ((ParameterizedType) declaredBy).getActualTypeArguments()[index]; 263 | } 264 | 265 | return unknown; 266 | } 267 | 268 | /** 269 | * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by 270 | * a class. 271 | */ 272 | private static Class declaringClassOf(TypeVariable typeVariable) { 273 | GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration(); 274 | return genericDeclaration instanceof Class ? (Class) genericDeclaration : null; 275 | } 276 | 277 | static void checkNotPrimitive(Type type) { 278 | if (type instanceof Class && ((Class) type).isPrimitive()) { 279 | throw new IllegalArgumentException(); 280 | } 281 | } 282 | 283 | static T checkNotNull(T object, String message) { 284 | if (object == null) { 285 | throw new NullPointerException(message); 286 | } 287 | return object; 288 | } 289 | 290 | /** Returns true if {@code annotations} contains an instance of {@code cls}. */ 291 | static boolean isAnnotationPresent(Annotation[] annotations, 292 | Class cls) { 293 | for (Annotation annotation : annotations) { 294 | if (cls.isInstance(annotation)) { 295 | return true; 296 | } 297 | } 298 | return false; 299 | } 300 | 301 | static ResponseBody buffer(final ResponseBody body) throws IOException { 302 | Buffer buffer = new Buffer(); 303 | body.source().readAll(buffer); 304 | return ResponseBody.create(body.contentType(), body.contentLength(), buffer); 305 | } 306 | 307 | static void validateServiceInterface(Class service) { 308 | if (!service.isInterface()) { 309 | throw new IllegalArgumentException("API declarations must be interfaces."); 310 | } 311 | // Prevent API interfaces from extending other interfaces. This not only avoids a bug in 312 | // Android (http://b.android.com/58753) but it forces composition of API declarations which is 313 | // the recommended pattern. 314 | if (service.getInterfaces().length > 0) { 315 | throw new IllegalArgumentException("API interfaces must not extend other interfaces."); 316 | } 317 | } 318 | 319 | static Type getParameterUpperBound(int index, ParameterizedType type) { 320 | Type[] types = type.getActualTypeArguments(); 321 | if (index < 0 || index >= types.length) { 322 | throw new IllegalArgumentException( 323 | "Index " + index + " not in range [0," + types.length + ") for " + type); 324 | } 325 | Type paramType = types[index]; 326 | if (paramType instanceof WildcardType) { 327 | return ((WildcardType) paramType).getUpperBounds()[0]; 328 | } 329 | return paramType; 330 | } 331 | 332 | static boolean hasUnresolvableType(Type type) { 333 | if (type instanceof Class) { 334 | return false; 335 | } 336 | if (type instanceof ParameterizedType) { 337 | ParameterizedType parameterizedType = (ParameterizedType) type; 338 | for (Type typeArgument : parameterizedType.getActualTypeArguments()) { 339 | if (hasUnresolvableType(typeArgument)) { 340 | return true; 341 | } 342 | } 343 | return false; 344 | } 345 | if (type instanceof GenericArrayType) { 346 | return hasUnresolvableType(((GenericArrayType) type).getGenericComponentType()); 347 | } 348 | if (type instanceof TypeVariable) { 349 | return true; 350 | } 351 | if (type instanceof WildcardType) { 352 | return true; 353 | } 354 | String className = type == null ? "null" : type.getClass().getName(); 355 | throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " 356 | + "GenericArrayType, but <" + type + "> is of type " + className); 357 | } 358 | 359 | static Type getCallResponseType(Type returnType) { 360 | if (!(returnType instanceof ParameterizedType)) { 361 | throw new IllegalArgumentException( 362 | "Call return type must be parameterized as Call or Call"); 363 | } 364 | return getParameterUpperBound(0, (ParameterizedType) returnType); 365 | } 366 | 367 | private static final class ParameterizedTypeImpl implements ParameterizedType { 368 | private final Type ownerType; 369 | private final Type rawType; 370 | private final Type[] typeArguments; 371 | 372 | public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) { 373 | // Require an owner type if the raw type needs it. 374 | if (rawType instanceof Class 375 | && (ownerType == null) != (((Class) rawType).getEnclosingClass() == null)) { 376 | throw new IllegalArgumentException(); 377 | } 378 | 379 | this.ownerType = ownerType; 380 | this.rawType = rawType; 381 | this.typeArguments = typeArguments.clone(); 382 | 383 | for (Type typeArgument : this.typeArguments) { 384 | if (typeArgument == null) throw new NullPointerException(); 385 | checkNotPrimitive(typeArgument); 386 | } 387 | } 388 | 389 | @Override public Type[] getActualTypeArguments() { 390 | return typeArguments.clone(); 391 | } 392 | 393 | @Override public Type getRawType() { 394 | return rawType; 395 | } 396 | 397 | @Override public Type getOwnerType() { 398 | return ownerType; 399 | } 400 | 401 | @Override public boolean equals(Object other) { 402 | return other instanceof ParameterizedType && Utils.equals(this, (ParameterizedType) other); 403 | } 404 | 405 | @Override public int hashCode() { 406 | return Arrays.hashCode(typeArguments) ^ rawType.hashCode() ^ hashCodeOrZero(ownerType); 407 | } 408 | 409 | @Override public String toString() { 410 | StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1)); 411 | result.append(typeToString(rawType)); 412 | if (typeArguments.length == 0) return result.toString(); 413 | result.append("<").append(typeToString(typeArguments[0])); 414 | for (int i = 1; i < typeArguments.length; i++) { 415 | result.append(", ").append(typeToString(typeArguments[i])); 416 | } 417 | return result.append(">").toString(); 418 | } 419 | } 420 | 421 | private static final class GenericArrayTypeImpl implements GenericArrayType { 422 | private final Type componentType; 423 | 424 | public GenericArrayTypeImpl(Type componentType) { 425 | this.componentType = componentType; 426 | } 427 | 428 | @Override public Type getGenericComponentType() { 429 | return componentType; 430 | } 431 | 432 | @Override public boolean equals(Object o) { 433 | return o instanceof GenericArrayType 434 | && Utils.equals(this, (GenericArrayType) o); 435 | } 436 | 437 | @Override public int hashCode() { 438 | return componentType.hashCode(); 439 | } 440 | 441 | @Override public String toString() { 442 | return typeToString(componentType) + "[]"; 443 | } 444 | } 445 | 446 | /** 447 | * The WildcardType interface supports multiple upper bounds and multiple 448 | * lower bounds. We only support what the Java 6 language needs - at most one 449 | * bound. If a lower bound is set, the upper bound must be Object.class. 450 | */ 451 | private static final class WildcardTypeImpl implements WildcardType { 452 | private final Type upperBound; 453 | private final Type lowerBound; 454 | 455 | public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) { 456 | if (lowerBounds.length > 1) throw new IllegalArgumentException(); 457 | if (upperBounds.length != 1) throw new IllegalArgumentException(); 458 | 459 | if (lowerBounds.length == 1) { 460 | if (lowerBounds[0] == null) throw new NullPointerException(); 461 | checkNotPrimitive(lowerBounds[0]); 462 | if (upperBounds[0] != Object.class) throw new IllegalArgumentException(); 463 | this.lowerBound = lowerBounds[0]; 464 | this.upperBound = Object.class; 465 | } else { 466 | if (upperBounds[0] == null) throw new NullPointerException(); 467 | checkNotPrimitive(upperBounds[0]); 468 | this.lowerBound = null; 469 | this.upperBound = upperBounds[0]; 470 | } 471 | } 472 | 473 | @Override public Type[] getUpperBounds() { 474 | return new Type[] { upperBound }; 475 | } 476 | 477 | @Override public Type[] getLowerBounds() { 478 | return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY; 479 | } 480 | 481 | @Override public boolean equals(Object other) { 482 | return other instanceof WildcardType && Utils.equals(this, (WildcardType) other); 483 | } 484 | 485 | @Override public int hashCode() { 486 | // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()). 487 | return (lowerBound != null ? 31 + lowerBound.hashCode() : 1) ^ (31 + upperBound.hashCode()); 488 | } 489 | 490 | @Override public String toString() { 491 | if (lowerBound != null) return "? super " + typeToString(lowerBound); 492 | if (upperBound == Object.class) return "?"; 493 | return "? extends " + typeToString(upperBound); 494 | } 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/adapter/rxjava/CompletableHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, 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 | package retrofit2.adapter.rxjava; 17 | 18 | import java.lang.reflect.Type; 19 | import retrofit2.Call; 20 | import retrofit2.CallAdapter; 21 | import retrofit2.Response; 22 | import rx.Completable; 23 | import rx.Completable.CompletableOnSubscribe; 24 | import rx.Completable.CompletableSubscriber; 25 | import rx.Scheduler; 26 | import rx.Subscription; 27 | import rx.exceptions.Exceptions; 28 | import rx.functions.Action0; 29 | import rx.subscriptions.Subscriptions; 30 | 31 | final class CompletableHelper { 32 | static CallAdapter createCallAdapter(Scheduler scheduler) { 33 | return new CompletableCallAdapter(scheduler); 34 | } 35 | 36 | private static final class CompletableCallOnSubscribe implements CompletableOnSubscribe { 37 | private final Call originalCall; 38 | 39 | CompletableCallOnSubscribe(Call originalCall) { 40 | this.originalCall = originalCall; 41 | } 42 | 43 | @Override public void call(CompletableSubscriber subscriber) { 44 | // Since Call is a one-shot type, clone it for each new subscriber. 45 | final Call call = originalCall.clone(); 46 | 47 | // Attempt to cancel the call if it is still in-flight on unsubscription. 48 | Subscription subscription = Subscriptions.create(new Action0() { 49 | @Override public void call() { 50 | call.cancel(); 51 | } 52 | }); 53 | subscriber.onSubscribe(subscription); 54 | 55 | try { 56 | Response response = call.execute(); 57 | if (!subscription.isUnsubscribed()) { 58 | if (response.isSuccessful()) { 59 | subscriber.onCompleted(); 60 | } else { 61 | subscriber.onError(new HttpException(response)); 62 | } 63 | } 64 | } catch (Throwable t) { 65 | Exceptions.throwIfFatal(t); 66 | if (!subscription.isUnsubscribed()) { 67 | subscriber.onError(t); 68 | } 69 | } 70 | } 71 | } 72 | 73 | static class CompletableCallAdapter implements CallAdapter { 74 | private final Scheduler scheduler; 75 | 76 | CompletableCallAdapter(Scheduler scheduler) { 77 | this.scheduler = scheduler; 78 | } 79 | 80 | @Override public Type responseType() { 81 | return Void.class; 82 | } 83 | 84 | @Override public Completable adapt(Call call) { 85 | Completable completable = Completable.create(new CompletableCallOnSubscribe(call)); 86 | if (scheduler != null) { 87 | return completable.subscribeOn(scheduler); 88 | } 89 | return completable; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/adapter/rxjava/HttpException.java: -------------------------------------------------------------------------------- 1 | package retrofit2.adapter.rxjava; 2 | 3 | import retrofit2.Response; 4 | 5 | /** Exception for an unexpected, non-2xx HTTP response. */ 6 | public final class HttpException extends Exception { 7 | private final int code; 8 | private final String message; 9 | private final transient Response response; 10 | 11 | public HttpException(Response response) { 12 | super("HTTP " + response.code() + " " + response.message()); 13 | this.code = response.code(); 14 | this.message = response.message(); 15 | this.response = response; 16 | } 17 | 18 | /** HTTP status code. */ 19 | public int code() { 20 | return code; 21 | } 22 | 23 | /** HTTP status message. */ 24 | public String message() { 25 | return message; 26 | } 27 | 28 | /** 29 | * The full HTTP response. This may be null if the exception was serialized. 30 | */ 31 | public Response response() { 32 | return response; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/adapter/rxjava/OperatorMapResponseToBodyOrError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, 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 | package retrofit2.adapter.rxjava; 17 | 18 | import retrofit2.Response; 19 | import rx.Observable; 20 | import rx.Observable.Operator; 21 | import rx.Subscriber; 22 | import rx.functions.Func1; 23 | 24 | /** 25 | * A version of {@link Observable#map(Func1)} which lets us trigger {@code onError} without having 26 | * to use {@link Observable#flatMap(Func1)} which breaks producer requests from propagating. 27 | */ 28 | final class OperatorMapResponseToBodyOrError implements Operator> { 29 | private static final OperatorMapResponseToBodyOrError INSTANCE = 30 | new OperatorMapResponseToBodyOrError<>(); 31 | 32 | @SuppressWarnings("unchecked") // Safe because of erasure. 33 | static OperatorMapResponseToBodyOrError instance() { 34 | return (OperatorMapResponseToBodyOrError) INSTANCE; 35 | } 36 | 37 | @Override public Subscriber> call(final Subscriber child) { 38 | return new Subscriber>(child) { 39 | @Override public void onNext(Response response) { 40 | if (response.isSuccessful()) { 41 | child.onNext(response.body()); 42 | } else { 43 | child.onError(new HttpException(response)); 44 | } 45 | } 46 | 47 | @Override public void onCompleted() { 48 | child.onCompleted(); 49 | } 50 | 51 | @Override public void onError(Throwable e) { 52 | child.onError(e); 53 | } 54 | }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/adapter/rxjava/Result.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.adapter.rxjava; 17 | 18 | import java.io.IOException; 19 | import retrofit2.Response; 20 | 21 | /** The result of executing an HTTP request. */ 22 | public final class Result { 23 | public static Result error(Throwable error) { 24 | if (error == null) throw new NullPointerException("error == null"); 25 | return new Result<>(null, error); 26 | } 27 | 28 | public static Result response(Response response) { 29 | if (response == null) throw new NullPointerException("response == null"); 30 | return new Result<>(response, null); 31 | } 32 | 33 | private final Response response; 34 | private final Throwable error; 35 | 36 | private Result(Response response, Throwable error) { 37 | this.response = response; 38 | this.error = error; 39 | } 40 | 41 | /** 42 | * The response received from executing an HTTP request. Only present when {@link #isError()} is 43 | * false, null otherwise. 44 | */ 45 | public Response response() { 46 | return response; 47 | } 48 | 49 | /** 50 | * The error experienced while attempting to execute an HTTP request. Only present when {@link 51 | * #isError()} is true, null otherwise. 52 | *

53 | * If the error is an {@link IOException} then there was a problem with the transport to the 54 | * remote server. Any other exception type indicates an unexpected failure and should be 55 | * considered fatal (configuration error, programming error, etc.). 56 | */ 57 | public Throwable error() { 58 | return error; 59 | } 60 | 61 | /** {@code true} if the request resulted in an error. See {@link #error()} for the cause. */ 62 | public boolean isError() { 63 | return error != null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/adapter/rxjava/RxJavaCallAdapterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.adapter.rxjava; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.reflect.ParameterizedType; 21 | import java.lang.reflect.Type; 22 | import java.util.concurrent.atomic.AtomicBoolean; 23 | import retrofit2.Call; 24 | import retrofit2.CallAdapter; 25 | import retrofit2.Response; 26 | import retrofit2.Retrofit; 27 | import rx.Observable; 28 | import rx.Producer; 29 | import rx.Scheduler; 30 | import rx.Subscriber; 31 | import rx.Subscription; 32 | import rx.exceptions.Exceptions; 33 | import rx.functions.Func1; 34 | 35 | /** 36 | * A {@linkplain CallAdapter.Factory call adapter} which uses RxJava for creating observables. 37 | *

38 | * Adding this class to {@link Retrofit} allows you to return {@link Observable} from service 39 | * methods. 40 | *


 41 |  * interface MyService {
 42 |  *   @GET("user/me")
 43 |  *   Observable<User> getUser()
 44 |  * }
 45 |  * 
46 | * There are three configurations supported for the {@code Observable} type parameter: 47 | *
    48 | *
  • Direct body (e.g., {@code Observable}) calls {@code onNext} with the deserialized body 49 | * for 2XX responses and calls {@code onError} with {@link HttpException} for non-2XX responses and 50 | * {@link IOException} for network errors.
  • 51 | *
  • Response wrapped body (e.g., {@code Observable>}) calls {@code onNext} 52 | * with a {@link Response} object for all HTTP responses and calls {@code onError} with 53 | * {@link IOException} for network errors
  • 54 | *
  • Result wrapped body (e.g., {@code Observable>}) calls {@code onNext} with a 55 | * {@link Result} object for all HTTP responses and errors.
  • 56 | *
57 | */ 58 | public final class RxJavaCallAdapterFactory extends CallAdapter.Factory { 59 | /** 60 | * Returns an instance which creates synchronous observables that do not operate on any scheduler 61 | * by default. 62 | */ 63 | public static RxJavaCallAdapterFactory create() { 64 | return new RxJavaCallAdapterFactory(null); 65 | } 66 | 67 | /** 68 | * Returns an instance which creates synchronous observables that 69 | * {@linkplain Observable#subscribeOn(Scheduler) subscribe on} {@code scheduler} by default. 70 | */ 71 | public static RxJavaCallAdapterFactory createWithScheduler(Scheduler scheduler) { 72 | if (scheduler == null) throw new NullPointerException("scheduler == null"); 73 | return new RxJavaCallAdapterFactory(scheduler); 74 | } 75 | 76 | private final Scheduler scheduler; 77 | 78 | private RxJavaCallAdapterFactory(Scheduler scheduler) { 79 | this.scheduler = scheduler; 80 | } 81 | 82 | @Override 83 | public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { 84 | Class rawType = getRawType(returnType); 85 | String canonicalName = rawType.getCanonicalName(); 86 | boolean isSingle = "rx.Single".equals(canonicalName); 87 | boolean isCompletable = "rx.Completable".equals(canonicalName); 88 | if (rawType != Observable.class && !isSingle && !isCompletable) { 89 | return null; 90 | } 91 | if (!isCompletable && !(returnType instanceof ParameterizedType)) { 92 | String name = isSingle ? "Single" : "Observable"; 93 | throw new IllegalStateException(name + " return type must be parameterized" 94 | + " as " + name + " or " + name + ""); 95 | } 96 | 97 | if (isCompletable) { 98 | // Add Completable-converter wrapper from a separate class. This defers classloading such that 99 | // regular Observable operation can be leveraged without relying on this unstable RxJava API. 100 | // Note that this has to be done separately since Completable doesn't have a parametrized 101 | // type. 102 | return CompletableHelper.createCallAdapter(scheduler); 103 | } 104 | 105 | CallAdapter> callAdapter = getCallAdapter(returnType, scheduler); 106 | if (isSingle) { 107 | // Add Single-converter wrapper from a separate class. This defers classloading such that 108 | // regular Observable operation can be leveraged without relying on this unstable RxJava API. 109 | return SingleHelper.makeSingle(callAdapter); 110 | } 111 | return callAdapter; 112 | } 113 | 114 | private CallAdapter> getCallAdapter(Type returnType, Scheduler scheduler) { 115 | Type observableType = getParameterUpperBound(0, (ParameterizedType) returnType); 116 | Class rawObservableType = getRawType(observableType); 117 | if (rawObservableType == Response.class) { 118 | if (!(observableType instanceof ParameterizedType)) { 119 | throw new IllegalStateException("Response must be parameterized" 120 | + " as Response or Response"); 121 | } 122 | Type responseType = getParameterUpperBound(0, (ParameterizedType) observableType); 123 | return new ResponseCallAdapter(responseType, scheduler); 124 | } 125 | 126 | if (rawObservableType == Result.class) { 127 | if (!(observableType instanceof ParameterizedType)) { 128 | throw new IllegalStateException("Result must be parameterized" 129 | + " as Result or Result"); 130 | } 131 | Type responseType = getParameterUpperBound(0, (ParameterizedType) observableType); 132 | return new ResultCallAdapter(responseType, scheduler); 133 | } 134 | 135 | return new SimpleCallAdapter(observableType, scheduler); 136 | } 137 | 138 | static final class CallOnSubscribe implements Observable.OnSubscribe> { 139 | private final Call originalCall; 140 | 141 | CallOnSubscribe(Call originalCall) { 142 | this.originalCall = originalCall; 143 | } 144 | 145 | @Override public void call(final Subscriber> subscriber) { 146 | // Since Call is a one-shot type, clone it for each new subscriber. 147 | Call call = originalCall.clone(); 148 | 149 | // Wrap the call in a helper which handles both unsubscription and backpressure. 150 | RequestArbiter requestArbiter = new RequestArbiter<>(call, subscriber); 151 | subscriber.add(requestArbiter); 152 | subscriber.setProducer(requestArbiter); 153 | } 154 | } 155 | 156 | static final class RequestArbiter extends AtomicBoolean implements Subscription, Producer { 157 | private final Call call; 158 | private final Subscriber> subscriber; 159 | 160 | RequestArbiter(Call call, Subscriber> subscriber) { 161 | this.call = call; 162 | this.subscriber = subscriber; 163 | } 164 | 165 | @Override public void request(long n) { 166 | if (n < 0) throw new IllegalArgumentException("n < 0: " + n); 167 | if (n == 0) return; // Nothing to do when requesting 0. 168 | if (!compareAndSet(false, true)) return; // Request was already triggered. 169 | 170 | try { 171 | Response response = call.execute(); 172 | if (!subscriber.isUnsubscribed()) { 173 | subscriber.onNext(response); 174 | } 175 | } catch (Throwable t) { 176 | Exceptions.throwIfFatal(t); 177 | if (!subscriber.isUnsubscribed()) { 178 | subscriber.onError(t); 179 | } 180 | return; 181 | } 182 | 183 | if (!subscriber.isUnsubscribed()) { 184 | subscriber.onCompleted(); 185 | } 186 | } 187 | 188 | @Override public void unsubscribe() { 189 | call.cancel(); 190 | } 191 | 192 | @Override public boolean isUnsubscribed() { 193 | return call.isCanceled(); 194 | } 195 | } 196 | 197 | static final class ResponseCallAdapter implements CallAdapter> { 198 | private final Type responseType; 199 | private final Scheduler scheduler; 200 | 201 | ResponseCallAdapter(Type responseType, Scheduler scheduler) { 202 | this.responseType = responseType; 203 | this.scheduler = scheduler; 204 | } 205 | 206 | @Override public Type responseType() { 207 | return responseType; 208 | } 209 | 210 | @Override public Observable> adapt(Call call) { 211 | Observable> observable = Observable.create(new CallOnSubscribe<>(call)); 212 | if (scheduler != null) { 213 | return observable.subscribeOn(scheduler); 214 | } 215 | return observable; 216 | } 217 | } 218 | 219 | static final class SimpleCallAdapter implements CallAdapter> { 220 | private final Type responseType; 221 | private final Scheduler scheduler; 222 | 223 | SimpleCallAdapter(Type responseType, Scheduler scheduler) { 224 | this.responseType = responseType; 225 | this.scheduler = scheduler; 226 | } 227 | 228 | @Override public Type responseType() { 229 | return responseType; 230 | } 231 | 232 | @Override public Observable adapt(Call call) { 233 | Observable observable = Observable.create(new CallOnSubscribe<>(call)) // 234 | .lift(OperatorMapResponseToBodyOrError.instance()); 235 | if (scheduler != null) { 236 | return observable.subscribeOn(scheduler); 237 | } 238 | return observable; 239 | } 240 | } 241 | 242 | static final class ResultCallAdapter implements CallAdapter> { 243 | private final Type responseType; 244 | private final Scheduler scheduler; 245 | 246 | ResultCallAdapter(Type responseType, Scheduler scheduler) { 247 | this.responseType = responseType; 248 | this.scheduler = scheduler; 249 | } 250 | 251 | @Override public Type responseType() { 252 | return responseType; 253 | } 254 | 255 | @Override public Observable> adapt(Call call) { 256 | Observable> observable = Observable.create(new CallOnSubscribe<>(call)) // 257 | .map(new Func1, Result>() { 258 | @Override public Result call(Response response) { 259 | return Result.response(response); 260 | } 261 | }).onErrorReturn(new Func1>() { 262 | @Override public Result call(Throwable throwable) { 263 | return Result.error(throwable); 264 | } 265 | }); 266 | if (scheduler != null) { 267 | return observable.subscribeOn(scheduler); 268 | } 269 | return observable; 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/adapter/rxjava/SingleHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.adapter.rxjava; 17 | 18 | import java.lang.reflect.Type; 19 | import retrofit2.Call; 20 | import retrofit2.CallAdapter; 21 | import rx.Observable; 22 | import rx.Single; 23 | 24 | final class SingleHelper { 25 | static CallAdapter> makeSingle(final CallAdapter> callAdapter) { 26 | return new CallAdapter>() { 27 | @Override public Type responseType() { 28 | return callAdapter.responseType(); 29 | } 30 | 31 | @Override public Single adapt(Call call) { 32 | Observable observable = callAdapter.adapt(call); 33 | return observable.toSingle(); 34 | } 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/converter/gson/GsonConverterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.converter.gson; 17 | 18 | import com.google.gson.Gson; 19 | import com.google.gson.TypeAdapter; 20 | import com.google.gson.reflect.TypeToken; 21 | import java.lang.annotation.Annotation; 22 | import java.lang.reflect.Type; 23 | import okhttp3.RequestBody; 24 | import okhttp3.ResponseBody; 25 | import retrofit2.Converter; 26 | import retrofit2.Retrofit; 27 | 28 | /** 29 | * A {@linkplain Converter.Factory converter} which uses Gson for JSON. 30 | *

31 | * Because Gson is so flexible in the types it supports, this converter assumes that it can handle 32 | * all types. If you are mixing JSON serialization with something else (such as protocol buffers), 33 | * you must {@linkplain Retrofit.Builder#addConverterFactory(Converter.Factory) add this instance} 34 | * last to allow the other converters a chance to see their types. 35 | */ 36 | public final class GsonConverterFactory extends Converter.Factory { 37 | /** 38 | * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and 39 | * decoding from JSON (when no charset is specified by a header) will use UTF-8. 40 | */ 41 | public static GsonConverterFactory create() { 42 | return create(new Gson()); 43 | } 44 | 45 | /** 46 | * Create an instance using {@code gson} for conversion. Encoding to JSON and 47 | * decoding from JSON (when no charset is specified by a header) will use UTF-8. 48 | */ 49 | public static GsonConverterFactory create(Gson gson) { 50 | return new GsonConverterFactory(gson); 51 | } 52 | 53 | private final Gson gson; 54 | 55 | private GsonConverterFactory(Gson gson) { 56 | if (gson == null) throw new NullPointerException("gson == null"); 57 | this.gson = gson; 58 | } 59 | 60 | @Override 61 | public Converter responseBodyConverter(Type type, Annotation[] annotations, 62 | Retrofit retrofit) { 63 | TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); 64 | return new GsonResponseBodyConverter<>(gson, adapter); 65 | } 66 | 67 | @Override 68 | public Converter requestBodyConverter(Type type, 69 | Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 70 | TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); 71 | return new GsonRequestBodyConverter<>(gson, adapter); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/converter/gson/GsonRequestBodyConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.converter.gson; 17 | 18 | import com.google.gson.Gson; 19 | import com.google.gson.TypeAdapter; 20 | import com.google.gson.stream.JsonWriter; 21 | import java.io.IOException; 22 | import java.io.OutputStreamWriter; 23 | import java.io.Writer; 24 | import java.nio.charset.Charset; 25 | import okhttp3.MediaType; 26 | import okhttp3.RequestBody; 27 | import okio.Buffer; 28 | import retrofit2.Converter; 29 | 30 | final class GsonRequestBodyConverter implements Converter { 31 | private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); 32 | private static final Charset UTF_8 = Charset.forName("UTF-8"); 33 | 34 | private final Gson gson; 35 | private final TypeAdapter adapter; 36 | 37 | GsonRequestBodyConverter(Gson gson, TypeAdapter adapter) { 38 | this.gson = gson; 39 | this.adapter = adapter; 40 | } 41 | 42 | @Override public RequestBody convert(T value) throws IOException { 43 | Buffer buffer = new Buffer(); 44 | Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); 45 | JsonWriter jsonWriter = gson.newJsonWriter(writer); 46 | adapter.write(jsonWriter, value); 47 | jsonWriter.close(); 48 | return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/converter/gson/GsonResponseBodyConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.converter.gson; 17 | 18 | import com.google.gson.Gson; 19 | import com.google.gson.TypeAdapter; 20 | import com.google.gson.stream.JsonReader; 21 | import java.io.IOException; 22 | import okhttp3.ResponseBody; 23 | import retrofit2.Converter; 24 | 25 | final class GsonResponseBodyConverter implements Converter { 26 | private final Gson gson; 27 | private final TypeAdapter adapter; 28 | 29 | GsonResponseBodyConverter(Gson gson, TypeAdapter adapter) { 30 | this.gson = gson; 31 | this.adapter = adapter; 32 | } 33 | 34 | @Override public T convert(ResponseBody value) throws IOException { 35 | JsonReader jsonReader = gson.newJsonReader(value.charStream()); 36 | try { 37 | return adapter.read(jsonReader); 38 | } finally { 39 | value.close(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/entity/MediaTypes.java: -------------------------------------------------------------------------------- 1 | package retrofit2.entity; 2 | 3 | import java.io.File; 4 | import java.util.HashMap; 5 | 6 | /** 7 | * Created by benny on 6/1/16. 8 | */ 9 | public class MediaTypes { 10 | 11 | public static final String APPLICATION_JSON = "application/json"; 12 | public static final String APPLICATION_URLENCODED = "application/x-www-form-urlencoded"; 13 | public static final String MULTIPART_FORM_DATA = "multipart/form-data"; 14 | public static final String TEXT_HTML = "text/html"; 15 | public static final String IMAGE_PNG = "image/png"; 16 | public static final String TEXT_PLAIN = "text/plain"; 17 | public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; 18 | 19 | private static HashMap mediaTypeMap = new HashMap() { 20 | { 21 | put("json", APPLICATION_JSON); 22 | put("png", IMAGE_PNG); 23 | put("html", TEXT_HTML); 24 | put("txt", TEXT_PLAIN); 25 | } 26 | }; 27 | 28 | public static String getMediaType(File file) { 29 | String filename = file.getName(); 30 | int index = filename.lastIndexOf("."); 31 | if (index == -1) { 32 | return APPLICATION_OCTET_STREAM; 33 | } else { 34 | String ext = filename.substring(index); 35 | String mediaType = mediaTypeMap.get(ext); 36 | if (mediaType == null) { 37 | return APPLICATION_OCTET_STREAM; 38 | } else { 39 | return mediaType; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/entity/TypedFile.java: -------------------------------------------------------------------------------- 1 | package retrofit2.entity; 2 | 3 | import okhttp3.MediaType; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * Created by benny on 6/1/16. 9 | */ 10 | public class TypedFile { 11 | private MediaType mediaType; 12 | private String name; 13 | private File file; 14 | 15 | public TypedFile(String name, String filepath){ 16 | this(name, new File(filepath)); 17 | } 18 | 19 | public TypedFile(String name, File file) { 20 | this(MediaType.parse(MediaTypes.getMediaType(file)), name, file); 21 | } 22 | 23 | public TypedFile(MediaType mediaType, String name, String filepath) { 24 | this(mediaType, name, new File(filepath)); 25 | } 26 | 27 | public TypedFile(MediaType mediaType, String name, File file) { 28 | this.mediaType = mediaType; 29 | this.name = name; 30 | this.file = file; 31 | } 32 | 33 | public String getName() { 34 | return name; 35 | } 36 | 37 | public MediaType getMediaType() { 38 | return mediaType; 39 | } 40 | 41 | public File getFile() { 42 | return file; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/entity/TypedFileMultiPartBodyConverter.java: -------------------------------------------------------------------------------- 1 | package retrofit2.entity; 2 | 3 | import okhttp3.MultipartBody; 4 | import okhttp3.RequestBody; 5 | import retrofit2.Converter; 6 | 7 | import java.io.IOException; 8 | 9 | public class TypedFileMultiPartBodyConverter implements Converter { 10 | 11 | @Override 12 | public MultipartBody.Part convert(TypedFile typedFile) throws IOException { 13 | RequestBody requestFile = 14 | RequestBody.create(typedFile.getMediaType(), typedFile.getFile()); 15 | return MultipartBody.Part.createFormData(typedFile.getName(), typedFile.getFile().getName(), requestFile); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/retrofit2/entity/TypedFileMultiPartBodyConverterFactory.java: -------------------------------------------------------------------------------- 1 | package retrofit2.entity; 2 | 3 | import okhttp3.MultipartBody; 4 | import retrofit2.Converter; 5 | import retrofit2.Retrofit; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Type; 9 | 10 | public class TypedFileMultiPartBodyConverterFactory extends Converter.Factory { 11 | @Override 12 | public Converter arbitraryConverter(Type originalType, Type convertedType, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 13 | if (originalType == TypedFile.class && convertedType == MultipartBody.Part.class) { 14 | return new TypedFileMultiPartBodyConverter(); 15 | } 16 | return null; 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Body.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import retrofit2.Converter; 22 | import retrofit2.Retrofit; 23 | 24 | import static java.lang.annotation.ElementType.PARAMETER; 25 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 26 | 27 | /** 28 | * Use this annotation on a service method param when you want to directly control the request body 29 | * of a POST/PUT request (instead of sending in as request parameters or form-style request 30 | * body). The object will be serialized using the {@link Retrofit Retrofit} instance 31 | * {@link Converter Converter} and the result will be set directly as the 32 | * request body. 33 | *

34 | * Body parameters may not be {@code null}. 35 | */ 36 | @Documented 37 | @Target(PARAMETER) 38 | @Retention(RUNTIME) 39 | public @interface Body { 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/DELETE.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** Make a DELETE request. */ 27 | @Documented 28 | @Target(METHOD) 29 | @Retention(RUNTIME) 30 | public @interface DELETE { 31 | /** 32 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 33 | * parameter of the method is annotated with {@link Url @Url}. 34 | *

35 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 36 | * this is resolved against a base URL to create the full endpoint URL. 37 | */ 38 | String value() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Field.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Named pair for a form-encoded request. 27 | *

28 | * Values are converted to strings using {@link String#valueOf(Object)} and then form URL encoded. 29 | * {@code null} values are ignored. Passing a {@link java.util.List List} or array will result in a 30 | * field pair for each non-{@code null} item. 31 | *

32 | * Simple Example: 33 | *


34 |  * @FormUrlEncoded
35 |  * @POST("/")
36 |  * Call<ResponseBody> example(
37 |  *     @Field("name") String name,
38 |  *     @Field("occupation") String occupation);
39 |  * 
40 | * Calling with {@code foo.example("Bob Smith", "President")} yields a request body of 41 | * {@code name=Bob+Smith&occupation=President}. 42 | *

43 | * Array/Varargs Example: 44 | *


45 |  * @FormUrlEncoded
46 |  * @POST("/list")
47 |  * Call<ResponseBody> example(@Field("name") String... names);
48 |  * 
49 | * Calling with {@code foo.example("Bob Smith", "Jane Doe")} yields a request body of 50 | * {@code name=Bob+Smith&name=Jane+Doe}. 51 | * 52 | * @see FormUrlEncoded 53 | * @see FieldMap 54 | */ 55 | @Documented 56 | @Target(PARAMETER) 57 | @Retention(RUNTIME) 58 | public @interface Field { 59 | String value(); 60 | 61 | /** Specifies whether the {@linkplain #value() name} and value are already URL encoded. */ 62 | boolean encoded() default false; 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/FieldMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Named key/value pairs for a form-encoded request. 27 | *

28 | * Simple Example: 29 | *


30 |  * @FormUrlEncoded
31 |  * @POST("/things")
32 |  * Call<ResponseBody> things(@FieldMap Map<String, String> fields);
33 |  * 
34 | * Calling with {@code foo.things(ImmutableMap.of("foo", "bar", "kit", "kat")} yields a request 35 | * body of {@code foo=bar&kit=kat}. 36 | *

37 | * A {@code null} value for the map, as a key, or as a value is not allowed. 38 | * 39 | * @see FormUrlEncoded 40 | * @see Field 41 | */ 42 | @Documented 43 | @Target(PARAMETER) 44 | @Retention(RUNTIME) 45 | public @interface FieldMap { 46 | /** Specifies whether the names and values are already URL encoded. */ 47 | boolean encoded() default false; 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/FixedField.java: -------------------------------------------------------------------------------- 1 | package retrofit2.http; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Created by benny on 4/30/16. 10 | */ 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target({ElementType.METHOD, ElementType.TYPE}) 13 | public @interface FixedField { 14 | String[] keys(); 15 | String[] values(); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/FormUrlEncoded.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.METHOD; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Denotes that the request body will use form URL encoding. Fields should be declared as 27 | * parameters and annotated with {@link Field @Field}. 28 | *

29 | * Requests made with this annotation will have {@code application/x-www-form-urlencoded} MIME 30 | * type. Field names and values will be UTF-8 encoded before being URI-encoded in accordance to 31 | * RFC-3986. 32 | */ 33 | @Documented 34 | @Target(METHOD) 35 | @Retention(RUNTIME) 36 | public @interface FormUrlEncoded { 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/GET.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import okhttp3.HttpUrl; 19 | 20 | import java.lang.annotation.Documented; 21 | import java.lang.annotation.Retention; 22 | import java.lang.annotation.Target; 23 | 24 | import static java.lang.annotation.ElementType.METHOD; 25 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 26 | 27 | /** Make a GET request. */ 28 | @Documented 29 | @Target(METHOD) 30 | @Retention(RUNTIME) 31 | public @interface GET { 32 | /** 33 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 34 | * parameter of the method is annotated with {@link Url @Url}. 35 | *

36 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 37 | * this is resolved against a base URL to create the full endpoint URL. 38 | */ 39 | String value() default ""; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/GeneratedField.java: -------------------------------------------------------------------------------- 1 | package retrofit2.http; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import retrofit2.FieldGenerator; 9 | 10 | /** 11 | * Created by benny on 4/30/16. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD, ElementType.TYPE}) 15 | public @interface GeneratedField { 16 | String[] keys(); 17 | 18 | Class[] generators(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/GeneratedFieldMap.java: -------------------------------------------------------------------------------- 1 | package retrofit2.http; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import retrofit2.FieldMapGenerator; 9 | 10 | /** 11 | * Created by benny on 4/30/16. 12 | */ 13 | @Retention(RetentionPolicy.RUNTIME) 14 | @Target({ElementType.METHOD, ElementType.TYPE}) 15 | public @interface GeneratedFieldMap { 16 | Class value(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/HEAD.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** Make a HEAD request. */ 27 | @Documented 28 | @Target(METHOD) 29 | @Retention(RUNTIME) 30 | public @interface HEAD { 31 | /** 32 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 33 | * parameter of the method is annotated with {@link Url @Url}. 34 | *

35 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 36 | * this is resolved against a base URL to create the full endpoint URL. 37 | */ 38 | String value() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/HTTP.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** 27 | * Use a custom HTTP verb for a request. 28 | *


29 |  * interface Service {
30 |  *   @HTTP(method = "CUSTOM", path = "custom/endpoint/")
31 |  *   Call<ResponseBody> customEndpoint();
32 |  * }
33 |  * 
34 | * This annotation can also used for sending {@code DELETE} with a request body: 35 | *

36 |  * interface Service {
37 |  *   @HTTP(method = "DELETE", path = "remove/", hasBody = true)
38 |  *   Call<ResponseBody> deleteObject(@Body RequestBody object);
39 |  * }
40 |  * 
41 | */ 42 | @Documented 43 | @Target(METHOD) 44 | @Retention(RUNTIME) 45 | public @interface HTTP { 46 | String method(); 47 | /** 48 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 49 | * parameter of the method is annotated with {@link Url @Url}. 50 | *

51 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 52 | * this is resolved against a base URL to create the full endpoint URL. 53 | */ 54 | String path() default ""; 55 | boolean hasBody() default false; 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Header.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Replaces the header with the value of its target. 27 | *


28 |  * @GET("/")
29 |  * Call<ResponseBody> foo(@Header("Accept-Language") String lang);
30 |  * 
31 | * Header parameters may be {@code null} which will omit them from the request. Passing a 32 | * {@link java.util.List List} or array will result in a header for each non-{@code null} item. 33 | *

34 | * Note: Headers do not overwrite each other. All headers with the same name will 35 | * be included in the request. 36 | * 37 | * @see Headers 38 | * @see HeaderMap 39 | */ 40 | @Documented 41 | @Retention(RUNTIME) 42 | @Target(PARAMETER) 43 | public @interface Header { 44 | String value(); 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/HeaderMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Square, 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 | package retrofit2.http; 17 | 18 | import static java.lang.annotation.ElementType.PARAMETER; 19 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 20 | 21 | import java.lang.annotation.Documented; 22 | import java.lang.annotation.Retention; 23 | import java.lang.annotation.Target; 24 | import java.util.Map; 25 | 26 | /** 27 | * Adds headers specified in the {@link Map}. 28 | *

29 | * Values are converted to strings using {@link String#valueOf(Object)}. 30 | *

31 | * Simple Example: 32 | *

33 |  * @GET("/search")
34 |  * void list(@HeaderMap Map<String, String> headers);
35 |  *
36 |  * ...
37 |  *
38 |  * // The following call yields /search with headers
39 |  * // Accept: text/plain and Accept-Charset: utf-8
40 |  * foo.list(ImmutableMap.of("Accept", "text/plain", "Accept-Charset", "utf-8"));
41 |  * 
42 | * 43 | * @see Header 44 | * @see Headers 45 | */ 46 | @Documented 47 | @Target(PARAMETER) 48 | @Retention(RUNTIME) 49 | public @interface HeaderMap { 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Headers.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.METHOD; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Adds headers literally supplied in the {@code value}. 27 | *

28 |  * @Headers("Cache-Control: max-age=640000")
29 |  * @GET("/")
30 |  * ...
31 |  *
32 |  * @Headers({
33 |  *   "X-Foo: Bar",
34 |  *   "X-Ping: Pong"
35 |  * })
36 |  * @GET("/")
37 |  * ...
38 |  * 
39 | * Note: Headers do not overwrite each other. All headers with the same name will 40 | * be included in the request. 41 | * 42 | * @see Header 43 | * @see HeaderMap 44 | */ 45 | @Documented 46 | @Target(METHOD) 47 | @Retention(RUNTIME) 48 | public @interface Headers { 49 | String[] value(); 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Multipart.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.METHOD; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Denotes that the request body is multi-part. Parts should be declared as parameters and 27 | * annotated with {@link Part @Part}. 28 | */ 29 | @Documented 30 | @Target(METHOD) 31 | @Retention(RUNTIME) 32 | public @interface Multipart { 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/OPTIONS.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** Make an OPTIONS request. */ 27 | @Documented 28 | @Target(METHOD) 29 | @Retention(RUNTIME) 30 | public @interface OPTIONS { 31 | /** 32 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 33 | * parameter of the method is annotated with {@link Url @Url}. 34 | *

35 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 36 | * this is resolved against a base URL to create the full endpoint URL. 37 | */ 38 | String value() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/PATCH.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** Make a PATCH request. */ 27 | @Documented 28 | @Target(METHOD) 29 | @Retention(RUNTIME) 30 | public @interface PATCH { 31 | /** 32 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 33 | * parameter of the method is annotated with {@link Url @Url}. 34 | *

35 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 36 | * this is resolved against a base URL to create the full endpoint URL. 37 | */ 38 | String value() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/POST.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** Make a POST request. */ 27 | @Documented 28 | @Target(METHOD) 29 | @Retention(RUNTIME) 30 | public @interface POST { 31 | /** 32 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 33 | * parameter of the method is annotated with {@link Url @Url}. 34 | *

35 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 36 | * this is resolved against a base URL to create the full endpoint URL. 37 | */ 38 | String value() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/PUT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | 23 | import static java.lang.annotation.ElementType.METHOD; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** Make a PUT request. */ 27 | @Documented 28 | @Target(METHOD) 29 | @Retention(RUNTIME) 30 | public @interface PUT { 31 | /** 32 | * A relative or absolute path, or full URL of the endpoint. This value is optional if the first 33 | * parameter of the method is annotated with {@link Url @Url}. 34 | *

35 | * See {@linkplain retrofit2.Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 36 | * this is resolved against a base URL to create the full endpoint URL. 37 | */ 38 | String value() default ""; 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Part.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import retrofit2.Converter; 22 | 23 | import static java.lang.annotation.ElementType.PARAMETER; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** 27 | * Denotes a single part of a multi-part request. 28 | *

29 | * The parameter type on which this annotation exists will be processed in one of three ways: 30 | *

    31 | *
  • If the type is {@link okhttp3.MultipartBody.Part} the contents will be used directly. Omit 32 | * the name from the annotation (i.e., {@code @Part MultipartBody.Part part}).
  • 33 | *
  • If the type is {@link okhttp3.RequestBody RequestBody} the value will be used 34 | * directly with its content type. Supply the part name in the annotation (e.g., 35 | * {@code @Part("foo") RequestBody foo}).
  • 36 | *
  • Other object types will be converted to an appropriate representation by using 37 | * {@linkplain Converter a converter}. Supply the part name in the annotation (e.g., 38 | * {@code @Part("foo") Image photo}).
  • 39 | *
40 | *

41 | * Values may be {@code null} which will omit them from the request body. 42 | *

43 | *


44 |  * @Multipart
45 |  * @POST("/")
46 |  * Call<ResponseBody> example(
47 |  *     @Part("description") String description,
48 |  *     @Part(value = "image", encoding = "8-bit") RequestBody image);
49 |  * 
50 | *

51 | * Part parameters may not be {@code null}. 52 | */ 53 | @Documented 54 | @Target(PARAMETER) 55 | @Retention(RUNTIME) 56 | public @interface Part { 57 | /** 58 | * The name of the part. Required for all parameter types except 59 | * {@link okhttp3.MultipartBody.Part}. 60 | */ 61 | String value() default ""; 62 | /** The {@code Content-Transfer-Encoding} of this part. */ 63 | String encoding() default "binary"; 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/PartMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import retrofit2.Converter; 22 | 23 | import static java.lang.annotation.ElementType.PARAMETER; 24 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 25 | 26 | /** 27 | * Denotes name and value parts of a multi-part request. 28 | *

29 | * Values of the map on which this annotation exists will be processed in one of two ways: 30 | *

    31 | *
  • If the type is {@link okhttp3.RequestBody RequestBody} the value will be used 32 | * directly with its content type.
  • 33 | *
  • Other object types will be converted to an appropriate representation by using 34 | * {@linkplain Converter a converter}.
  • 35 | *
36 | *

37 | *


38 |  * @Multipart
39 |  * @POST("/upload")
40 |  * Call<ResponseBody> upload(
41 |  *     @Part("file") RequestBody file,
42 |  *     @PartMap Map<String, RequestBody> params);
43 |  * 
44 | *

45 | * A {@code null} value for the map, as a key, or as a value is not allowed. 46 | * 47 | * @see Multipart 48 | * @see Part 49 | */ 50 | @Documented 51 | @Target(PARAMETER) 52 | @Retention(RUNTIME) 53 | public @interface PartMap { 54 | /** The {@code Content-Transfer-Encoding} of the parts. */ 55 | String encoding() default "binary"; 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Path.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Named replacement in a URL path segment. Values are converted to string using 27 | * {@link String#valueOf(Object)} and URL encoded. 28 | *

29 | * Simple example: 30 | *


31 |  * @GET("/image/{id}")
32 |  * Call<ResponseBody> example(@Path("id") int id);
33 |  * 
34 | * Calling with {@code foo.example(1)} yields {@code /image/1}. 35 | *

36 | * Values are URL encoded by default. Disable with {@code encoded=true}. 37 | *


38 |  * @GET("/user/{name}")
39 |  * Call<ResponseBody> encoded(@Path("name") String name);
40 |  *
41 |  * @GET("/user/{name}")
42 |  * Call<ResponseBody> notEncoded(@Path(value="name", encoded=true) String name);
43 |  * 
44 | * Calling {@code foo.encoded("John+Doe")} yields {@code /user/John%2BDoe} whereas 45 | * {@code foo.notEncoded("John+Doe")} yields {@code /user/John+Doe}. 46 | *

47 | * Path parameters may not be {@code null}. 48 | */ 49 | @Documented 50 | @Retention(RUNTIME) 51 | @Target(PARAMETER) 52 | public @interface Path { 53 | String value(); 54 | 55 | /** 56 | * Specifies whether the argument value to the annotated method parameter is already URL encoded. 57 | */ 58 | boolean encoded() default false; 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Query.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Query parameter appended to the URL. 27 | *

28 | * Values are converted to strings using {@link String#valueOf(Object)} and then URL encoded. 29 | * {@code null} values are ignored. Passing a {@link java.util.List List} or array will result in a 30 | * query parameter for each non-{@code null} item. 31 | *

32 | * Simple Example: 33 | *


34 |  * @GET("/list")
35 |  * Call<ResponseBody> list(@Query("page") int page);
36 |  * 
37 | * Calling with {@code foo.list(1)} yields {@code /list?page=1}. 38 | *

39 | * Example with {@code null}: 40 | *


41 |  * @GET("/list")
42 |  * Call<ResponseBody> list(@Query("category") String category);
43 |  * 
44 | * Calling with {@code foo.list(null)} yields {@code /list}. 45 | *

46 | * Array/Varargs Example: 47 | *


48 |  * @GET("/list")
49 |  * Call<ResponseBody> list(@Query("category") String... categories);
50 |  * 
51 | * Calling with {@code foo.list("bar", "baz")} yields 52 | * {@code /list?category=bar&category=baz}. 53 | *

54 | * Parameter names and values are URL encoded by default. Specify {@link #encoded() encoded=true} 55 | * to change this behavior. 56 | *


57 |  * @GET("/search")
58 |  * Call<ResponseBody> list(@Query(value="foo", encoded=true) String foo);
59 |  * 
60 | * Calling with {@code foo.list("foo+bar"))} yields {@code /search?foo=foo+bar}. 61 | * 62 | * @see QueryMap 63 | */ 64 | @Documented 65 | @Target(PARAMETER) 66 | @Retention(RUNTIME) 67 | public @interface Query { 68 | /** The query parameter name. */ 69 | String value(); 70 | 71 | /** 72 | * Specifies whether the parameter {@linkplain #value() name} and value are already URL encoded. 73 | */ 74 | boolean encoded() default false; 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/QueryMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.PARAMETER; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Query parameter keys and values appended to the URL. 27 | *

28 | * Both keys and values are converted to strings using {@link String#valueOf(Object)}. 29 | *

30 | * Simple Example: 31 | *


32 |  * @GET("/search")
33 |  * Call<ResponseBody> list(@QueryMap Map<String, String> filters);
34 |  * 
35 | * Calling with {@code foo.list(ImmutableMap.of("foo", "bar", "kit", "kat"))} yields 36 | * {@code /search?foo=bar&kit=kat}. 37 | *

38 | * Map keys and values representing parameter values are URL encoded by default. Specify 39 | * {@link #encoded() encoded=true} to change this behavior. 40 | *


41 |  * @GET("/search")
42 |  * Call<ResponseBody> list(@QueryMap(encoded=true) Map<String, String> filters);
43 |  * 
44 | * Calling with {@code foo.list(ImmutableMap.of("foo", "foo+bar"))} yields 45 | * {@code /search?foo=foo+bar}. 46 | *

47 | * A {@code null} value for the map, as a key, or as a value is not allowed. 48 | * 49 | * @see Query 50 | */ 51 | @Documented 52 | @Target(PARAMETER) 53 | @Retention(RUNTIME) 54 | public @interface QueryMap { 55 | /** Specifies whether parameter names and values are already URL encoded. */ 56 | boolean encoded() default false; 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Streaming.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | 22 | import static java.lang.annotation.ElementType.METHOD; 23 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 24 | 25 | /** 26 | * Treat the response body on methods returning {@link okhttp3.Response Response} as is, 27 | * i.e. without converting {@link okhttp3.Response#body() body()} to {@code byte[]}. 28 | */ 29 | @Documented 30 | @Target(METHOD) 31 | @Retention(RUNTIME) 32 | public @interface Streaming { 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/Url.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.http; 17 | 18 | import java.lang.annotation.Documented; 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.Target; 21 | import okhttp3.HttpUrl; 22 | import retrofit2.Retrofit; 23 | 24 | import static java.lang.annotation.ElementType.PARAMETER; 25 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 26 | 27 | /** 28 | * URL resolved against the {@linkplain Retrofit#baseUrl() base URL}. 29 | *


30 |  * @GET
31 |  * Call<ResponseBody> list(@Url String url);
32 |  * 
33 | *

34 | * See {@linkplain Retrofit.Builder#baseUrl(HttpUrl) base URL} for details of how 35 | * the value will be resolved against a base URL to create the full endpoint URL. 36 | */ 37 | @Documented 38 | @Target(PARAMETER) 39 | @Retention(RUNTIME) 40 | public @interface Url { 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/http/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Square, Inc. 2 | 3 | /** Annotations for interface methods to control the HTTP request behavior. */ 4 | package retrofit2.http; 5 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/mock/BehaviorCall.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.mock; 17 | 18 | import java.io.IOException; 19 | import java.util.concurrent.CountDownLatch; 20 | import java.util.concurrent.ExecutorService; 21 | import java.util.concurrent.Future; 22 | import java.util.concurrent.atomic.AtomicReference; 23 | import okhttp3.Request; 24 | import retrofit2.Call; 25 | import retrofit2.Callback; 26 | import retrofit2.Response; 27 | 28 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 29 | 30 | public final class BehaviorCall implements Call { 31 | final NetworkBehavior behavior; 32 | final ExecutorService backgroundExecutor; 33 | final Call delegate; 34 | 35 | private volatile Future task; 36 | volatile boolean canceled; 37 | private volatile boolean executed; 38 | 39 | public BehaviorCall(NetworkBehavior behavior, ExecutorService backgroundExecutor, Call delegate) { 40 | this.behavior = behavior; 41 | this.backgroundExecutor = backgroundExecutor; 42 | this.delegate = delegate; 43 | } 44 | 45 | @SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state. 46 | @Override public Call clone() { 47 | return new BehaviorCall<>(behavior, backgroundExecutor, delegate.clone()); 48 | } 49 | 50 | @Override public Request request() { 51 | return delegate.request(); 52 | } 53 | 54 | @Override public void enqueue(final Callback callback) { 55 | if (callback == null) throw new NullPointerException("callback == null"); 56 | 57 | synchronized (this) { 58 | if (executed) throw new IllegalStateException("Already executed"); 59 | executed = true; 60 | } 61 | task = backgroundExecutor.submit(new Runnable() { 62 | boolean delaySleep() { 63 | long sleepMs = behavior.calculateDelay(MILLISECONDS); 64 | if (sleepMs > 0) { 65 | try { 66 | Thread.sleep(sleepMs); 67 | } catch (InterruptedException e) { 68 | callback.onFailure(BehaviorCall.this, new IOException("canceled")); 69 | return false; 70 | } 71 | } 72 | return true; 73 | } 74 | 75 | @Override public void run() { 76 | if (canceled) { 77 | callback.onFailure(BehaviorCall.this, new IOException("canceled")); 78 | } else if (behavior.calculateIsFailure()) { 79 | if (delaySleep()) { 80 | callback.onFailure(BehaviorCall.this, behavior.failureException()); 81 | } 82 | } else { 83 | delegate.enqueue(new Callback() { 84 | @Override public void onResponse(Call call, Response response) { 85 | if (delaySleep()) { 86 | callback.onResponse(call, response); 87 | } 88 | } 89 | 90 | @Override public void onFailure(Call call, Throwable t) { 91 | if (delaySleep()) { 92 | callback.onFailure(call, t); 93 | } 94 | } 95 | }); 96 | } 97 | } 98 | }); 99 | } 100 | 101 | @Override public synchronized boolean isExecuted() { 102 | return executed; 103 | } 104 | 105 | @Override public Response execute() throws IOException { 106 | final AtomicReference> responseRef = new AtomicReference<>(); 107 | final AtomicReference failureRef = new AtomicReference<>(); 108 | final CountDownLatch latch = new CountDownLatch(1); 109 | enqueue(new Callback() { 110 | @Override public void onResponse(Call call, Response response) { 111 | responseRef.set(response); 112 | latch.countDown(); 113 | } 114 | 115 | @Override public void onFailure(Call call, Throwable t) { 116 | failureRef.set(t); 117 | latch.countDown(); 118 | } 119 | }); 120 | try { 121 | latch.await(); 122 | } catch (InterruptedException e) { 123 | throw new IOException("canceled"); 124 | } 125 | Response response = responseRef.get(); 126 | if (response != null) return response; 127 | Throwable failure = failureRef.get(); 128 | if (failure instanceof RuntimeException) throw (RuntimeException) failure; 129 | if (failure instanceof IOException) throw (IOException) failure; 130 | throw new RuntimeException(failure); 131 | } 132 | 133 | @Override public void cancel() { 134 | canceled = true; 135 | Future task = this.task; 136 | if (task != null) { 137 | task.cancel(true); 138 | } 139 | } 140 | 141 | @Override public boolean isCanceled() { 142 | return canceled; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/mock/Calls.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.mock; 17 | 18 | import java.io.IOException; 19 | import okhttp3.Request; 20 | import retrofit2.Call; 21 | import retrofit2.Callback; 22 | import retrofit2.Response; 23 | 24 | /** Factory methods for creating {@link Call} instances which immediately respond or fail. */ 25 | public final class Calls { 26 | public static Call response(T successValue) { 27 | return response(Response.success(successValue)); 28 | } 29 | 30 | public static Call response(final Response response) { 31 | return new Call() { 32 | @Override public Response execute() throws IOException { 33 | return response; 34 | } 35 | 36 | @Override public void enqueue(Callback callback) { 37 | callback.onResponse(this, response); 38 | } 39 | 40 | @Override public boolean isExecuted() { 41 | return false; 42 | } 43 | 44 | @Override public void cancel() { 45 | } 46 | 47 | @Override public boolean isCanceled() { 48 | return false; 49 | } 50 | 51 | @SuppressWarnings("CloneDoesntCallSuperClone") // Immutable object. 52 | @Override public Call clone() { 53 | return this; 54 | } 55 | 56 | @Override public Request request() { 57 | return response.raw().request(); 58 | } 59 | }; 60 | } 61 | 62 | public static Call failure(final IOException failure) { 63 | return new Call() { 64 | @Override public Response execute() throws IOException { 65 | throw failure; 66 | } 67 | 68 | @Override public void enqueue(Callback callback) { 69 | callback.onFailure(this, failure); 70 | } 71 | 72 | @Override public boolean isExecuted() { 73 | return false; 74 | } 75 | 76 | @Override public void cancel() { 77 | } 78 | 79 | @Override public boolean isCanceled() { 80 | return false; 81 | } 82 | 83 | @SuppressWarnings("CloneDoesntCallSuperClone") // Immutable object. 84 | @Override public Call clone() { 85 | return this; 86 | } 87 | 88 | @Override public Request request() { 89 | return new Request.Builder().url("http://localhost").build(); 90 | } 91 | }; 92 | } 93 | 94 | private Calls() { 95 | throw new AssertionError("No instances."); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/mock/MockRetrofit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2015 Square, 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 | package retrofit2.mock; 17 | 18 | import java.util.concurrent.Executor; 19 | import java.util.concurrent.ExecutorService; 20 | import java.util.concurrent.Executors; 21 | 22 | import retrofit2.BehaviorDelegate; 23 | import retrofit2.Retrofit; 24 | 25 | public final class MockRetrofit { 26 | private final Retrofit retrofit; 27 | private final NetworkBehavior behavior; 28 | private final ExecutorService executor; 29 | 30 | MockRetrofit(Retrofit retrofit, NetworkBehavior behavior, ExecutorService executor) { 31 | this.retrofit = retrofit; 32 | this.behavior = behavior; 33 | this.executor = executor; 34 | } 35 | 36 | public Retrofit retrofit() { 37 | return retrofit; 38 | } 39 | 40 | public NetworkBehavior networkBehavior() { 41 | return behavior; 42 | } 43 | 44 | public Executor backgroundExecutor() { 45 | return executor; 46 | } 47 | 48 | @SuppressWarnings("unchecked") // Single-interface proxy creation guarded by parameter safety. 49 | public BehaviorDelegate create(Class service) { 50 | return new BehaviorDelegate<>(retrofit, behavior, executor, service); 51 | } 52 | 53 | public static final class Builder { 54 | private final Retrofit retrofit; 55 | private NetworkBehavior behavior; 56 | private ExecutorService executor; 57 | 58 | public Builder(Retrofit retrofit) { 59 | if (retrofit == null) throw new NullPointerException("retrofit == null"); 60 | this.retrofit = retrofit; 61 | } 62 | 63 | public Builder networkBehavior(NetworkBehavior behavior) { 64 | if (behavior == null) throw new NullPointerException("behavior == null"); 65 | this.behavior = behavior; 66 | return this; 67 | } 68 | 69 | public Builder backgroundExecutor(ExecutorService executor) { 70 | if (executor == null) throw new NullPointerException("executor == null"); 71 | this.executor = executor; 72 | return this; 73 | } 74 | 75 | public MockRetrofit build() { 76 | if (behavior == null) behavior = NetworkBehavior.create(); 77 | if (executor == null) executor = Executors.newCachedThreadPool(); 78 | return new MockRetrofit(retrofit, behavior, executor); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/mock/NetworkBehavior.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Square, 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 | package retrofit2.mock; 17 | 18 | import java.io.IOException; 19 | import java.util.Random; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 23 | 24 | /** 25 | * A simple emulation of the behavior of network calls. 26 | *

27 | * This class models three properties of a network: 28 | *

    29 | *
  • Delay – the time it takes before a response is received (successful or otherwise).
  • 30 | *
  • Variance – the amount of fluctuation of the delay to be faster or slower.
  • 31 | *
  • Failure - the percentage of operations which fail (such as {@link IOException}).
  • 32 | *
33 | * Behavior can be applied to a Retrofit interface with {@link MockRetrofit}. Behavior can also 34 | * be applied elsewhere using {@link #calculateDelay(TimeUnit)} and {@link #calculateIsFailure()}. 35 | *

36 | * By default, instances of this class will use a 2 second delay with 40% variance and failures 37 | * will occur 3% of the time. 38 | */ 39 | public final class NetworkBehavior { 40 | private static final int DEFAULT_DELAY_MS = 2000; // Network calls will take 2 seconds. 41 | private static final int DEFAULT_VARIANCE_PERCENT = 40; // Network delay varies by ±40%. 42 | private static final int DEFAULT_FAILURE_PERCENT = 3; // 3% of network calls will fail. 43 | 44 | /** Applies {@link NetworkBehavior} to instances of {@code T}. */ 45 | public interface Adapter { 46 | /** 47 | * Apply {@code behavior} to {@code value} so that it exhibits the configured network behavior 48 | * traits when interacted with. 49 | */ 50 | T applyBehavior(NetworkBehavior behavior, T value); 51 | } 52 | 53 | /** Create an instance with default behavior. */ 54 | public static NetworkBehavior create() { 55 | return new NetworkBehavior(new Random()); 56 | } 57 | 58 | /** 59 | * Create an instance with default behavior which uses {@code random} to control variance and 60 | * failure calculation. 61 | */ 62 | public static NetworkBehavior create(Random random) { 63 | if (random == null) throw new NullPointerException("random == null"); 64 | return new NetworkBehavior(random); 65 | } 66 | 67 | private final Random random; 68 | 69 | private volatile long delayMs = DEFAULT_DELAY_MS; 70 | private volatile int variancePercent = DEFAULT_VARIANCE_PERCENT; 71 | private volatile int failurePercent = DEFAULT_FAILURE_PERCENT; 72 | private volatile Throwable failureException = new IOException("Mock failure!"); 73 | 74 | private NetworkBehavior(Random random) { 75 | this.random = random; 76 | } 77 | 78 | /** Set the network round trip delay. */ 79 | public void setDelay(long amount, TimeUnit unit) { 80 | if (amount < 0) { 81 | throw new IllegalArgumentException("Amount must be positive value."); 82 | } 83 | this.delayMs = unit.toMillis(amount); 84 | } 85 | 86 | /** The network round trip delay. */ 87 | public long delay(TimeUnit unit) { 88 | return MILLISECONDS.convert(delayMs, unit); 89 | } 90 | 91 | /** Set the plus-or-minus variance percentage of the network round trip delay. */ 92 | public void setVariancePercent(int variancePercent) { 93 | if (variancePercent < 0 || variancePercent > 100) { 94 | throw new IllegalArgumentException("Variance percentage must be between 0 and 100."); 95 | } 96 | this.variancePercent = variancePercent; 97 | } 98 | 99 | /** The plus-or-minus variance percentage of the network round trip delay. */ 100 | public int variancePercent() { 101 | return variancePercent; 102 | } 103 | 104 | /** Set the percentage of calls to {@link #calculateIsFailure()} that return {@code true}. */ 105 | public void setFailurePercent(int failurePercent) { 106 | if (failurePercent < 0 || failurePercent > 100) { 107 | throw new IllegalArgumentException("Failure percentage must be between 0 and 100."); 108 | } 109 | this.failurePercent = failurePercent; 110 | } 111 | 112 | /** The percentage of calls to {@link #calculateIsFailure()} that return {@code true}. */ 113 | public int failurePercent() { 114 | return failurePercent; 115 | } 116 | 117 | /** Set the exception to be used when a failure is triggered. */ 118 | public void setFailureException(Throwable t) { 119 | if (t == null) { 120 | throw new NullPointerException("t == null"); 121 | } 122 | this.failureException = t; 123 | } 124 | 125 | /** The exception to be used when a failure is triggered. */ 126 | public Throwable failureException() { 127 | return failureException; 128 | } 129 | 130 | /** 131 | * Randomly determine whether this call should result in a network failure in accordance with 132 | * configured behavior. When true, {@link #failureException()} should be thrown. 133 | */ 134 | public boolean calculateIsFailure() { 135 | int randomValue = random.nextInt(100); 136 | return randomValue < failurePercent; 137 | } 138 | 139 | /** 140 | * Get the delay that should be used for delaying a response in accordance with configured 141 | * behavior. 142 | */ 143 | public long calculateDelay(TimeUnit unit) { 144 | float delta = variancePercent / 100f; // e.g., 20 / 100f == 0.2f 145 | float lowerBound = 1f - delta; // 0.2f --> 0.8f 146 | float upperBound = 1f + delta; // 0.2f --> 1.2f 147 | float bound = upperBound - lowerBound; // 1.2f - 0.8f == 0.4f 148 | float delayPercent = lowerBound + (random.nextFloat() * bound); // 0.8 + (rnd * 0.4) 149 | long callDelayMs = (long) (delayMs * delayPercent); 150 | return MILLISECONDS.convert(callDelayMs, unit); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/retrofit2/package-info.java: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Square, Inc. 2 | 3 | /** 4 | * Retrofit turns your REST API into a Java interface. 5 | *

 6 |  * public interface GitHubService {
 7 |  *   @GET("/users/{user}/repos")
 8 |  *   List<Repo> listRepos(@Path("user") String user);
 9 |  * }
10 |  * 
11 | */ 12 | package retrofit2; 13 | --------------------------------------------------------------------------------