Migrate DefaultDateTypeAdapter to streaming adapter (#1070)

This commit is contained in:
Lyubomyr Shaydariv 2017-05-31 04:12:50 +03:00 committed by inder123
parent a300148003
commit b8f616c939
3 changed files with 89 additions and 64 deletions

View File

@ -16,7 +16,7 @@
package com.google.gson; package com.google.gson;
import java.lang.reflect.Type; import java.io.IOException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.ParseException; import java.text.ParseException;
@ -26,6 +26,9 @@ import java.util.Date;
import java.util.Locale; import java.util.Locale;
import com.google.gson.internal.bind.util.ISO8601Utils; import com.google.gson.internal.bind.util.ISO8601Utils;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
/** /**
* This type adapter supports three subclasses of date: Date, Timestamp, and * This type adapter supports three subclasses of date: Date, Timestamp, and
@ -34,34 +37,45 @@ import com.google.gson.internal.bind.util.ISO8601Utils;
* @author Inderjeet Singh * @author Inderjeet Singh
* @author Joel Leitch * @author Joel Leitch
*/ */
final class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> { final class DefaultDateTypeAdapter extends TypeAdapter<Date> {
// TODO: migrate to streaming adapter
private static final String SIMPLE_NAME = "DefaultDateTypeAdapter"; private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";
private final Class<? extends Date> dateType;
private final DateFormat enUsFormat; private final DateFormat enUsFormat;
private final DateFormat localFormat; private final DateFormat localFormat;
DefaultDateTypeAdapter() { DefaultDateTypeAdapter(Class<? extends Date> dateType) {
this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US), this(dateType,
DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US),
DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
} }
DefaultDateTypeAdapter(String datePattern) { DefaultDateTypeAdapter(Class<? extends Date> dateType, String datePattern) {
this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern)); this(dateType, new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern));
} }
DefaultDateTypeAdapter(int style) { DefaultDateTypeAdapter(Class<? extends Date> dateType, int style) {
this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style)); this(dateType, DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style));
} }
public DefaultDateTypeAdapter(int dateStyle, int timeStyle) { public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US), this(Date.class,
DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
DateFormat.getDateTimeInstance(dateStyle, timeStyle)); DateFormat.getDateTimeInstance(dateStyle, timeStyle));
} }
DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) { public DefaultDateTypeAdapter(Class<? extends Date> dateType, int dateStyle, int timeStyle) {
this(dateType,
DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
DateFormat.getDateTimeInstance(dateStyle, timeStyle));
}
DefaultDateTypeAdapter(final Class<? extends Date> dateType, DateFormat enUsFormat, DateFormat localFormat) {
if ( dateType != Date.class && dateType != java.sql.Date.class && dateType != Timestamp.class ) {
throw new IllegalArgumentException("Date type must be one of " + Date.class + ", " + Timestamp.class + ", or " + java.sql.Date.class + " but was " + dateType);
}
this.dateType = dateType;
this.enUsFormat = enUsFormat; this.enUsFormat = enUsFormat;
this.localFormat = localFormat; this.localFormat = localFormat;
} }
@ -69,43 +83,43 @@ final class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserial
// These methods need to be synchronized since JDK DateFormat classes are not thread-safe // These methods need to be synchronized since JDK DateFormat classes are not thread-safe
// See issue 162 // See issue 162
@Override @Override
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { public void write(JsonWriter out, Date value) throws IOException {
synchronized (localFormat) { synchronized (localFormat) {
String dateFormatAsString = enUsFormat.format(src); String dateFormatAsString = enUsFormat.format(value);
return new JsonPrimitive(dateFormatAsString); out.value(dateFormatAsString);
} }
} }
@Override @Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) public Date read(JsonReader in) throws IOException {
throws JsonParseException { if (in.peek() != JsonToken.STRING) {
if (!(json instanceof JsonPrimitive)) {
throw new JsonParseException("The date should be a string value"); throw new JsonParseException("The date should be a string value");
} }
Date date = deserializeToDate(json); Date date = deserializeToDate(in.nextString());
if (typeOfT == Date.class) { if (dateType == Date.class) {
return date; return date;
} else if (typeOfT == Timestamp.class) { } else if (dateType == Timestamp.class) {
return new Timestamp(date.getTime()); return new Timestamp(date.getTime());
} else if (typeOfT == java.sql.Date.class) { } else if (dateType == java.sql.Date.class) {
return new java.sql.Date(date.getTime()); return new java.sql.Date(date.getTime());
} else { } else {
throw new IllegalArgumentException(getClass() + " cannot deserialize to " + typeOfT); // This must never happen: dateType is guarded in the primary constructor
throw new AssertionError();
} }
} }
private Date deserializeToDate(JsonElement json) { private Date deserializeToDate(String s) {
synchronized (localFormat) { synchronized (localFormat) {
try { try {
return localFormat.parse(json.getAsString()); return localFormat.parse(s);
} catch (ParseException ignored) {} } catch (ParseException ignored) {}
try { try {
return enUsFormat.parse(json.getAsString()); return enUsFormat.parse(s);
} catch (ParseException ignored) {} } catch (ParseException ignored) {}
try { try {
return ISO8601Utils.parse(json.getAsString(), new ParsePosition(0)); return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) { } catch (ParseException e) {
throw new JsonSyntaxException(json.getAsString(), e); throw new JsonSyntaxException(s, e);
} }
} }
} }

