Skip to content

Commit

Permalink
Merge pull request #912 from square/jw/rearchitect-parsing
Browse files Browse the repository at this point in the history
Switch the architecture of method parsing and invocation.
  • Loading branch information
JakeWharton committed Jun 21, 2015
2 parents 2f973d1 + f15eee4 commit 03439b7
Show file tree
Hide file tree
Showing 14 changed files with 427 additions and 336 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ final class DefaultCallAdapterFactory implements CallAdapter.Factory {
}

@Override public String toString() {
return "Built-in CallAdapterFactory";
return "Default CallAdapterFactory";
}

@Override public CallAdapter<?> get(Type returnType) {
Expand Down
97 changes: 97 additions & 0 deletions retrofit/src/main/java/retrofit/MethodHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit;

import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.ResponseBody;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import retrofit.http.Streaming;

import static retrofit.Utils.methodError;

final class MethodHandler<T> {
@SuppressWarnings("unchecked")
static MethodHandler<?> create(Method method, OkHttpClient client, Endpoint endpoint,
CallAdapter.Factory callAdapterFactory, Converter.Factory converterFactory) {
CallAdapter<Object> callAdapter =
(CallAdapter<Object>) createCallAdapter(method, callAdapterFactory);
Converter<Object> responseConverter =
(Converter<Object>) createResponseConverter(method, callAdapter.responseType(),
converterFactory);
RequestFactory requestFactory = RequestFactoryParser.parse(method, endpoint, converterFactory);
return new MethodHandler<>(client, requestFactory, callAdapter, responseConverter);
}

private static CallAdapter<?> createCallAdapter(Method method,
CallAdapter.Factory adapterFactory) {
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw methodError(method,
"Method return type must not include a type variable or wildcard: %s", returnType);
}

if (returnType == void.class) {
throw methodError(method, "Service methods cannot return void.");
}

CallAdapter<?> adapter = adapterFactory.get(returnType);
if (adapter == null) {
throw methodError(method, "Call adapter factory '%s' was unable to handle return type %s",
adapterFactory, returnType);
}
return adapter;
}

private static Converter<?> createResponseConverter(Method method, Type responseType,
Converter.Factory converterFactory) {
if (responseType == ResponseBody.class) {
boolean isStreaming = method.isAnnotationPresent(Streaming.class);
return new OkHttpResponseBodyConverter(isStreaming);
}

if (converterFactory == null) {
throw methodError(method, "Method response type is "
+ responseType
+ " but no converter factory registered. "
+ "Either add a converter factory to the Retrofit instance or use ResponseBody.");
}

Converter<?> converter = converterFactory.get(responseType);
if (converter == null) {
throw methodError(method, "Converter factory '%s' was unable to handle response type %s",
converterFactory, responseType);
}
return converter;
}

private final OkHttpClient client;
private final RequestFactory requestFactory;
private final CallAdapter<T> callAdapter;
private final Converter<T> responseConverter;

private MethodHandler(OkHttpClient client, RequestFactory requestFactory,
CallAdapter<T> callAdapter, Converter<T> responseConverter) {
this.client = client;
this.requestFactory = requestFactory;
this.callAdapter = callAdapter;
this.responseConverter = responseConverter;
}

Object invoke(Object... args) {
return callAdapter.adapt(new OkHttpCall<>(client, requestFactory, responseConverter, args));
}
}
32 changes: 8 additions & 24 deletions retrofit/src/main/java/retrofit/OkHttpCall.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,33 @@
*/
package retrofit;

import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;

import static retrofit.Utils.closeQueitly;
import static retrofit.Utils.closeQuietly;

