Skip to content

Commit

Permalink
Add delegation support to CallAdapter.Factory.
Browse files Browse the repository at this point in the history
  • Loading branch information
JakeWharton committed Sep 23, 2015
1 parent b6a657a commit eddc0ae
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public static RxJavaCallAdapterFactory create() {
private RxJavaCallAdapterFactory() {
}

@Override public CallAdapter<?> get(Type returnType, Annotation[] annotations) {
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
Class<?> rawType = Utils.getRawType(returnType);
boolean isSingle = "rx.Single".equals(rawType.getCanonicalName());
if (rawType != Observable.class && !isSingle) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ interface Service {
@GET("/") Single<Result<String>> singleResult();
}

private Retrofit retrofit;
private Service service;

@Before public void setUp() {
Retrofit retrofit = new Retrofit.Builder()
retrofit = new Retrofit.Builder()
.baseUrl(server.url("/"))
.addConverterFactory(new StringConverterFactory())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
Expand Down Expand Up @@ -159,36 +160,40 @@ interface Service {
@Test public void responseType() {
CallAdapter.Factory factory = RxJavaCallAdapterFactory.create();
Type classType = new TypeToken<Observable<String>>() {}.getType();
assertThat(factory.get(classType, NO_ANNOTATIONS).responseType()).isEqualTo(String.class);
assertThat(factory.get(classType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(String.class);
Type wilcardType = new TypeToken<Observable<? extends String>>() {}.getType();
assertThat(factory.get(wilcardType, NO_ANNOTATIONS).responseType()).isEqualTo(String.class);
assertThat(factory.get(wilcardType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(String.class);
Type genericType = new TypeToken<Observable<List<String>>>() {}.getType();
assertThat(factory.get(genericType, NO_ANNOTATIONS).responseType()) //
assertThat(factory.get(genericType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(new TypeToken<List<String>>() {}.getType());
Type responseType = new TypeToken<Observable<Response<String>>>() {}.getType();
assertThat(factory.get(responseType, NO_ANNOTATIONS).responseType()).isEqualTo(String.class);
assertThat(factory.get(responseType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(String.class);
Type resultType = new TypeToken<Observable<Response<String>>>() {}.getType();
assertThat(factory.get(resultType, NO_ANNOTATIONS).responseType()).isEqualTo(String.class);
assertThat(factory.get(resultType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(String.class);
}

@Test public void nonObservableTypeReturnsNull() {
CallAdapter.Factory factory = RxJavaCallAdapterFactory.create();
CallAdapter<?> adapter = factory.get(String.class, NO_ANNOTATIONS);
CallAdapter<?> adapter = factory.get(String.class, NO_ANNOTATIONS, retrofit);
assertThat(adapter).isNull();
}

@Test public void rawTypeThrows() {
CallAdapter.Factory factory = RxJavaCallAdapterFactory.create();
Type observableType = new TypeToken<Observable>() {}.getType();
try {
factory.get(observableType, NO_ANNOTATIONS);
factory.get(observableType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Observable return type must be parameterized as Observable<Foo> or Observable<? extends Foo>");
}
Type singleType = new TypeToken<Single>() {}.getType();
try {
factory.get(singleType, NO_ANNOTATIONS);
factory.get(singleType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Single return type must be parameterized as Single<Foo> or Single<? extends Foo>");
Expand All @@ -199,14 +204,14 @@ interface Service {
CallAdapter.Factory factory = RxJavaCallAdapterFactory.create();
Type observableType = new TypeToken<Observable<Response>>() {}.getType();
try {
factory.get(observableType, NO_ANNOTATIONS);
factory.get(observableType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Response must be parameterized as Response<Foo> or Response<? extends Foo>");
}
Type singleType = new TypeToken<Single<Response>>() {}.getType();
try {
factory.get(singleType, NO_ANNOTATIONS);
factory.get(singleType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Response must be parameterized as Response<Foo> or Response<? extends Foo>");
Expand All @@ -217,14 +222,14 @@ interface Service {
CallAdapter.Factory factory = RxJavaCallAdapterFactory.create();
Type observableType = new TypeToken<Observable<Result>>() {}.getType();
try {
factory.get(observableType, NO_ANNOTATIONS);
factory.get(observableType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Result must be parameterized as Result<Foo> or Result<? extends Foo>");
}
Type singleType = new TypeToken<Single<Result>>() {}.getType();
try {
factory.get(singleType, NO_ANNOTATIONS);
factory.get(singleType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Result must be parameterized as Result<Foo> or Result<? extends Foo>");
Expand Down
2 changes: 1 addition & 1 deletion retrofit/src/main/java/retrofit/CallAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,6 @@ interface Factory {
* Returns a call adapter for interface methods that return {@code returnType}, or null if this
* factory doesn't adapt that type.
*/
CallAdapter<?> get(Type returnType, Annotation[] annotations);
CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit);
}
}
5 changes: 3 additions & 2 deletions retrofit/src/main/java/retrofit/DefaultCallAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@
* is a thread provided by OkHttp's dispatcher.
*/
final class DefaultCallAdapter implements CallAdapter<Call<?>> {
public static final Factory FACTORY = new Factory() {
@Override public CallAdapter<?> get(Type returnType, Annotation[] annotations) {
static final Factory FACTORY = new Factory() {
@Override
public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (Utils.getRawType(returnType) != Call.class) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ final class ExecutorCallAdapterFactory implements CallAdapter.Factory {
this.callbackExecutor = callbackExecutor;
}

@Override public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations) {
@Override
public CallAdapter<Call<?>> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
if (Utils.getRawType(returnType) != Call.class) {
return null;
}
Expand Down
18 changes: 8 additions & 10 deletions retrofit/src/main/java/retrofit/MethodHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,17 @@

final class MethodHandler<T> {
@SuppressWarnings("unchecked")
static MethodHandler<?> create(Method method, OkHttpClient client, BaseUrl baseUrl,
List<CallAdapter.Factory> callAdapterFactories, List<Converter.Factory> converterFactories) {
CallAdapter<Object> callAdapter =
(CallAdapter<Object>) createCallAdapter(method, callAdapterFactories);
static MethodHandler<?> create(Retrofit retrofit, Method method) {
CallAdapter<Object> callAdapter = (CallAdapter<Object>) createCallAdapter(method, retrofit);
Converter<ResponseBody, Object> responseConverter =
(Converter<ResponseBody, Object>) createResponseConverter(method,
callAdapter.responseType(), converterFactories);
RequestFactory requestFactory = RequestFactoryParser.parse(method, baseUrl, converterFactories);
return new MethodHandler<>(client, requestFactory, callAdapter, responseConverter);
callAdapter.responseType(), retrofit.converterFactories());
RequestFactory requestFactory =
RequestFactoryParser.parse(method, retrofit.baseUrl(), retrofit.converterFactories());
return new MethodHandler<>(retrofit.client(), requestFactory, callAdapter, responseConverter);
}

private static CallAdapter<?> createCallAdapter(Method method,
List<CallAdapter.Factory> adapterFactories) {
private static CallAdapter<?> createCallAdapter(Method method, Retrofit retrofit) {
Type returnType = method.getGenericReturnType();
if (Utils.hasUnresolvableType(returnType)) {
throw Utils.methodError(method,
Expand All @@ -47,7 +45,7 @@ private static CallAdapter<?> createCallAdapter(Method method,
}
Annotation[] annotations = method.getAnnotations();
try {
return Utils.resolveCallAdapter(adapterFactories, returnType, annotations);
return retrofit.callAdapter(returnType, annotations);
} catch (RuntimeException e) { // Wide exception range because factories are user code.
throw Utils.methodError(e, method, "Unable to create call adapter for %s", returnType);
}
Expand Down
53 changes: 47 additions & 6 deletions retrofit/src/main/java/retrofit/Retrofit.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.ResponseBody;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -146,8 +148,7 @@ MethodHandler<?> loadMethodHandler(Method method) {
synchronized (methodHandlerCache) {
handler = methodHandlerCache.get(method);
if (handler == null) {
handler =
MethodHandler.create(method, client, baseUrl, adapterFactories, converterFactories);
handler = MethodHandler.create(this, method);
methodHandlerCache.put(method, handler);
}
}
Expand All @@ -162,17 +163,57 @@ public BaseUrl baseUrl() {
return baseUrl;
}

public List<CallAdapter.Factory> callAdapterFactories() {
return Collections.unmodifiableList(adapterFactories);
}

/**
* Returns the {@link CallAdapter} for {@code returnType} from the available {@linkplain
* #callAdapterFactories() factories}.
*/
public CallAdapter<?> callAdapter(Type returnType, Annotation[] annotations) {
return nextCallAdapter(null, returnType, annotations);
}

/**
* Returns the {@link CallAdapter} for {@code returnType} from the available {@linkplain
* #callAdapterFactories() factories} except {@code skipPast}.
*/
public CallAdapter<?> nextCallAdapter(CallAdapter.Factory skipPast, Type returnType,
Annotation[] annotations) {
checkNotNull(returnType, "returnType == null");
checkNotNull(annotations, "annotations == null");

int start = adapterFactories.indexOf(skipPast) + 1;
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}

StringBuilder builder = new StringBuilder("Could not locate call adapter for ")
.append(returnType)
.append(". Tried:");
for (int i = start, count = adapterFactories.size(); i < count; i++) {
builder.append("\n * ").append(adapterFactories.get(i).getClass().getName());
}
if (skipPast != null) {
builder.append("\nSkipped:");
for (int i = 0; i < start; i++) {
builder.append("\n * ").append(adapterFactories.get(i).getClass().getName());
}
}
throw new IllegalArgumentException(builder.toString());
}

/**
* TODO
*/
public List<Converter.Factory> converterFactories() {
return Collections.unmodifiableList(converterFactories);
}

public List<CallAdapter.Factory> callAdapterFactories() {
return Collections.unmodifiableList(adapterFactories);
}

public Executor callbackExecutor() {
return callbackExecutor;
}
Expand Down
18 changes: 0 additions & 18 deletions retrofit/src/main/java/retrofit/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,24 +62,6 @@ static boolean isAnnotationPresent(Annotation[] annotations,
return false;
}

static CallAdapter<?> resolveCallAdapter(List<CallAdapter.Factory> adapterFactories, Type type,
Annotation[] annotations) {
for (int i = 0, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?> adapter = adapterFactories.get(i).get(type, annotations);
if (adapter != null) {
return adapter;
}
}

StringBuilder builder = new StringBuilder("Could not locate call adapter for ")
.append(type)
.append(". Tried:");
for (CallAdapter.Factory adapterFactory : adapterFactories) {
builder.append("\n * ").append(adapterFactory.getClass().getName());
}
throw new IllegalArgumentException(builder.toString());
}

static Converter<?, RequestBody> resolveRequestBodyConverter(
List<Converter.Factory> converterFactories, Type type, Annotation[] annotations) {
for (int i = 0, count = converterFactories.size(); i < count; i++) {
Expand Down
30 changes: 20 additions & 10 deletions retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
public final class ExecutorCallAdapterFactoryTest {
private static final Annotation[] NO_ANNOTATIONS = new Annotation[0];

private final Retrofit retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:1")
.build();
private final Callback<String> callback = mock(Callback.class);
private final Executor callbackExecutor = spy(new Executor() {
@Override public void execute(Runnable runnable) {
Expand All @@ -45,7 +48,7 @@ public final class ExecutorCallAdapterFactoryTest {

@Test public void rawTypeThrows() {
try {
factory.get(Call.class, NO_ANNOTATIONS);
factory.get(Call.class, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Call return type must be parameterized as Call<Foo> or Call<? extends Foo>");
Expand All @@ -55,7 +58,7 @@ public final class ExecutorCallAdapterFactoryTest {
@Test public void responseThrows() {
Type returnType = new TypeToken<Call<Response<String>>>() {}.getType();
try {
factory.get(returnType, NO_ANNOTATIONS);
factory.get(returnType, NO_ANNOTATIONS, retrofit);
fail();
} catch (IllegalArgumentException e) {
assertThat(e).hasMessage("Call<T> cannot use Response as its generic parameter. "
Expand All @@ -65,17 +68,20 @@ public final class ExecutorCallAdapterFactoryTest {

@Test public void responseType() {
Type classType = new TypeToken<Call<String>>() {}.getType();
assertThat(factory.get(classType, NO_ANNOTATIONS).responseType()).isEqualTo(String.class);
assertThat(factory.get(classType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(String.class);
Type wilcardType = new TypeToken<Call<? extends String>>() {}.getType();
assertThat(factory.get(wilcardType, NO_ANNOTATIONS).responseType()).isEqualTo(String.class);
assertThat(factory.get(wilcardType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(String.class);
Type genericType = new TypeToken<Call<List<String>>>() {}.getType();
assertThat(factory.get(genericType, NO_ANNOTATIONS).responseType()) //
assertThat(factory.get(genericType, NO_ANNOTATIONS, retrofit).responseType())
.isEqualTo(new TypeToken<List<String>>() {}.getType());
}

@Test public void adaptedCallExecute() throws IOException {
Type returnType = new TypeToken<Call<String>>() {}.getType();
CallAdapter<Call<?>> adapter = (CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS);
CallAdapter<Call<?>> adapter =
(CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS, retrofit);
final Response<String> response = Response.success("Hi");
Call<String> call = (Call<String>) adapter.adapt(new EmptyCall() {
@Override public Response<String> execute() throws IOException {
Expand All @@ -87,7 +93,8 @@ public final class ExecutorCallAdapterFactoryTest {

@Test public void adaptedCallEnqueueUsesExecutorForSuccessCallback() {
Type returnType = new TypeToken<Call<String>>() {}.getType();
CallAdapter<Call<?>> adapter = (CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS);
CallAdapter<Call<?>> adapter =
(CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS, retrofit);
final Response<String> response = Response.success("Hi");
Call<String> call = (Call<String>) adapter.adapt(new EmptyCall() {
@Override public void enqueue(Callback<String> callback) {
Expand All @@ -101,7 +108,8 @@ public final class ExecutorCallAdapterFactoryTest {

@Test public void adaptedCallEnqueueUsesExecutorForFailureCallback() {
Type returnType = new TypeToken<Call<String>>() {}.getType();
CallAdapter<Call<?>> adapter = (CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS);
CallAdapter<Call<?>> adapter =
(CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS, retrofit);
final Throwable throwable = new IOException();
Call<String> call = (Call<String>) adapter.adapt(new EmptyCall() {
@Override public void enqueue(Callback<String> callback) {
Expand All @@ -117,7 +125,8 @@ public final class ExecutorCallAdapterFactoryTest {

@Test public void adaptedCallCloneDeepCopy() {
Type returnType = new TypeToken<Call<String>>() {}.getType();
CallAdapter<Call<?>> adapter = (CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS);
CallAdapter<Call<?>> adapter =
(CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS, retrofit);
Call<String> delegate = mock(Call.class);
Call<String> call = (Call<String>) adapter.adapt(delegate);
Call<String> cloned = call.clone();
Expand All @@ -128,7 +137,8 @@ public final class ExecutorCallAdapterFactoryTest {

@Test public void adaptedCallCancel() {
Type returnType = new TypeToken<Call<String>>() {}.getType();
CallAdapter<Call<?>> adapter = (CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS);
CallAdapter<Call<?>> adapter =
(CallAdapter<Call<?>>) factory.get(returnType, NO_ANNOTATIONS, retrofit);
Call<String> delegate = mock(Call.class);
Call<String> call = (Call<String>) adapter.adapt(delegate);
call.cancel();
Expand Down
Loading

0 comments on commit eddc0ae

Please sign in to comment.