From 4f93cd8c6840c2a11ae9a25203d3b7ef178e8b39 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Fri, 16 Nov 2018 15:13:38 -0500 Subject: [PATCH] Enable built-in Optional converter on Android API 24+ --- pom.xml | 2 +- .../retrofit2/OptionalConverterFactory.java | 6 +- .../src/main/java/retrofit2/Platform.java | 11 +++ .../OptionalConverterFactoryAndroidTest.java | 82 +++++++++++++++++++ .../OptionalConverterFactoryTest.java | 25 +----- .../retrofit2/RequestFactoryAndroidTest.java | 3 + .../ObjectInstanceConverterFactory.java | 39 +++++++++ 7 files changed, 141 insertions(+), 27 deletions(-) create mode 100644 retrofit/src/test/java/retrofit2/OptionalConverterFactoryAndroidTest.java create mode 100644 retrofit/src/test/java/retrofit2/helpers/ObjectInstanceConverterFactory.java diff --git a/pom.xml b/pom.xml index 9bf69434cb..1dd4b32aad 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ 4.12 1.7.0 1.9.5 - 3.0 + 3.2 diff --git a/retrofit/src/main/java/retrofit2/OptionalConverterFactory.java b/retrofit/src/main/java/retrofit2/OptionalConverterFactory.java index 7174e3ad8f..a9325322c3 100644 --- a/retrofit/src/main/java/retrofit2/OptionalConverterFactory.java +++ b/retrofit/src/main/java/retrofit2/OptionalConverterFactory.java @@ -24,11 +24,7 @@ import okhttp3.ResponseBody; import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement; -/** - * A {@linkplain Converter.Factory converter} for {@code Optional} which delegates to another - * converter to deserialize {@code T} and then wraps it into {@link Optional}. - */ -@IgnoreJRERequirement +@IgnoreJRERequirement // Only added when Optional is available (Java 8+ / Android API 24+). final class OptionalConverterFactory extends Converter.Factory { static final Converter.Factory INSTANCE = new OptionalConverterFactory(); diff --git a/retrofit/src/main/java/retrofit2/Platform.java b/retrofit/src/main/java/retrofit2/Platform.java index 270a8040ae..dcaf317e91 100644 --- a/retrofit/src/main/java/retrofit2/Platform.java +++ b/retrofit/src/main/java/retrofit2/Platform.java @@ -22,6 +22,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import javax.annotation.Nullable; @@ -149,6 +150,16 @@ static class Android extends Platform { return singletonList(new ExecutorCallAdapterFactory(callbackExecutor)); } + @Override List defaultConverterFactories() { + return Build.VERSION.SDK_INT >= 24 + ? singletonList(OptionalConverterFactory.INSTANCE) + : Collections.emptyList(); + } + + @Override int defaultConverterFactoriesSize() { + return Build.VERSION.SDK_INT >= 24 ? 1 : 0; + } + static class MainThreadExecutor implements Executor { private final Handler handler = new Handler(Looper.getMainLooper()); diff --git a/retrofit/src/test/java/retrofit2/OptionalConverterFactoryAndroidTest.java b/retrofit/src/test/java/retrofit2/OptionalConverterFactoryAndroidTest.java new file mode 100644 index 0000000000..e64dccc124 --- /dev/null +++ b/retrofit/src/test/java/retrofit2/OptionalConverterFactoryAndroidTest.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2017 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 retrofit2; + +import java.io.IOException; +import java.util.Optional; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import retrofit2.helpers.ObjectInstanceConverterFactory; +import retrofit2.http.GET; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; +import static org.robolectric.annotation.Config.NEWEST_SDK; + +@RunWith(RobolectricTestRunner.class) +@Config(sdk = NEWEST_SDK) +public final class OptionalConverterFactoryAndroidTest { + interface Service { + @GET("/") Call> optional(); + @GET("/") Call object(); + } + + @Rule public final MockWebServer server = new MockWebServer(); + + private Service service; + + @Before public void setUp() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(server.url("/")) + .addConverterFactory(new ObjectInstanceConverterFactory()) + .build(); + service = retrofit.create(Service.class); + } + + @Config(sdk = 24) + @Test public void optionalApi24() throws IOException { + server.enqueue(new MockResponse()); + + Optional optional = service.optional().execute().body(); + assertThat(optional).isNotNull(); + assertThat(optional.get()).isSameAs(ObjectInstanceConverterFactory.VALUE); + } + + @Config(sdk = 21) + @Test public void optionalPreApi24() { + try { + service.optional(); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage( + "Unable to create converter for java.util.Optional\n" + + " for method Service.optional"); + } + } + + @Test public void onlyMatchesOptional() throws IOException { + server.enqueue(new MockResponse()); + + Object body = service.object().execute().body(); + assertThat(body).isSameAs(ObjectInstanceConverterFactory.VALUE); + } +} diff --git a/retrofit/src/test/java/retrofit2/OptionalConverterFactoryTest.java b/retrofit/src/test/java/retrofit2/OptionalConverterFactoryTest.java index 2bd4d94513..e7925f7a61 100644 --- a/retrofit/src/test/java/retrofit2/OptionalConverterFactoryTest.java +++ b/retrofit/src/test/java/retrofit2/OptionalConverterFactoryTest.java @@ -16,16 +16,13 @@ package retrofit2; import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.reflect.Type; import java.util.Optional; -import javax.annotation.Nullable; -import okhttp3.ResponseBody; import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockWebServer; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import retrofit2.helpers.ObjectInstanceConverterFactory; import retrofit2.http.GET; import static org.assertj.core.api.Assertions.assertThat; @@ -43,7 +40,7 @@ interface Service { @Before public void setUp() { Retrofit retrofit = new Retrofit.Builder() .baseUrl(server.url("/")) - .addConverterFactory(new ObjectToNullConverterFactory()) + .addConverterFactory(new ObjectInstanceConverterFactory()) .build(); service = retrofit.create(Service.class); } @@ -53,27 +50,13 @@ interface Service { Optional optional = service.optional().execute().body(); assertThat(optional).isNotNull(); - assertThat(optional.isPresent()).isFalse(); + assertThat(optional.get()).isSameAs(ObjectInstanceConverterFactory.VALUE); } @Test public void onlyMatchesOptional() throws IOException { server.enqueue(new MockResponse()); Object body = service.object().execute().body(); - assertThat(body).isNull(); - } - - static final class ObjectToNullConverterFactory extends Converter.Factory { - @Override public @Nullable Converter responseBodyConverter( - Type type, Annotation[] annotations, Retrofit retrofit) { - if (type != Object.class) { - return null; - } - return new Converter() { - @Override public Object convert(ResponseBody value) { - return null; - } - }; - } + assertThat(body).isSameAs(ObjectInstanceConverterFactory.VALUE); } } diff --git a/retrofit/src/test/java/retrofit2/RequestFactoryAndroidTest.java b/retrofit/src/test/java/retrofit2/RequestFactoryAndroidTest.java index 37c4705295..b50e46753c 100644 --- a/retrofit/src/test/java/retrofit2/RequestFactoryAndroidTest.java +++ b/retrofit/src/test/java/retrofit2/RequestFactoryAndroidTest.java @@ -21,13 +21,16 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import retrofit2.http.GET; import retrofit2.http.Url; import static org.assertj.core.api.Assertions.assertThat; +import static org.robolectric.annotation.Config.NEWEST_SDK; import static retrofit2.RequestFactoryTest.buildRequest; @RunWith(RobolectricTestRunner.class) +@Config(sdk = NEWEST_SDK) @SuppressWarnings({"UnusedParameters", "unused"}) // Parameters inspected reflectively. public final class RequestFactoryAndroidTest { @Test public void getWithAndroidUriUrl() { diff --git a/retrofit/src/test/java/retrofit2/helpers/ObjectInstanceConverterFactory.java b/retrofit/src/test/java/retrofit2/helpers/ObjectInstanceConverterFactory.java new file mode 100644 index 0000000000..bf3134696e --- /dev/null +++ b/retrofit/src/test/java/retrofit2/helpers/ObjectInstanceConverterFactory.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 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 retrofit2.helpers; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import javax.annotation.Nullable; +import okhttp3.ResponseBody; +import retrofit2.Converter; +import retrofit2.Retrofit; + +public final class ObjectInstanceConverterFactory extends Converter.Factory { + public static final Object VALUE = new Object(); + + @Override public @Nullable Converter responseBodyConverter( + Type type, Annotation[] annotations, Retrofit retrofit) { + if (type != Object.class) { + return null; + } + return new Converter() { + @Override public Object convert(ResponseBody value) { + return VALUE; + } + }; + } +}