Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make dependency on java.sql optional #1707

Merged
merged 6 commits into from Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 8 additions & 5 deletions gson/src/main/java/com/google/gson/Gson.java
Expand Up @@ -49,9 +49,8 @@
import com.google.gson.internal.bind.MapTypeAdapterFactory;
import com.google.gson.internal.bind.ObjectTypeAdapter;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import com.google.gson.internal.bind.SqlDateTypeAdapter;
import com.google.gson.internal.bind.TimeTypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.internal.sql.SqlTypesSupport;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
Expand Down Expand Up @@ -262,9 +261,13 @@ public Gson() {
factories.add(TypeAdapters.BIT_SET_FACTORY);
factories.add(DateTypeAdapter.FACTORY);
factories.add(TypeAdapters.CALENDAR_FACTORY);
factories.add(TimeTypeAdapter.FACTORY);
factories.add(SqlDateTypeAdapter.FACTORY);
factories.add(TypeAdapters.TIMESTAMP_FACTORY);

if (SqlTypesSupport.SUPPORTS_SQL_TYPES) {
factories.add(SqlTypesSupport.TIME_FACTORY);
factories.add(SqlTypesSupport.DATE_FACTORY);
factories.add(SqlTypesSupport.TIMESTAMP_FACTORY);
}

factories.add(ArrayTypeAdapter.FACTORY);
factories.add(TypeAdapters.CLASS_FACTORY);

Expand Down
44 changes: 27 additions & 17 deletions gson/src/main/java/com/google/gson/GsonBuilder.java
Expand Up @@ -17,7 +17,6 @@
package com.google.gson;

import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -28,8 +27,10 @@

import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.DefaultDateTypeAdapter;
import com.google.gson.internal.bind.TreeTypeAdapter;
import com.google.gson.internal.bind.TypeAdapters;
import com.google.gson.internal.sql.SqlTypesSupport;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;

Expand Down Expand Up @@ -417,8 +418,8 @@ public GsonBuilder disableHtmlEscaping() {
* call this method or {@link #setDateFormat(int)} multiple times, but only the last invocation
* will be used to decide the serialization format.
*
* <p>The date format will be used to serialize and deserialize {@link java.util.Date}, {@link
* java.sql.Timestamp} and {@link java.sql.Date}.
* <p>The date format will be used to serialize and deserialize {@link java.util.Date} and in case
* the {@code java.sql} module is present, also {@link java.sql.Timestamp} and {@link java.sql.Date}.
*
* <p>Note that this pattern must abide by the convention provided by {@code SimpleDateFormat}
* class. See the documentation in {@link java.text.SimpleDateFormat} for more information on
Expand Down Expand Up @@ -602,26 +603,35 @@ public Gson create() {
this.factories, this.hierarchyFactories, factories);
}

@SuppressWarnings("unchecked")
private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
List<TypeAdapterFactory> factories) {
DefaultDateTypeAdapter dateTypeAdapter;
TypeAdapter<Timestamp> timestampTypeAdapter;
TypeAdapter<java.sql.Date> javaSqlDateTypeAdapter;
if (datePattern != null && !"".equals(datePattern.trim())) {
dateTypeAdapter = new DefaultDateTypeAdapter(Date.class, datePattern);
timestampTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(Timestamp.class, datePattern);
javaSqlDateTypeAdapter = (TypeAdapter) new DefaultDateTypeAdapter(java.sql.Date.class, datePattern);
TypeAdapterFactory dateAdapterFactory;
boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES;
TypeAdapterFactory sqlTimestampAdapterFactory = null;
TypeAdapterFactory sqlDateAdapterFactory = null;

if (datePattern != null && !datePattern.trim().isEmpty()) {
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(datePattern);

if (sqlTypesSupported) {
sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(datePattern);
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(datePattern);
}
} else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
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);
dateAdapterFactory = DefaultDateTypeAdapter.DateType.DATE.createAdapterFactory(dateStyle, timeStyle);

if (sqlTypesSupported) {
sqlTimestampAdapterFactory = SqlTypesSupport.TIMESTAMP_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
sqlDateAdapterFactory = SqlTypesSupport.DATE_DATE_TYPE.createAdapterFactory(dateStyle, timeStyle);
}
} else {
return;
}

factories.add(TypeAdapters.newFactory(Date.class, dateTypeAdapter));
factories.add(TypeAdapters.newFactory(Timestamp.class, timestampTypeAdapter));
factories.add(TypeAdapters.newFactory(java.sql.Date.class, javaSqlDateTypeAdapter));
factories.add(dateAdapterFactory);
if (sqlTypesSupported) {
factories.add(sqlTimestampAdapterFactory);
factories.add(sqlDateAdapterFactory);
}
}
}
Expand Up @@ -339,6 +339,7 @@ public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawTyp
}

