Skip to content

Commit fe6d0f9

Browse files
committed
Introduce factory for converter concept.
This is still highly-inefficient in its internal use and there are no tests proving that we now fail earlier in configuring a service method. Both of those will come in follow up commits
1 parent 2303424 commit fe6d0f9

30 files changed

+635
-327
lines changed

retrofit-adapters/rxjava/src/test/java/retrofit/ObservableCallAdapterFactoryTest.java

+11-7
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ interface Service {
4949
@Before public void setUp() {
5050
Retrofit retrofit = new Retrofit.Builder()
5151
.endpoint(server.getUrl("/").toString())
52-
.converter(new StringConverter())
52+
.converterFactory(new StringConverterFactory())
5353
.callAdapterFactory(ObservableCallAdapterFactory.create())
5454
.build();
5555
service = retrofit.create(Service.class);
@@ -195,13 +195,17 @@ interface Service {
195195
}
196196
}
197197

198-
static class StringConverter implements Converter {
199-
@Override public Object fromBody(ResponseBody body, Type type) throws IOException {
200-
return body.string();
201-
}
198+
static class StringConverterFactory implements Converter.Factory {
199+
@Override public Converter<?> get(Type type) {
200+
return new Converter<String>() {
201+
@Override public String fromBody(ResponseBody body) throws IOException {
202+
return body.string();
203+
}
202204

203-
@Override public RequestBody toBody(Object object, Type type) {
204-
return RequestBody.create(MediaType.parse("text/plain"), String.valueOf(object));
205+
@Override public RequestBody toBody(String value) {
206+
return RequestBody.create(MediaType.parse("text/plain"), value);
207+
}
208+
};
205209
}
206210
}
207211
}

retrofit-converters/gson/src/main/java/retrofit/GsonConverter.java

+25-48
Original file line numberDiff line numberDiff line change
@@ -15,71 +15,48 @@
1515
*/
1616
package retrofit;
1717

18-
import com.google.gson.Gson;
18+
import com.google.gson.TypeAdapter;
1919
import com.squareup.okhttp.MediaType;
2020
import com.squareup.okhttp.RequestBody;
2121
import com.squareup.okhttp.ResponseBody;
2222
import java.io.IOException;
23-
import java.io.InputStream;
24-
import java.io.InputStreamReader;
25-
import java.lang.reflect.Type;
23+
import java.io.OutputStreamWriter;
24+
import java.io.Reader;
25+
import java.io.Writer;
2626
import java.nio.charset.Charset;
27+
import okio.Buffer;
2728

28-
/**
29-
* A {@link Converter} which uses GSON for serialization and deserialization of entities.
30-
*/
31-
public class GsonConverter implements Converter {
32-
private final Gson gson;
33-
private final Charset charset;
34-
private final MediaType mediaType;
35-
36-
/**
37-
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
38-
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
39-
*/
40-
public GsonConverter() {
41-
this(new Gson());
42-
}
29+
final class GsonConverter<T> implements Converter<T> {
30+
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
31+
private static final Charset UTF_8 = Charset.forName("UTF-8");
4332

44-
/**
45-
* Create an instance using the supplied {@link Gson} object for conversion. Encoding to JSON and
46-
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
47-
*/
48-
public GsonConverter(Gson gson) {
49-
this(gson, Charset.forName("UTF-8"));
50-
}
33+
private final TypeAdapter<T> typeAdapter;
5134

52-
/**
53-
* Create an instance using the supplied {@link Gson} object for conversion. Encoding to JSON and
54-
* decoding from JSON (when no charset is specified by a header) will use the specified charset.
55-
*/
56-
public GsonConverter(Gson gson, Charset charset) {
57-
if (gson == null) throw new NullPointerException("gson == null");
58-
if (charset == null) throw new NullPointerException("charset == null");
59-
this.gson = gson;
60-
this.charset = charset;
61-
this.mediaType = MediaType.parse("application/json; charset=" + charset.name());
35+
GsonConverter(TypeAdapter<T> typeAdapter) {
36+
this.typeAdapter = typeAdapter;
6237
}
6338

64-
@Override public Object fromBody(ResponseBody body, Type type) throws IOException {
65-
Charset charset = this.charset;
66-
if (body.contentType() != null) {
67-
charset = body.contentType().charset(charset);
68-
}
69-
70-
InputStream is = body.byteStream();
39+
@Override public T fromBody(ResponseBody body) throws IOException {
40+
Reader in = body.charStream();
7141
try {
72-
return gson.fromJson(new InputStreamReader(is, charset), type);
42+
return typeAdapter.fromJson(in);
7343
} finally {
7444
try {
75-
is.close();
45+
in.close();
7646
} catch (IOException ignored) {
7747
}
7848
}
7949
}
8050

81-
@Override public RequestBody toBody(Object object, Type type) {
82-
String json = gson.toJson(object, type);
83-
return RequestBody.create(mediaType, json);
51+
@Override public RequestBody toBody(T value) {
52+
Buffer buffer = new Buffer();
53+
Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8);
54+
try {
55+
typeAdapter.toJson(writer, value);
56+
writer.flush();
57+
} catch (IOException e) {
58+
throw new AssertionError(e); // Writing to Buffer does no I/O.
59+
}
60+
return RequestBody.create(MEDIA_TYPE, buffer.readByteString());
8461
}
8562
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 retrofit;
17+
18+
import com.google.gson.Gson;
19+
import com.google.gson.TypeAdapter;
20+
import com.google.gson.reflect.TypeToken;
21+
import java.lang.reflect.Type;
22+
23+
/** A {@linkplain Converter.Factory converter} which uses Gson for JSON. */
24+
public final class GsonConverterFactory implements Converter.Factory {
25+
/**
26+
* Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and
27+
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
28+
*/
29+
public static GsonConverterFactory create() {
30+
return create(new Gson());
31+
}
32+
33+
/**
34+
* Create an instance using {@code gson} for conversion. Encoding to JSON and
35+
* decoding from JSON (when no charset is specified by a header) will use UTF-8.
36+
*/
37+
public static GsonConverterFactory create(Gson gson) {
38+
return new GsonConverterFactory(gson);
39+
}
40+
41+
private final Gson gson;
42+
43+
private GsonConverterFactory(Gson gson) {
44+
if (gson == null) throw new NullPointerException("gson == null");
45+
this.gson = gson;
46+
}
47+
48+
@Override public Converter<?> get(Type type) {
49+
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
50+
return new GsonConverter<>(adapter);
51+
}
52+
}

retrofit-converters/gson/src/test/java/retrofit/GsonConverterTest.java retrofit-converters/gson/src/test/java/retrofit/GsonConverterFactoryTest.java

+2-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333

3434
import static org.assertj.core.api.Assertions.assertThat;
3535

36-
public final class GsonConverterTest {
36+
public final class GsonConverterFactoryTest {
3737
interface AnInterface {
3838
String getName();
3939
}
@@ -87,10 +87,9 @@ interface Service {
8787
Gson gson = new GsonBuilder()
8888
.registerTypeAdapter(AnInterface.class, new AnInterfaceAdapter())
8989
.create();
90-
Converter converter = new GsonConverter(gson);
9190
Retrofit retrofit = new Retrofit.Builder()
9291
.endpoint(server.getUrl("/").toString())
93-
.converter(converter)
92+
.converterFactory(GsonConverterFactory.create(gson))
9493
.build();
9594
service = retrofit.create(Service.class);
9695
}

retrofit-converters/jackson/src/main/java/retrofit/JacksonConverter.java

+28-23
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,45 @@
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+
*/
116
package retrofit;
217

318
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import com.fasterxml.jackson.databind.JavaType;
5-
import com.fasterxml.jackson.databind.ObjectMapper;
19+
import com.fasterxml.jackson.databind.ObjectReader;
20+
import com.fasterxml.jackson.databind.ObjectWriter;
621
import com.squareup.okhttp.MediaType;
722
import com.squareup.okhttp.RequestBody;
823
import com.squareup.okhttp.ResponseBody;
924
import java.io.IOException;
1025
import java.io.InputStream;
11-
import java.lang.reflect.Type;
1226

13-
/**
14-
* A {@link Converter} which uses Jackson for reading and writing entities.
15-
*
16-
* @author Kai Waldron ([email protected])
17-
*/
18-
public class JacksonConverter implements Converter {
27+
final class JacksonConverter<T> implements Converter<T> {
1928
private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");
2029

21-
private final ObjectMapper objectMapper;
30+
private final ObjectWriter writer;
31+
private final ObjectReader reader;
2232

23-
public JacksonConverter() {
24-
this(new ObjectMapper());
33+
JacksonConverter(ObjectWriter writer, ObjectReader reader) {
34+
this.writer = writer;
35+
this.reader = reader;
2536
}
2637

27-
public JacksonConverter(ObjectMapper objectMapper) {
28-
if (objectMapper == null) throw new NullPointerException("objectMapper == null");
29-
this.objectMapper = objectMapper;
30-
}
3138

32-
@Override public Object fromBody(ResponseBody body, Type type) throws IOException {
39+
@Override public T fromBody(ResponseBody body) throws IOException {
3340
InputStream is = body.byteStream();
3441
try {
35-
JavaType javaType = objectMapper.getTypeFactory().constructType(type);
36-
return objectMapper.readValue(is, javaType);
42+
return reader.readValue(is);
3743
} finally {
3844
try {
3945
is.close();
@@ -42,11 +48,10 @@ public JacksonConverter(ObjectMapper objectMapper) {
4248
}
4349
}
4450

45-
@Override public RequestBody toBody(Object object, Type type) {
51+
@Override public RequestBody toBody(T value) {
4652
try {
47-
JavaType javaType = objectMapper.getTypeFactory().constructType(type);
48-
String json = objectMapper.writerWithType(javaType).writeValueAsString(object);
49-
return RequestBody.create(MEDIA_TYPE, json);
53+
byte[] bytes = writer.writeValueAsBytes(value);
54+
return RequestBody.create(MEDIA_TYPE, bytes);
5055
} catch (JsonProcessingException e) {
5156
throw new RuntimeException(e);
5257
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 retrofit;
17+
18+
import com.fasterxml.jackson.databind.JavaType;
19+
import com.fasterxml.jackson.databind.ObjectMapper;
20+
import com.fasterxml.jackson.databind.ObjectReader;
21+
import com.fasterxml.jackson.databind.ObjectWriter;
22+
import java.lang.reflect.Type;
23+
24+
/** A {@linkplain Converter.Factory converter} which uses Jackson. */
25+
public final class JacksonConverterFactory implements Converter.Factory {
26+
/** Create an instance using a default {@link ObjectMapper} instance for conversion. */
27+
public static JacksonConverterFactory create() {
28+
return create(new ObjectMapper());
29+
}
30+
31+
/** Create an instance using {@code mapper} for conversion. */
32+
public static JacksonConverterFactory create(ObjectMapper mapper) {
33+
return new JacksonConverterFactory(mapper);
34+
}
35+
36+
private final ObjectMapper mapper;
37+
38+
private JacksonConverterFactory(ObjectMapper mapper) {
39+
if (mapper == null) throw new NullPointerException("mapper == null");
40+
this.mapper = mapper;
41+
}
42+
43+
@Override public Converter<?> get(Type type) {
44+
JavaType javaType = mapper.getTypeFactory().constructType(type);
45+
ObjectWriter writer = mapper.writerWithType(javaType);
46+
ObjectReader reader = mapper.reader(javaType);
47+
return new JacksonConverter<>(writer, reader);
48+
}
49+
}

retrofit-converters/jackson/src/test/java/retrofit/JacksonConverterTest.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,9 @@ interface Service {
119119
.getDefaultVisibilityChecker()
120120
.withFieldVisibility(JsonAutoDetect.Visibility.ANY));
121121

122-
Converter converter = new JacksonConverter(mapper);
123122
Retrofit retrofit = new Retrofit.Builder()
124123
.endpoint(server.getUrl("/").toString())
125-
.converter(converter)
124+
.converterFactory(JacksonConverterFactory.create(mapper))
126125
.build();
127126
service = retrofit.create(Service.class);
128127
}

0 commit comments

Comments
 (0)