View File

@ -572,19 +572,26 @@ public final class GsonBuilder {
serializeSpecialFloatingPointValues, longSerializationPolicy, factories); serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
} }
@SuppressWarnings("unchecked")
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle, private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
List<TypeAdapterFactory> factories) { List<TypeAdapterFactory> factories) {
DefaultDateTypeAdapter dateTypeAdapter; DefaultDateTypeAdapter dateTypeAdapter;
TypeAdapter<Timestamp> timestampTypeAdapter;
TypeAdapter<java.sql.Date> javaSqlDateTypeAdapter;
if (datePattern != null && !"".equals(datePattern.trim())) { if (datePattern != null && !"".equals(datePattern.trim())) {
dateTypeAdapter = new DefaultDateTypeAdapter(datePattern); dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, datePattern);
timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, datePattern);
javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, datePattern);
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) { } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle); dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle, timeStyle);
timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, dateStyle, timeStyle);
javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, dateStyle, timeStyle);
} else { } else {
return; return;
} }
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Date.class), dateTypeAdapter)); factories.add(TypeAdapters.newFactory(Date.class, dateTypeAdapter));
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Timestamp.class), dateTypeAdapter)); factories.add(TypeAdapters.newFactory(Timestamp.class, timestampTypeAdapter));
factories.add(TreeTypeAdapter.newFactory(TypeToken.get(java.sql.Date.class), dateTypeAdapter)); factories.add(TypeAdapters.newFactory(java.sql.Date.class, javaSqlDateTypeAdapter));
} }
} }

View File