public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {

return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable<?>, Type>());
}

Expand Down
Expand Up @@ -14,10 +14,9 @@
* limitations under the License.
*/

package com.google.gson;
package com.google.gson.internal.bind;

import java.io.IOException;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.ParsePosition;
Expand All @@ -27,6 +26,10 @@
import java.util.List;
import java.util.Locale;

import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Preconditions;
import com.google.gson.internal.JavaVersion;
import com.google.gson.internal.PreJava9DateFormatProvider;
import com.google.gson.internal.bind.util.ISO8601Utils;
Expand All @@ -35,45 +38,70 @@
import com.google.gson.stream.JsonWriter;

/**
* This type adapter supports three subclasses of date: Date, Timestamp, and
* java.sql.Date.
* This type adapter supports subclasses of date by defining a
* {@link DefaultDateTypeAdapter.DateType} and then using its {@code createAdapterFactory}
* methods.
*
* @author Inderjeet Singh
* @author Joel Leitch
*/
final class DefaultDateTypeAdapter extends TypeAdapter<Date> {

public final class DefaultDateTypeAdapter<T extends Date> extends TypeAdapter<T> {
private static final String SIMPLE_NAME = "DefaultDateTypeAdapter";

private final Class<? extends Date> dateType;
public static abstract class DateType<T extends Date> {
public static final DateType<Date> DATE = new DateType<Date>(Date.class) {
@Override protected Date deserialize(Date date) {
return date;
}
};

private final Class<T> dateClass;

protected DateType(Class<T> dateClass) {
this.dateClass = dateClass;
}

protected abstract T deserialize(Date date);

private final TypeAdapterFactory createFactory(DefaultDateTypeAdapter<T> adapter) {
return TypeAdapters.newFactory(dateClass, adapter);
}

public final TypeAdapterFactory createAdapterFactory(String datePattern) {
return createFactory(new DefaultDateTypeAdapter<T>(this, datePattern));
}

public final TypeAdapterFactory createAdapterFactory(int style) {
return createFactory(new DefaultDateTypeAdapter<T>(this, style));
}

public final TypeAdapterFactory createAdapterFactory(int dateStyle, int timeStyle) {
return createFactory(new DefaultDateTypeAdapter<T>(this, dateStyle, timeStyle));
}

public final TypeAdapterFactory createDefaultsAdapterFactory() {
return createFactory(new DefaultDateTypeAdapter<T>(this, DateFormat.DEFAULT, DateFormat.DEFAULT));
}
}

private final DateType<T> dateType;

/**
* List of 1 or more different date formats used for de-serialization attempts.
* The first of them is used for serialization as well.
*/
private final List<DateFormat> dateFormats = new ArrayList<DateFormat>();

DefaultDateTypeAdapter(Class<? extends Date> dateType) {
this.dateType = verifyDateType(dateType);
dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
}
if (JavaVersion.isJava9OrLater()) {
dateFormats.add(PreJava9DateFormatProvider.getUSDateTimeFormat(DateFormat.DEFAULT, DateFormat.DEFAULT));
}
}

DefaultDateTypeAdapter(Class<? extends Date> dateType, String datePattern) {
this.dateType = verifyDateType(dateType);
private DefaultDateTypeAdapter(DateType<T> dateType, String datePattern) {
this.dateType = $Gson$Preconditions.checkNotNull(dateType);
dateFormats.add(new SimpleDateFormat(datePattern, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(new SimpleDateFormat(datePattern));
}
}

DefaultDateTypeAdapter(Class<? extends Date> dateType, int style) {
this.dateType = verifyDateType(dateType);
private DefaultDateTypeAdapter(DateType<T> dateType, int style) {
this.dateType = $Gson$Preconditions.checkNotNull(dateType);
dateFormats.add(DateFormat.getDateInstance(style, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateInstance(style));
Expand All @@ -83,12 +111,8 @@ final class DefaultDateTypeAdapter extends TypeAdapter<Date> {
}
}

public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
this(Date.class, dateStyle, timeStyle);
}

public DefaultDateTypeAdapter(Class<? extends Date> dateType, int dateStyle, int timeStyle) {
this.dateType = verifyDateType(dateType);
private DefaultDateTypeAdapter(DateType<T> dateType, int dateStyle, int timeStyle) {
this.dateType = $Gson$Preconditions.checkNotNull(dateType);
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US));
if (!Locale.getDefault().equals(Locale.US)) {
dateFormats.add(DateFormat.getDateTimeInstance(dateStyle, timeStyle));
Expand All @@ -98,13 +122,6 @@ public DefaultDateTypeAdapter(Class<? extends Date> dateType, int dateStyle, int
}
}

private static Class<? extends Date> verifyDateType(Class<? extends Date> dateType) {
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);
}
return dateType;
}

// These methods need to be synchronized since JDK DateFormat classes are not thread-safe
// See issue 162
@Override
Expand All @@ -120,22 +137,13 @@ public void write(JsonWriter out, Date value) throws IOException {
}

@Override
public Date read(JsonReader in) throws IOException {
public T read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
in.nextNull();
return null;
}
Date date = deserializeToDate(in.nextString());
if (dateType == Date.class) {
return date;
} else if (dateType == Timestamp.class) {
return new Timestamp(date.getTime());
} else if (dateType == java.sql.Date.class) {
return new java.sql.Date(date.getTime());
} else {
// This must never happen: dateType is guarded in the primary constructor
throw new AssertionError();
}
return dateType.deserialize(date);
}

private Date deserializeToDate(String s) {
Expand All @@ -145,11 +153,12 @@ private Date deserializeToDate(String s) {
return dateFormat.parse(s);
} catch (ParseException ignored) {}
}
try {
return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) {
throw new JsonSyntaxException(s, e);
}
}

