From eddc0aea76968547b8caf6eeef4663c2ab84714d Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Wed, 23 Sep 2015 01:20:55 -0400 Subject: [PATCH] Add delegation support to CallAdapter.Factory. --- .../retrofit/RxJavaCallAdapterFactory.java | 3 +- .../RxJavaCallAdapterFactoryTest.java | 31 ++-- .../src/main/java/retrofit/CallAdapter.java | 2 +- .../java/retrofit/DefaultCallAdapter.java | 5 +- .../retrofit/ExecutorCallAdapterFactory.java | 3 +- .../src/main/java/retrofit/MethodHandler.java | 18 +-- retrofit/src/main/java/retrofit/Retrofit.java | 53 +++++- retrofit/src/main/java/retrofit/Utils.java | 18 --- .../ExecutorCallAdapterFactoryTest.java | 30 ++-- .../src/test/java/retrofit/RetrofitTest.java | 153 +++++++++++++++++- .../example/retrofit/CustomCallAdapter.java | 4 +- 11 files changed, 253 insertions(+), 67 deletions(-) diff --git a/retrofit-adapters/rxjava/src/main/java/retrofit/RxJavaCallAdapterFactory.java b/retrofit-adapters/rxjava/src/main/java/retrofit/RxJavaCallAdapterFactory.java index 279d13eb92..d955651db3 100644 --- a/retrofit-adapters/rxjava/src/main/java/retrofit/RxJavaCallAdapterFactory.java +++ b/retrofit-adapters/rxjava/src/main/java/retrofit/RxJavaCallAdapterFactory.java @@ -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) { diff --git a/retrofit-adapters/rxjava/src/test/java/retrofit/RxJavaCallAdapterFactoryTest.java b/retrofit-adapters/rxjava/src/test/java/retrofit/RxJavaCallAdapterFactoryTest.java index 142c4c9d69..b37b3c4675 100644 --- a/retrofit-adapters/rxjava/src/test/java/retrofit/RxJavaCallAdapterFactoryTest.java +++ b/retrofit-adapters/rxjava/src/test/java/retrofit/RxJavaCallAdapterFactoryTest.java @@ -52,10 +52,11 @@ interface Service { @GET("/") Single> 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()) @@ -159,21 +160,25 @@ interface Service { @Test public void responseType() { CallAdapter.Factory factory = RxJavaCallAdapterFactory.create(); Type classType = new TypeToken>() {}.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>() {}.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>>() {}.getType(); - assertThat(factory.get(genericType, NO_ANNOTATIONS).responseType()) // + assertThat(factory.get(genericType, NO_ANNOTATIONS, retrofit).responseType()) .isEqualTo(new TypeToken>() {}.getType()); Type responseType = new TypeToken>>() {}.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>>() {}.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(); } @@ -181,14 +186,14 @@ interface Service { CallAdapter.Factory factory = RxJavaCallAdapterFactory.create(); Type observableType = new TypeToken() {}.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 or Observable"); } Type singleType = new TypeToken() {}.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 or Single"); @@ -199,14 +204,14 @@ interface Service { CallAdapter.Factory factory = RxJavaCallAdapterFactory.create(); Type observableType = new TypeToken>() {}.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 or Response"); } Type singleType = new TypeToken>() {}.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 or Response"); @@ -217,14 +222,14 @@ interface Service { CallAdapter.Factory factory = RxJavaCallAdapterFactory.create(); Type observableType = new TypeToken>() {}.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 or Result"); } Type singleType = new TypeToken>() {}.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 or Result"); diff --git a/retrofit/src/main/java/retrofit/CallAdapter.java b/retrofit/src/main/java/retrofit/CallAdapter.java index 4fd172a6b9..d8dba7b004 100644 --- a/retrofit/src/main/java/retrofit/CallAdapter.java +++ b/retrofit/src/main/java/retrofit/CallAdapter.java @@ -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); } } diff --git a/retrofit/src/main/java/retrofit/DefaultCallAdapter.java b/retrofit/src/main/java/retrofit/DefaultCallAdapter.java index 220169a483..64f9bf9130 100644 --- a/retrofit/src/main/java/retrofit/DefaultCallAdapter.java +++ b/retrofit/src/main/java/retrofit/DefaultCallAdapter.java @@ -24,8 +24,9 @@ * is a thread provided by OkHttp's dispatcher. */ final class DefaultCallAdapter implements CallAdapter> { - 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; } diff --git a/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java b/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java index 655f60e250..218fc1f785 100644 --- a/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java +++ b/retrofit/src/main/java/retrofit/ExecutorCallAdapterFactory.java @@ -27,7 +27,8 @@ final class ExecutorCallAdapterFactory implements CallAdapter.Factory { this.callbackExecutor = callbackExecutor; } - @Override public CallAdapter> get(Type returnType, Annotation[] annotations) { + @Override + public CallAdapter> get(Type returnType, Annotation[] annotations, Retrofit retrofit) { if (Utils.getRawType(returnType) != Call.class) { return null; } diff --git a/retrofit/src/main/java/retrofit/MethodHandler.java b/retrofit/src/main/java/retrofit/MethodHandler.java index 2312996f6f..b269a7ccfa 100644 --- a/retrofit/src/main/java/retrofit/MethodHandler.java +++ b/retrofit/src/main/java/retrofit/MethodHandler.java @@ -24,19 +24,17 @@ final class MethodHandler { @SuppressWarnings("unchecked") - static MethodHandler create(Method method, OkHttpClient client, BaseUrl baseUrl, - List callAdapterFactories, List converterFactories) { - CallAdapter callAdapter = - (CallAdapter) createCallAdapter(method, callAdapterFactories); + static MethodHandler create(Retrofit retrofit, Method method) { + CallAdapter callAdapter = (CallAdapter) createCallAdapter(method, retrofit); Converter responseConverter = (Converter) 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 adapterFactories) { + private static CallAdapter createCallAdapter(Method method, Retrofit retrofit) { Type returnType = method.getGenericReturnType(); if (Utils.hasUnresolvableType(returnType)) { throw Utils.methodError(method, @@ -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); } diff --git a/retrofit/src/main/java/retrofit/Retrofit.java b/retrofit/src/main/java/retrofit/Retrofit.java index 50562c3ff1..2306579330 100644 --- a/retrofit/src/main/java/retrofit/Retrofit.java +++ b/retrofit/src/main/java/retrofit/Retrofit.java @@ -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; @@ -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); } } @@ -162,6 +163,50 @@ public BaseUrl baseUrl() { return baseUrl; } + public List 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 */ @@ -169,10 +214,6 @@ public List converterFactories() { return Collections.unmodifiableList(converterFactories); } - public List callAdapterFactories() { - return Collections.unmodifiableList(adapterFactories); - } - public Executor callbackExecutor() { return callbackExecutor; } diff --git a/retrofit/src/main/java/retrofit/Utils.java b/retrofit/src/main/java/retrofit/Utils.java index 053b12d0d8..751b9b1158 100644 --- a/retrofit/src/main/java/retrofit/Utils.java +++ b/retrofit/src/main/java/retrofit/Utils.java @@ -62,24 +62,6 @@ static boolean isAnnotationPresent(Annotation[] annotations, return false; } - static CallAdapter resolveCallAdapter(List 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 resolveRequestBodyConverter( List converterFactories, Type type, Annotation[] annotations) { for (int i = 0, count = converterFactories.size(); i < count; i++) { diff --git a/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java b/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java index 1032cf461c..393077d827 100644 --- a/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java +++ b/retrofit/src/test/java/retrofit/ExecutorCallAdapterFactoryTest.java @@ -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 callback = mock(Callback.class); private final Executor callbackExecutor = spy(new Executor() { @Override public void execute(Runnable runnable) { @@ -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 or Call"); @@ -55,7 +58,7 @@ public final class ExecutorCallAdapterFactoryTest { @Test public void responseThrows() { Type returnType = new TypeToken>>() {}.getType(); try { - factory.get(returnType, NO_ANNOTATIONS); + factory.get(returnType, NO_ANNOTATIONS, retrofit); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Call cannot use Response as its generic parameter. " @@ -65,17 +68,20 @@ public final class ExecutorCallAdapterFactoryTest { @Test public void responseType() { Type classType = new TypeToken>() {}.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>() {}.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>>() {}.getType(); - assertThat(factory.get(genericType, NO_ANNOTATIONS).responseType()) // + assertThat(factory.get(genericType, NO_ANNOTATIONS, retrofit).responseType()) .isEqualTo(new TypeToken>() {}.getType()); } @Test public void adaptedCallExecute() throws IOException { Type returnType = new TypeToken>() {}.getType(); - CallAdapter> adapter = (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS); + CallAdapter> adapter = + (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS, retrofit); final Response response = Response.success("Hi"); Call call = (Call) adapter.adapt(new EmptyCall() { @Override public Response execute() throws IOException { @@ -87,7 +93,8 @@ public final class ExecutorCallAdapterFactoryTest { @Test public void adaptedCallEnqueueUsesExecutorForSuccessCallback() { Type returnType = new TypeToken>() {}.getType(); - CallAdapter> adapter = (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS); + CallAdapter> adapter = + (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS, retrofit); final Response response = Response.success("Hi"); Call call = (Call) adapter.adapt(new EmptyCall() { @Override public void enqueue(Callback callback) { @@ -101,7 +108,8 @@ public final class ExecutorCallAdapterFactoryTest { @Test public void adaptedCallEnqueueUsesExecutorForFailureCallback() { Type returnType = new TypeToken>() {}.getType(); - CallAdapter> adapter = (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS); + CallAdapter> adapter = + (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS, retrofit); final Throwable throwable = new IOException(); Call call = (Call) adapter.adapt(new EmptyCall() { @Override public void enqueue(Callback callback) { @@ -117,7 +125,8 @@ public final class ExecutorCallAdapterFactoryTest { @Test public void adaptedCallCloneDeepCopy() { Type returnType = new TypeToken>() {}.getType(); - CallAdapter> adapter = (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS); + CallAdapter> adapter = + (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS, retrofit); Call delegate = mock(Call.class); Call call = (Call) adapter.adapt(delegate); Call cloned = call.clone(); @@ -128,7 +137,8 @@ public final class ExecutorCallAdapterFactoryTest { @Test public void adaptedCallCancel() { Type returnType = new TypeToken>() {}.getType(); - CallAdapter> adapter = (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS); + CallAdapter> adapter = + (CallAdapter>) factory.get(returnType, NO_ANNOTATIONS, retrofit); Call delegate = mock(Call.class); Call call = (Call) adapter.adapt(delegate); call.cancel(); diff --git a/retrofit/src/test/java/retrofit/RetrofitTest.java b/retrofit/src/test/java/retrofit/RetrofitTest.java index 4e2ba0631d..82de976ba6 100644 --- a/retrofit/src/test/java/retrofit/RetrofitTest.java +++ b/retrofit/src/test/java/retrofit/RetrofitTest.java @@ -34,6 +34,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -141,7 +142,8 @@ interface Annotated { final AtomicBoolean factoryCalled = new AtomicBoolean(); final AtomicBoolean adapterCalled = new AtomicBoolean(); class MyCallAdapterFactory implements CallAdapter.Factory { - @Override public CallAdapter get(final Type returnType, Annotation[] annotations) { + @Override public CallAdapter get(final Type returnType, Annotation[] annotations, + Retrofit retrofit) { factoryCalled.set(true); if (Utils.getRawType(returnType) != Call.class) { return null; @@ -171,7 +173,8 @@ class MyCallAdapterFactory implements CallAdapter.Factory { @Test public void customCallAdapter() { class GreetingCallAdapterFactory implements CallAdapter.Factory { - @Override public CallAdapter get(Type returnType, Annotation[] annotations) { + @Override public CallAdapter get(Type returnType, Annotation[] annotations, + Retrofit retrofit) { if (Utils.getRawType(returnType) != String.class) { return null; } @@ -199,7 +202,8 @@ class GreetingCallAdapterFactory implements CallAdapter.Factory { @Test public void methodAnnotationsPassedToCallAdapter() { final AtomicReference annotationsRef = new AtomicReference<>(); class MyCallAdapterFactory implements CallAdapter.Factory { - @Override public CallAdapter get(Type returnType, Annotation[] annotations) { + @Override public CallAdapter get(Type returnType, Annotation[] annotations, + Retrofit retrofit) { annotationsRef.set(annotations); return null; } @@ -560,6 +564,149 @@ public Converter toRequestBody(Type type, Annotation[] annotatio assertThat(retrofit.callAdapterFactories()).contains(factory); } + @Test public void callAdapterFactoryQueried() { + Type type = String.class; + Annotation[] annotations = new Annotation[0]; + + CallAdapter expectedAdapter = mock(CallAdapter.class); + CallAdapter.Factory factory = mock(CallAdapter.Factory.class); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://example.com/") + .addCallAdapterFactory(factory) + .build(); + + doReturn(expectedAdapter).when(factory).get(type, annotations, retrofit); + + CallAdapter actualAdapter = retrofit.callAdapter(type, annotations); + assertThat(actualAdapter).isSameAs(expectedAdapter); + + verify(factory).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory); + } + + @Test public void callAdapterFactoryQueriedCanDelegate() { + Type type = String.class; + Annotation[] annotations = new Annotation[0]; + + CallAdapter expectedAdapter = mock(CallAdapter.class); + CallAdapter.Factory factory2 = mock(CallAdapter.Factory.class); + CallAdapter.Factory factory1 = spy(new CallAdapter.Factory() { + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + return retrofit.nextCallAdapter(this, returnType, annotations); + } + }); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://example.com/") + .addCallAdapterFactory(factory1) + .addCallAdapterFactory(factory2) + .build(); + + doReturn(expectedAdapter).when(factory2).get(type, annotations, retrofit); + + CallAdapter actualAdapter = retrofit.callAdapter(type, annotations); + assertThat(actualAdapter).isSameAs(expectedAdapter); + + verify(factory1).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory1); + verify(factory2).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory2); + } + + @Test public void callAdapterFactoryQueriedCanDelegateTwiceWithoutRecursion() { + Type type = String.class; + Annotation[] annotations = new Annotation[0]; + + CallAdapter expectedAdapter = mock(CallAdapter.class); + CallAdapter.Factory factory3 = mock(CallAdapter.Factory.class); + CallAdapter.Factory factory2 = spy(new CallAdapter.Factory() { + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + return retrofit.nextCallAdapter(this, returnType, annotations); + } + }); + CallAdapter.Factory factory1 = spy(new CallAdapter.Factory() { + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + return retrofit.nextCallAdapter(this, returnType, annotations); + } + }); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://example.com/") + .addCallAdapterFactory(factory1) + .addCallAdapterFactory(factory2) + .addCallAdapterFactory(factory3) + .build(); + + doReturn(expectedAdapter).when(factory3).get(type, annotations, retrofit); + + CallAdapter actualAdapter = retrofit.callAdapter(type, annotations); + assertThat(actualAdapter).isSameAs(expectedAdapter); + + verify(factory1).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory1); + verify(factory2).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory2); + verify(factory3).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory3); + } + + @Test public void callAdapterFactoryNoMatchThrows() { + Type type = String.class; + Annotation[] annotations = new Annotation[0]; + + CallAdapter.Factory factory = mock(CallAdapter.Factory.class); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://example.com/") + .addCallAdapterFactory(factory) + .build(); + + doReturn(null).when(factory).get(type, annotations, retrofit); + + try { + retrofit.callAdapter(type, annotations); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageStartingWith( + "Could not locate call adapter for class java.lang.String. Tried:"); + } + + verify(factory).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory); + } + + @Test public void callAdapterFactoryDelegateNoMatchThrows() { + Type type = String.class; + Annotation[] annotations = new Annotation[0]; + + CallAdapter.Factory factory1 = spy(new CallAdapter.Factory() { + @Override + public CallAdapter get(Type returnType, Annotation[] annotations, Retrofit retrofit) { + return retrofit.nextCallAdapter(this, returnType, annotations); + } + }); + + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("http://example.com/") + .addCallAdapterFactory(factory1) + .build(); + + try { + retrofit.callAdapter(type, annotations); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessageContaining("Skipped:") + .hasMessageStartingWith( + "Could not locate call adapter for class java.lang.String. Tried:"); + } + + + verify(factory1).get(type, annotations, retrofit); + verifyNoMoreInteractions(factory1); + } + @Test public void callbackExecutorNullThrows() { try { new Retrofit.Builder().callbackExecutor(null); diff --git a/samples/src/main/java/com/example/retrofit/CustomCallAdapter.java b/samples/src/main/java/com/example/retrofit/CustomCallAdapter.java index eba5385fa5..a3a8ba28fa 100644 --- a/samples/src/main/java/com/example/retrofit/CustomCallAdapter.java +++ b/samples/src/main/java/com/example/retrofit/CustomCallAdapter.java @@ -36,8 +36,8 @@ */ public final class CustomCallAdapter { public static class ListenableFutureCallAdapterFactory implements CallAdapter.Factory { - @Override - public CallAdapter> get(Type returnType, Annotation[] annotations) { + @Override public CallAdapter> get(Type returnType, Annotation[] annotations, + Retrofit retrofit) { TypeToken token = TypeToken.of(returnType); if (token.getRawType() != ListenableFuture.class) { return null;