@ -16,6 +16,7 @@
package com.google.gson; package com.google.gson;
import java.io.IOException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
@ -44,10 +45,10 @@ public class DefaultDateTypeAdapterTest extends TestCase {
Locale defaultLocale = Locale.getDefault(); Locale defaultLocale = Locale.getDefault();
Locale.setDefault(locale); Locale.setDefault(locale);
try { try {
assertFormatted("Jan 1, 1970 12:00:00 AM", new DefaultDateTypeAdapter()); assertFormatted("Jan 1, 1970 12:00:00 AM", new DefaultDateTypeAdapter(Date.class));
assertFormatted("1/1/70", new DefaultDateTypeAdapter(DateFormat.SHORT)); assertFormatted("1/1/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT));
assertFormatted("Jan 1, 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM)); assertFormatted("Jan 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM));
assertFormatted("January 1, 1970", new DefaultDateTypeAdapter(DateFormat.LONG)); assertFormatted("January 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG));
assertFormatted("1/1/70 12:00 AM", assertFormatted("1/1/70 12:00 AM",
new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
assertFormatted("Jan 1, 1970 12:00:00 AM", assertFormatted("Jan 1, 1970 12:00:00 AM",
@ -62,16 +63,16 @@ public class DefaultDateTypeAdapterTest extends TestCase {
} }
} }
public void testParsingDatesFormattedWithSystemLocale() { public void testParsingDatesFormattedWithSystemLocale() throws Exception {
TimeZone defaultTimeZone = TimeZone.getDefault(); TimeZone defaultTimeZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("UTC")); TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
Locale defaultLocale = Locale.getDefault(); Locale defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.FRANCE); Locale.setDefault(Locale.FRANCE);
try { try {
assertParsed("1 janv. 1970 00:00:00", new DefaultDateTypeAdapter()); assertParsed("1 janv. 1970 00:00:00", new DefaultDateTypeAdapter(Date.class));
assertParsed("01/01/70", new DefaultDateTypeAdapter(DateFormat.SHORT)); assertParsed("01/01/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT));
assertParsed("1 janv. 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM)); assertParsed("1 janv. 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM));
assertParsed("1 janvier 1970", new DefaultDateTypeAdapter(DateFormat.LONG)); assertParsed("1 janvier 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG));
assertParsed("01/01/70 00:00", assertParsed("01/01/70 00:00",
new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
assertParsed("1 janv. 1970 00:00:00", assertParsed("1 janv. 1970 00:00:00",
@ -86,16 +87,16 @@ public class DefaultDateTypeAdapterTest extends TestCase {
} }
} }
public void testParsingDatesFormattedWithUsLocale() { public void testParsingDatesFormattedWithUsLocale() throws Exception {
TimeZone defaultTimeZone = TimeZone.getDefault(); TimeZone defaultTimeZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("UTC")); TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
Locale defaultLocale = Locale.getDefault(); Locale defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
try { try {
assertParsed("Jan 1, 1970 0:00:00 AM", new DefaultDateTypeAdapter()); assertParsed("Jan 1, 1970 0:00:00 AM", new DefaultDateTypeAdapter(Date.class));
assertParsed("1/1/70", new DefaultDateTypeAdapter(DateFormat.SHORT)); assertParsed("1/1/70", new DefaultDateTypeAdapter(Date.class, DateFormat.SHORT));
assertParsed("Jan 1, 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM)); assertParsed("Jan 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.MEDIUM));
assertParsed("January 1, 1970", new DefaultDateTypeAdapter(DateFormat.LONG)); assertParsed("January 1, 1970", new DefaultDateTypeAdapter(Date.class, DateFormat.LONG));
assertParsed("1/1/70 0:00 AM", assertParsed("1/1/70 0:00 AM",
new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT)); new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
assertParsed("Jan 1, 1970 0:00:00 AM", assertParsed("Jan 1, 1970 0:00:00 AM",
@ -110,14 +111,14 @@ public class DefaultDateTypeAdapterTest extends TestCase {
} }
} }
public void testFormatUsesDefaultTimezone() { public void testFormatUsesDefaultTimezone() throws Exception {
TimeZone defaultTimeZone = TimeZone.getDefault(); TimeZone defaultTimeZone = TimeZone.getDefault();
TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles")); TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
Locale defaultLocale = Locale.getDefault(); Locale defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.US); Locale.setDefault(Locale.US);
try { try {
assertFormatted("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter()); assertFormatted("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter(Date.class));
assertParsed("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter()); assertParsed("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter(Date.class));
} finally { } finally {
TimeZone.setDefault(defaultTimeZone); TimeZone.setDefault(defaultTimeZone);
Locale.setDefault(defaultLocale); Locale.setDefault(defaultLocale);
@ -125,7 +126,7 @@ public class DefaultDateTypeAdapterTest extends TestCase {
} }
public void testDateDeserializationISO8601() throws Exception { public void testDateDeserializationISO8601() throws Exception {
DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(); DefaultDateTypeAdapter adapter = new DefaultDateTypeAdapter(Date.class);
assertParsed("1970-01-01T00:00:00.000Z", adapter); assertParsed("1970-01-01T00:00:00.000Z", adapter);
assertParsed("1970-01-01T00:00Z", adapter); assertParsed("1970-01-01T00:00Z", adapter);
assertParsed("1970-01-01T00:00:00+00:00", adapter); assertParsed("1970-01-01T00:00:00+00:00", adapter);
@ -135,38 +136,41 @@ public class DefaultDateTypeAdapterTest extends TestCase {
public void testDateSerialization() throws Exception { public void testDateSerialization() throws Exception {
int dateStyle = DateFormat.LONG; int dateStyle = DateFormat.LONG;
DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle); DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, dateStyle);
DateFormat formatter = DateFormat.getDateInstance(dateStyle, Locale.US); DateFormat formatter = DateFormat.getDateInstance(dateStyle, Locale.US);
Date currentDate = new Date(); Date currentDate = new Date();
String dateString = dateTypeAdapter.serialize(currentDate, Date.class, null).getAsString(); String dateString = dateTypeAdapter.toJson(currentDate);
assertEquals(formatter.format(currentDate), dateString); assertEquals(toLiteral(formatter.format(currentDate)), dateString);
} }
public void testDatePattern() throws Exception { public void testDatePattern() throws Exception {
String pattern = "yyyy-MM-dd"; String pattern = "yyyy-MM-dd";
DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(pattern); DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, pattern);
DateFormat formatter = new SimpleDateFormat(pattern); DateFormat formatter = new SimpleDateFormat(pattern);
Date currentDate = new Date(); Date currentDate = new Date();
String dateString = dateTypeAdapter.serialize(currentDate, Date.class, null).getAsString(); String dateString = dateTypeAdapter.toJson(currentDate);
assertEquals(formatter.format(currentDate), dateString); assertEquals(toLiteral(formatter.format(currentDate)), dateString);
} }
public void testInvalidDatePattern() throws Exception { public void testInvalidDatePattern() throws Exception {
try { try {
new DefaultDateTypeAdapter("I am a bad Date pattern...."); new DefaultDateTypeAdapter(Date.class, "I am a bad Date pattern....");
fail("Invalid date pattern should fail."); fail("Invalid date pattern should fail.");
} catch (IllegalArgumentException expected) { } } catch (IllegalArgumentException expected) { }
} }
private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) { private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) {
assertEquals(formatted, adapter.serialize(new Date(0), Date.class, null).getAsString()); assertEquals(toLiteral(formatted), adapter.toJson(new Date(0)));
} }
private void assertParsed(String date, DefaultDateTypeAdapter adapter) { private void assertParsed(String date, DefaultDateTypeAdapter adapter) throws IOException {
assertEquals(date, new Date(0), adapter.deserialize(new JsonPrimitive(date), Date.class, null)); assertEquals(date, new Date(0), adapter.fromJson(toLiteral(date)));
assertEquals("ISO 8601", new Date(0), adapter.deserialize( assertEquals("ISO 8601", new Date(0), adapter.fromJson(toLiteral("1970-01-01T00:00:00Z")));
new JsonPrimitive("1970-01-01T00:00:00Z"), Date.class, null)); }
private static String toLiteral(String s) {
return '"' + s + '"';
} }
} }