try {
return ISO8601Utils.parse(s, new ParsePosition(0));
} catch (ParseException e) {
throw new JsonSyntaxException(s, e);
}
}

Expand Down
27 changes: 2 additions & 25 deletions gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java
Expand Up @@ -26,12 +26,10 @@
import java.net.URL;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Calendar;
import java.util.Currency;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -409,7 +407,7 @@ public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
};

public static final TypeAdapter<BigDecimal> BIG_DECIMAL = new TypeAdapter<BigDecimal>() {
@Override public BigDecimal read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
Expand All @@ -427,7 +425,7 @@ public void write(JsonWriter out, String value) throws IOException {
out.value(value);
}
};

public static final TypeAdapter<BigInteger> BIG_INTEGER = new TypeAdapter<BigInteger>() {
@Override public BigInteger read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
Expand Down Expand Up @@ -572,27 +570,6 @@ public void write(JsonWriter out, Currency value) throws IOException {
}.nullSafe();
public static final TypeAdapterFactory CURRENCY_FACTORY = newFactory(Currency.class, CURRENCY);

public static final TypeAdapterFactory TIMESTAMP_FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (typeToken.getRawType() != Timestamp.class) {
return null;
}

final TypeAdapter<Date> dateTypeAdapter = gson.getAdapter(Date.class);
return (TypeAdapter<T>) new TypeAdapter<Timestamp>() {
@Override public Timestamp read(JsonReader in) throws IOException {
Date date = dateTypeAdapter.read(in);
return date != null ? new Timestamp(date.getTime()) : null;
}

@Override public void write(JsonWriter out, Timestamp value) throws IOException {
dateTypeAdapter.write(out, value);
}
};
}
};

public static final TypeAdapter<Calendar> CALENDAR = new TypeAdapter<Calendar>() {
private static final String YEAR = "year";
private static final String MONTH = "month";
Expand Down
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

package com.google.gson.internal.bind;
package com.google.gson.internal.sql;

import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
Expand All @@ -35,8 +35,8 @@
* this class state. DateFormat isn't thread safe either, so this class has
* to synchronize its read and write methods.
*/
public final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {
static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
@SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
return typeToken.getRawType() == java.sql.Date.class
Expand All @@ -46,6 +46,9 @@ public final class SqlDateTypeAdapter extends TypeAdapter<java.sql.Date> {

private final DateFormat format = new SimpleDateFormat("MMM d, yyyy");

private SqlDateTypeAdapter() {
}

@Override
public synchronized java.sql.Date read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.NULL) {
Expand Down