final class OkHttpCall<T> implements Call<T> {
private final OkHttpClient client;
private final Endpoint endpoint;
private final RequestFactory requestFactory;
private final Converter<T> responseConverter;
private final MethodInfo methodInfo;
private final Object[] args;

private volatile com.squareup.okhttp.Call rawCall;
private boolean executed; // Guarded by this.

OkHttpCall(OkHttpClient client, Endpoint endpoint, Converter<T> responseConverter,
MethodInfo methodInfo, Object[] args) {
OkHttpCall(OkHttpClient client, RequestFactory requestFactory, Converter<T> responseConverter,
Object[] args) {
this.client = client;
this.endpoint = endpoint;
this.requestFactory = requestFactory;
this.responseConverter = responseConverter;
this.methodInfo = methodInfo;
this.args = args;
}

@SuppressWarnings("CloneDoesntCallSuperClone") // We are a final type & this saves clearing state.
@Override public OkHttpCall<T> clone() {
return new OkHttpCall<>(client, endpoint, responseConverter, methodInfo, args);
return new OkHttpCall<>(client, requestFactory, responseConverter, args);
}

public void enqueue(final Callback<T> callback) {
Expand Down Expand Up @@ -109,20 +106,7 @@ public Response<T> execute() throws IOException {
}

private com.squareup.okhttp.Call createRawCall() {
HttpUrl url = endpoint.url();
RequestBuilder requestBuilder = new RequestBuilder(url, methodInfo);

Object[] args = this.args;
if (args != null) {
RequestBuilderAction[] actions = methodInfo.requestBuilderActions;
for (int i = 0, count = args.length; i < count; i++) {
actions[i].perform(requestBuilder, args[i]);
}
}

Request request = requestBuilder.build();

return client.newCall(request);
return client.newCall(requestFactory.create(args));
}

private Response<T> parseResponse(com.squareup.okhttp.Response rawResponse) throws IOException {
Expand All @@ -140,7 +124,7 @@ private Response<T> parseResponse(com.squareup.okhttp.Response rawResponse) thro
ResponseBody bufferedBody = Utils.readBodyToBytesIfNecessary(rawBody);
return Response.error(bufferedBody, rawResponse);
} finally {
closeQueitly(rawBody);
closeQuietly(rawBody);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import com.squareup.okhttp.ResponseBody;
import java.io.IOException;

import static retrofit.Utils.closeQueitly;
import static retrofit.Utils.closeQuietly;

final class OkHttpResponseBodyConverter implements Converter<ResponseBody> {
private final boolean isStreaming;
Expand All @@ -37,7 +37,7 @@ final class OkHttpResponseBodyConverter implements Converter<ResponseBody> {
try {
return Utils.readBodyToBytesIfNecessary(body);
} finally {
closeQueitly(body);
closeQuietly(body);
}
}

Expand Down
68 changes: 31 additions & 37 deletions retrofit/src/main/java/retrofit/RequestBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,51 +28,44 @@
import okio.BufferedSink;

final class RequestBuilder {
private final String requestMethod;
private final boolean requestHasBody;
private final String method;
private final HttpUrl.Builder urlBuilder;
private String pathUrl;

private final Request.Builder requestBuilder;
private MediaType mediaType;

private final boolean hasBody;
private MultipartBuilder multipartBuilder;
private FormEncodingBuilder formEncodingBuilder;
private RequestBody body;

private String relativeUrl;
private MediaType mediaType;

RequestBuilder(HttpUrl url, MethodInfo methodInfo) {
requestMethod = methodInfo.requestMethod;
requestHasBody = methodInfo.requestHasBody;
mediaType = methodInfo.mediaType;
relativeUrl = methodInfo.requestUrl;
RequestBuilder(String method, HttpUrl url, String pathUrl, String queryParams, Headers headers,
MediaType mediaType, boolean hasBody, boolean isFormEncoded, boolean isMultipart) {
this.method = method;

urlBuilder = url.newBuilder();
requestBuilder = new Request.Builder();
HttpUrl.Builder urlBuilder = url.newBuilder();
if (queryParams != null) {
urlBuilder.query(queryParams);
}
this.urlBuilder = urlBuilder;
this.pathUrl = pathUrl;

Headers headers = methodInfo.headers;
Request.Builder requestBuilder = new Request.Builder();
if (headers != null) {
requestBuilder.headers(headers);
}
this.requestBuilder = requestBuilder;
this.mediaType = mediaType;

String requestQuery = methodInfo.requestQuery;
if (requestQuery != null) {
urlBuilder.query(requestQuery);
}
this.hasBody = hasBody;

switch (methodInfo.bodyEncoding) {
case FORM_URL_ENCODED:
// Will be set to 'body' in 'build'.
formEncodingBuilder = new FormEncodingBuilder();
break;
case MULTIPART:
// Will be set to 'body' in 'build'.
multipartBuilder = new MultipartBuilder();
break;
case NONE:
// If present, 'body' will be set in 'setArguments' call.
break;
default:
throw new IllegalArgumentException("Unknown request type: " + methodInfo.bodyEncoding);
if (isFormEncoded) {
// Will be set to 'body' in 'build'.
formEncodingBuilder = new FormEncodingBuilder();
} else if (isMultipart) {
// Will be set to 'body' in 'build'.
multipartBuilder = new MultipartBuilder();
}
}

Expand All @@ -92,9 +85,9 @@ void addPathParam(String name, String value, boolean encoded) {
// encode spaces rather than +. Query encoding difference specified in HTML spec.
// Any remaining plus signs represent spaces as already URLEncoded.
encodedValue = encodedValue.replace("+", "%20");
relativeUrl = relativeUrl.replace("{" + name + "}", encodedValue);
pathUrl = pathUrl.replace("{" + name + "}", encodedValue);
} else {
relativeUrl = relativeUrl.replace("{" + name + "}", String.valueOf(value));
pathUrl = pathUrl.replace("{" + name + "}", String.valueOf(value));
}
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(
Expand Down Expand Up @@ -127,7 +120,8 @@ void setBody(RequestBody body) {
}

Request build() {
urlBuilder.encodedPath(relativeUrl);
// TODO this should append, not replace.
HttpUrl url = urlBuilder.encodedPath(pathUrl).build();

RequestBody body = this.body;
if (body == null) {
Expand All @@ -136,7 +130,7 @@ Request build() {
body = formEncodingBuilder.build();
} else if (multipartBuilder != null) {
body = multipartBuilder.build();
} else if (requestHasBody) {
} else if (hasBody) {
// Body is absent, make an empty body.
body = RequestBody.create(null, new byte[0]);
}
Expand All @@ -152,8 +146,8 @@ Request build() {
}

return requestBuilder
.url(urlBuilder.build())
.method(requestMethod, body)
.url(url)
.method(method, body)
.build();
}

Expand Down
65 changes: 65 additions & 0 deletions retrofit/src/main/java/retrofit/RequestFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package retrofit;

import com.squareup.okhttp.Headers;
import com.squareup.okhttp.HttpUrl;
import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.Request;

final class RequestFactory {
private final String method;
private final Endpoint endpoint;
private final String pathUrl;
private final String queryParams;
private final Headers headers;
private final MediaType mediaType;
private final boolean hasBody;
private final boolean isFormEncoded;
private final boolean isMultipart;
private final RequestBuilderAction[] requestBuilderActions;

RequestFactory(String method, Endpoint endpoint, String pathUrl, String queryParams,
Headers headers, MediaType mediaType, boolean hasBody, boolean isFormEncoded,
boolean isMultipart, RequestBuilderAction[] requestBuilderActions) {
this.method = method;
this.endpoint = endpoint;
this.pathUrl = pathUrl;
this.queryParams = queryParams;
this.headers = headers;
this.mediaType = mediaType;
this.hasBody = hasBody;
this.isFormEncoded = isFormEncoded;
this.isMultipart = isMultipart;
this.requestBuilderActions = requestBuilderActions;
}

Request create(Object... args) {
HttpUrl url = endpoint.url();
RequestBuilder requestBuilder =
new RequestBuilder(method, url, pathUrl, queryParams, headers, mediaType, hasBody,
isFormEncoded, isMultipart);

if (args != null) {
RequestBuilderAction[] actions = requestBuilderActions;
for (int i = 0, count = args.length; i < count; i++) {
actions[i].perform(requestBuilder, args[i]);
}
}

return requestBuilder.build();
}
}
Loading

0 comments on commit 03439b7

Please sign in to comment.