diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java index ef8c81e378..a06f290747 100644 --- a/gson/src/main/java/com/google/gson/Gson.java +++ b/gson/src/main/java/com/google/gson/Gson.java @@ -16,26 +16,18 @@ package com.google.gson; +import static com.google.gson.GsonBuilder.newImmutableList; + import com.google.gson.annotations.JsonAdapter; import com.google.gson.internal.ConstructorConstructor; import com.google.gson.internal.Excluder; import com.google.gson.internal.GsonBuildConfig; -import com.google.gson.internal.LazilyParsedNumber; import com.google.gson.internal.Primitives; import com.google.gson.internal.Streams; -import com.google.gson.internal.bind.ArrayTypeAdapter; -import com.google.gson.internal.bind.CollectionTypeAdapterFactory; -import com.google.gson.internal.bind.DefaultDateTypeAdapter; import com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory; import com.google.gson.internal.bind.JsonTreeReader; import com.google.gson.internal.bind.JsonTreeWriter; -import com.google.gson.internal.bind.MapTypeAdapterFactory; -import com.google.gson.internal.bind.NumberTypeAdapter; -import com.google.gson.internal.bind.ObjectTypeAdapter; -import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.internal.bind.SerializationDelegatingTypeAdapter; -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; @@ -47,19 +39,12 @@ import java.io.StringReader; import java.io.Writer; import java.lang.reflect.Type; -import java.math.BigDecimal; -import java.math.BigInteger; -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicLongArray; /** * This is the main class for using Gson. Gson is typically used by first constructing a Gson @@ -149,21 +134,6 @@ */ public final class Gson { - static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; - // Strictness of `null` is the legacy mode where some Gson APIs are always lenient - static final Strictness DEFAULT_STRICTNESS = null; - static final FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT; - static final boolean DEFAULT_ESCAPE_HTML = true; - static final boolean DEFAULT_SERIALIZE_NULLS = false; - static final boolean DEFAULT_COMPLEX_MAP_KEYS = false; - static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false; - static final boolean DEFAULT_USE_JDK_UNSAFE = true; - static final String DEFAULT_DATE_PATTERN = null; - static final FieldNamingStrategy DEFAULT_FIELD_NAMING_STRATEGY = FieldNamingPolicy.IDENTITY; - static final ToNumberStrategy DEFAULT_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE; - static final ToNumberStrategy DEFAULT_NUMBER_TO_NUMBER_STRATEGY = - ToNumberPolicy.LAZILY_PARSED_NUMBER; - private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; /** @@ -250,151 +220,40 @@ public final class Gson { * */ public Gson() { - this( - Excluder.DEFAULT, - DEFAULT_FIELD_NAMING_STRATEGY, - Collections.emptyMap(), - DEFAULT_SERIALIZE_NULLS, - DEFAULT_COMPLEX_MAP_KEYS, - DEFAULT_JSON_NON_EXECUTABLE, - DEFAULT_ESCAPE_HTML, - DEFAULT_FORMATTING_STYLE, - DEFAULT_STRICTNESS, - DEFAULT_SPECIALIZE_FLOAT_VALUES, - DEFAULT_USE_JDK_UNSAFE, - LongSerializationPolicy.DEFAULT, - DEFAULT_DATE_PATTERN, - DateFormat.DEFAULT, - DateFormat.DEFAULT, - Collections.emptyList(), - Collections.emptyList(), - Collections.emptyList(), - DEFAULT_OBJECT_TO_NUMBER_STRATEGY, - DEFAULT_NUMBER_TO_NUMBER_STRATEGY, - Collections.emptyList()); + this(GsonBuilder.DEFAULT); } - Gson( - Excluder excluder, - FieldNamingStrategy fieldNamingStrategy, - Map> instanceCreators, - boolean serializeNulls, - boolean complexMapKeySerialization, - boolean generateNonExecutableGson, - boolean htmlSafe, - FormattingStyle formattingStyle, - Strictness strictness, - boolean serializeSpecialFloatingPointValues, - boolean useJdkUnsafe, - LongSerializationPolicy longSerializationPolicy, - String datePattern, - int dateStyle, - int timeStyle, - List builderFactories, - List builderHierarchyFactories, - List factoriesToBeAdded, - ToNumberStrategy objectToNumberStrategy, - ToNumberStrategy numberToNumberStrategy, - List reflectionFilters) { - this.excluder = excluder; - this.fieldNamingStrategy = fieldNamingStrategy; - this.instanceCreators = instanceCreators; - this.constructorConstructor = - new ConstructorConstructor(instanceCreators, useJdkUnsafe, reflectionFilters); - this.serializeNulls = serializeNulls; - this.complexMapKeySerialization = complexMapKeySerialization; - this.generateNonExecutableJson = generateNonExecutableGson; - this.htmlSafe = htmlSafe; - this.formattingStyle = formattingStyle; - this.strictness = strictness; - this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues; - this.useJdkUnsafe = useJdkUnsafe; - this.longSerializationPolicy = longSerializationPolicy; - this.datePattern = datePattern; - this.dateStyle = dateStyle; - this.timeStyle = timeStyle; - this.builderFactories = builderFactories; - this.builderHierarchyFactories = builderHierarchyFactories; - this.objectToNumberStrategy = objectToNumberStrategy; - this.numberToNumberStrategy = numberToNumberStrategy; - this.reflectionFilters = reflectionFilters; - - List factories = new ArrayList<>(); - - // built-in type adapters that cannot be overridden - factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); - factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy)); - - // the excluder must precede all adapters that handle user-defined types - factories.add(excluder); - - // users' type adapters - factories.addAll(factoriesToBeAdded); - - // type adapters for basic platform types - factories.add(TypeAdapters.STRING_FACTORY); - factories.add(TypeAdapters.INTEGER_FACTORY); - factories.add(TypeAdapters.BOOLEAN_FACTORY); - factories.add(TypeAdapters.BYTE_FACTORY); - factories.add(TypeAdapters.SHORT_FACTORY); - TypeAdapter longAdapter = longAdapter(longSerializationPolicy); - factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter)); - factories.add( - TypeAdapters.newFactory( - double.class, Double.class, doubleAdapter(serializeSpecialFloatingPointValues))); - factories.add( - TypeAdapters.newFactory( - float.class, Float.class, floatAdapter(serializeSpecialFloatingPointValues))); - factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy)); - factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY); - factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY); - factories.add(TypeAdapters.newFactory(AtomicLong.class, atomicLongAdapter(longAdapter))); - factories.add( - TypeAdapters.newFactory(AtomicLongArray.class, atomicLongArrayAdapter(longAdapter))); - factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY); - factories.add(TypeAdapters.CHARACTER_FACTORY); - factories.add(TypeAdapters.STRING_BUILDER_FACTORY); - factories.add(TypeAdapters.STRING_BUFFER_FACTORY); - factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL)); - factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER)); - // Add adapter for LazilyParsedNumber because user can obtain it from Gson and then try to - // serialize it again - factories.add( - TypeAdapters.newFactory(LazilyParsedNumber.class, TypeAdapters.LAZILY_PARSED_NUMBER)); - factories.add(TypeAdapters.URL_FACTORY); - factories.add(TypeAdapters.URI_FACTORY); - factories.add(TypeAdapters.UUID_FACTORY); - factories.add(TypeAdapters.CURRENCY_FACTORY); - factories.add(TypeAdapters.LOCALE_FACTORY); - factories.add(TypeAdapters.INET_ADDRESS_FACTORY); - factories.add(TypeAdapters.BIT_SET_FACTORY); - factories.add(DefaultDateTypeAdapter.DEFAULT_STYLE_FACTORY); - factories.add(TypeAdapters.CALENDAR_FACTORY); - - if (SqlTypesSupport.SUPPORTS_SQL_TYPES) { - factories.add(SqlTypesSupport.TIME_FACTORY); - factories.add(SqlTypesSupport.DATE_FACTORY); - factories.add(SqlTypesSupport.TIMESTAMP_FACTORY); + Gson(GsonBuilder builder) { + this.excluder = builder.excluder; + this.fieldNamingStrategy = builder.fieldNamingPolicy; + this.instanceCreators = new HashMap<>(builder.instanceCreators); + this.serializeNulls = builder.serializeNulls; + this.complexMapKeySerialization = builder.complexMapKeySerialization; + this.generateNonExecutableJson = builder.generateNonExecutableJson; + this.htmlSafe = builder.escapeHtmlChars; + this.formattingStyle = builder.formattingStyle; + this.strictness = builder.strictness; + this.serializeSpecialFloatingPointValues = builder.serializeSpecialFloatingPointValues; + this.useJdkUnsafe = builder.useJdkUnsafe; + this.longSerializationPolicy = builder.longSerializationPolicy; + this.datePattern = builder.datePattern; + this.dateStyle = builder.dateStyle; + this.timeStyle = builder.timeStyle; + this.builderFactories = newImmutableList(builder.factories); + this.builderHierarchyFactories = newImmutableList(builder.hierarchyFactories); + this.objectToNumberStrategy = builder.objectToNumberStrategy; + this.numberToNumberStrategy = builder.numberToNumberStrategy; + this.reflectionFilters = newImmutableList(builder.reflectionFilters); + if (builder == GsonBuilder.DEFAULT) { + this.constructorConstructor = GsonBuilder.DEFAULT_CONSTRUCTOR_CONSTRUCTOR; + this.jsonAdapterFactory = GsonBuilder.DEFAULT_JSON_ADAPTER_ANNOTATION_TYPE_ADAPTER_FACTORY; + this.factories = GsonBuilder.DEFAULT_TYPE_ADAPTER_FACTORIES; + } else { + this.constructorConstructor = + new ConstructorConstructor(instanceCreators, useJdkUnsafe, reflectionFilters); + this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor); + this.factories = builder.createFactories(constructorConstructor, jsonAdapterFactory); } - - factories.add(ArrayTypeAdapter.FACTORY); - factories.add(TypeAdapters.CLASS_FACTORY); - - // type adapters for composite and user-defined types - factories.add(new CollectionTypeAdapterFactory(constructorConstructor)); - factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization)); - this.jsonAdapterFactory = new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor); - factories.add(jsonAdapterFactory); - factories.add(TypeAdapters.ENUM_FACTORY); - factories.add( - new ReflectiveTypeAdapterFactory( - constructorConstructor, - fieldNamingStrategy, - excluder, - jsonAdapterFactory, - reflectionFilters)); - - this.factories = Collections.unmodifiableList(factories); } /** @@ -446,143 +305,6 @@ public boolean htmlSafe() { return htmlSafe; } - private TypeAdapter doubleAdapter(boolean serializeSpecialFloatingPointValues) { - if (serializeSpecialFloatingPointValues) { - return TypeAdapters.DOUBLE; - } - return new TypeAdapter() { - @Override - public Double read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - return in.nextDouble(); - } - - @Override - public void write(JsonWriter out, Number value) throws IOException { - if (value == null) { - out.nullValue(); - return; - } - double doubleValue = value.doubleValue(); - checkValidFloatingPoint(doubleValue); - out.value(doubleValue); - } - }; - } - - private TypeAdapter floatAdapter(boolean serializeSpecialFloatingPointValues) { - if (serializeSpecialFloatingPointValues) { - return TypeAdapters.FLOAT; - } - return new TypeAdapter() { - @Override - public Float read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - return (float) in.nextDouble(); - } - - @Override - public void write(JsonWriter out, Number value) throws IOException { - if (value == null) { - out.nullValue(); - return; - } - float floatValue = value.floatValue(); - checkValidFloatingPoint(floatValue); - // For backward compatibility don't call `JsonWriter.value(float)` because that method has - // been newly added and not all custom JsonWriter implementations might override it yet - Number floatNumber = value instanceof Float ? value : floatValue; - out.value(floatNumber); - } - }; - } - - static void checkValidFloatingPoint(double value) { - if (Double.isNaN(value) || Double.isInfinite(value)) { - throw new IllegalArgumentException( - value - + " is not a valid double value as per JSON specification. To override this" - + " behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method."); - } - } - - private static TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { - if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) { - return TypeAdapters.LONG; - } - return new TypeAdapter() { - @Override - public Number read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - return in.nextLong(); - } - - @Override - public void write(JsonWriter out, Number value) throws IOException { - if (value == null) { - out.nullValue(); - return; - } - out.value(value.toString()); - } - }; - } - - private static TypeAdapter atomicLongAdapter(TypeAdapter longAdapter) { - return new TypeAdapter() { - @Override - public void write(JsonWriter out, AtomicLong value) throws IOException { - longAdapter.write(out, value.get()); - } - - @Override - public AtomicLong read(JsonReader in) throws IOException { - Number value = longAdapter.read(in); - return new AtomicLong(value.longValue()); - } - }.nullSafe(); - } - - private static TypeAdapter atomicLongArrayAdapter( - TypeAdapter longAdapter) { - return new TypeAdapter() { - @Override - public void write(JsonWriter out, AtomicLongArray value) throws IOException { - out.beginArray(); - for (int i = 0, length = value.length(); i < length; i++) { - longAdapter.write(out, value.get(i)); - } - out.endArray(); - } - - @Override - public AtomicLongArray read(JsonReader in) throws IOException { - List list = new ArrayList<>(); - in.beginArray(); - while (in.hasNext()) { - long value = longAdapter.read(in).longValue(); - list.add(value); - } - in.endArray(); - int length = list.size(); - AtomicLongArray array = new AtomicLongArray(length); - for (int i = 0; i < length; ++i) { - array.set(i, list.get(i)); - } - return array; - } - }.nullSafe(); - } - /** * Returns the type adapter for {@code type}. * diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java index 5879973223..3fc35af5d2 100644 --- a/gson/src/main/java/com/google/gson/GsonBuilder.java +++ b/gson/src/main/java/com/google/gson/GsonBuilder.java @@ -16,24 +16,20 @@ package com.google.gson; -import static com.google.gson.Gson.DEFAULT_COMPLEX_MAP_KEYS; -import static com.google.gson.Gson.DEFAULT_DATE_PATTERN; -import static com.google.gson.Gson.DEFAULT_ESCAPE_HTML; -import static com.google.gson.Gson.DEFAULT_FORMATTING_STYLE; -import static com.google.gson.Gson.DEFAULT_JSON_NON_EXECUTABLE; -import static com.google.gson.Gson.DEFAULT_NUMBER_TO_NUMBER_STRATEGY; -import static com.google.gson.Gson.DEFAULT_OBJECT_TO_NUMBER_STRATEGY; -import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS; -import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES; -import static com.google.gson.Gson.DEFAULT_STRICTNESS; -import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE; - import com.google.errorprone.annotations.CanIgnoreReturnValue; import com.google.errorprone.annotations.InlineMe; import com.google.gson.annotations.Since; import com.google.gson.annotations.Until; +import com.google.gson.internal.ConstructorConstructor; import com.google.gson.internal.Excluder; +import com.google.gson.internal.bind.ArrayTypeAdapter; +import com.google.gson.internal.bind.CollectionTypeAdapterFactory; import com.google.gson.internal.bind.DefaultDateTypeAdapter; +import com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory; +import com.google.gson.internal.bind.MapTypeAdapterFactory; +import com.google.gson.internal.bind.NumberTypeAdapter; +import com.google.gson.internal.bind.ObjectTypeAdapter; +import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.internal.bind.TreeTypeAdapter; import com.google.gson.internal.bind.TypeAdapters; import com.google.gson.internal.sql.SqlTypesSupport; @@ -45,12 +41,16 @@ import java.text.SimpleDateFormat; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; /** * Use this builder to construct a {@link Gson} instance when you need to set configuration options @@ -90,29 +90,64 @@ * @author Jesse Wilson */ public final class GsonBuilder { - private Excluder excluder = Excluder.DEFAULT; - private LongSerializationPolicy longSerializationPolicy = LongSerializationPolicy.DEFAULT; - private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY; - private final Map> instanceCreators = new HashMap<>(); - private final List factories = new ArrayList<>(); + private static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; + // Strictness of `null` is the legacy mode where some Gson APIs are always lenient + private static final Strictness DEFAULT_STRICTNESS = null; + private static final FormattingStyle DEFAULT_FORMATTING_STYLE = FormattingStyle.COMPACT; + private static final boolean DEFAULT_ESCAPE_HTML = true; + private static final boolean DEFAULT_SERIALIZE_NULLS = false; + private static final boolean DEFAULT_COMPLEX_MAP_KEYS = false; + private static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false; + private static final boolean DEFAULT_USE_JDK_UNSAFE = true; + private static final String DEFAULT_DATE_PATTERN = null; + private static final FieldNamingStrategy DEFAULT_FIELD_NAMING_STRATEGY = + FieldNamingPolicy.IDENTITY; + private static final ToNumberStrategy DEFAULT_OBJECT_TO_NUMBER_STRATEGY = ToNumberPolicy.DOUBLE; + private static final ToNumberStrategy DEFAULT_NUMBER_TO_NUMBER_STRATEGY = + ToNumberPolicy.LAZILY_PARSED_NUMBER; + + static final ConstructorConstructor DEFAULT_CONSTRUCTOR_CONSTRUCTOR = + new ConstructorConstructor( + Collections.emptyMap(), DEFAULT_USE_JDK_UNSAFE, Collections.emptyList()); + + static final JsonAdapterAnnotationTypeAdapterFactory + DEFAULT_JSON_ADAPTER_ANNOTATION_TYPE_ADAPTER_FACTORY = + new JsonAdapterAnnotationTypeAdapterFactory(DEFAULT_CONSTRUCTOR_CONSTRUCTOR); + + /** + * Default instance of the builder, to be used only by the default {@link Gson#Gson()} + * constructor. Must not be used for anything else and must not be leaked to user code, since that + * could lead to accidental modification of this default builder. + */ + static final GsonBuilder DEFAULT = new GsonBuilder(); + + static final List DEFAULT_TYPE_ADAPTER_FACTORIES = + GsonBuilder.DEFAULT.createFactories( + DEFAULT_CONSTRUCTOR_CONSTRUCTOR, DEFAULT_JSON_ADAPTER_ANNOTATION_TYPE_ADAPTER_FACTORY); + + Excluder excluder = Excluder.DEFAULT; + LongSerializationPolicy longSerializationPolicy = LongSerializationPolicy.DEFAULT; + FieldNamingStrategy fieldNamingPolicy = DEFAULT_FIELD_NAMING_STRATEGY; + final Map> instanceCreators = new HashMap<>(); + final List factories = new ArrayList<>(); /** tree-style hierarchy factories. These come after factories for backwards compatibility. */ - private final List hierarchyFactories = new ArrayList<>(); - - private boolean serializeNulls = DEFAULT_SERIALIZE_NULLS; - private String datePattern = DEFAULT_DATE_PATTERN; - private int dateStyle = DateFormat.DEFAULT; - private int timeStyle = DateFormat.DEFAULT; - private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS; - private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES; - private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML; - private FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE; - private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; - private Strictness strictness = DEFAULT_STRICTNESS; - private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE; - private ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY; - private ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY; - private final ArrayDeque reflectionFilters = new ArrayDeque<>(); + final List hierarchyFactories = new ArrayList<>(); + + boolean serializeNulls = DEFAULT_SERIALIZE_NULLS; + String datePattern = DEFAULT_DATE_PATTERN; + int dateStyle = DateFormat.DEFAULT; + int timeStyle = DateFormat.DEFAULT; + boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS; + boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES; + boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML; + FormattingStyle formattingStyle = DEFAULT_FORMATTING_STYLE; + boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; + Strictness strictness = DEFAULT_STRICTNESS; + boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE; + ToNumberStrategy objectToNumberStrategy = DEFAULT_OBJECT_TO_NUMBER_STRATEGY; + ToNumberStrategy numberToNumberStrategy = DEFAULT_NUMBER_TO_NUMBER_STRATEGY; + final ArrayDeque reflectionFilters = new ArrayDeque<>(); /** * Creates a GsonBuilder instance that can be used to build Gson with various configuration @@ -884,43 +919,123 @@ public GsonBuilder addReflectionAccessFilter(ReflectionAccessFilter filter) { * @return an instance of Gson configured with the options currently set in this builder */ public Gson create() { - List factories = - new ArrayList<>(this.factories.size() + this.hierarchyFactories.size() + 3); - factories.addAll(this.factories); - Collections.reverse(factories); - - List hierarchyFactories = new ArrayList<>(this.hierarchyFactories); - Collections.reverse(hierarchyFactories); - factories.addAll(hierarchyFactories); - - addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories); - - return new Gson( - excluder, - fieldNamingPolicy, - new HashMap<>(instanceCreators), - serializeNulls, - complexMapKeySerialization, - generateNonExecutableJson, - escapeHtmlChars, - formattingStyle, - strictness, - serializeSpecialFloatingPointValues, - useJdkUnsafe, - longSerializationPolicy, - datePattern, - dateStyle, - timeStyle, - new ArrayList<>(this.factories), - new ArrayList<>(this.hierarchyFactories), - factories, - objectToNumberStrategy, - numberToNumberStrategy, - new ArrayList<>(reflectionFilters)); - } - - private static void addTypeAdaptersForDate( - String datePattern, int dateStyle, int timeStyle, List factories) { + return new Gson(this); + } + + List createFactories( + ConstructorConstructor constructorConstructor, + JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) { + ArrayList factories = new ArrayList<>(); + + // built-in type adapters that cannot be overridden + factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); + factories.add(ObjectTypeAdapter.getFactory(objectToNumberStrategy)); + + // the excluder must precede all adapters that handle user-defined types + factories.add(excluder); + + // users' type adapters + addUserDefinedAdapters(factories); + + // custom Date adapters + addDateTypeAdapters(factories); + + // type adapters for basic platform types + factories.add(TypeAdapters.STRING_FACTORY); + factories.add(TypeAdapters.INTEGER_FACTORY); + factories.add(TypeAdapters.BOOLEAN_FACTORY); + factories.add(TypeAdapters.BYTE_FACTORY); + factories.add(TypeAdapters.SHORT_FACTORY); + TypeAdapter longAdapter = longSerializationPolicy.typeAdapter(); + factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter)); + factories.add(TypeAdapters.newFactory(double.class, Double.class, doubleAdapter())); + factories.add(TypeAdapters.newFactory(float.class, Float.class, floatAdapter())); + factories.add(NumberTypeAdapter.getFactory(numberToNumberStrategy)); + factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY); + factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY); + factories.add( + TypeAdapters.newFactory(AtomicLong.class, TypeAdapters.atomicLongAdapter(longAdapter))); + factories.add( + TypeAdapters.newFactory( + AtomicLongArray.class, TypeAdapters.atomicLongArrayAdapter(longAdapter))); + factories.add(TypeAdapters.ATOMIC_INTEGER_ARRAY_FACTORY); + factories.add(TypeAdapters.CHARACTER_FACTORY); + factories.add(TypeAdapters.STRING_BUILDER_FACTORY); + factories.add(TypeAdapters.STRING_BUFFER_FACTORY); + factories.add(TypeAdapters.BIG_DECIMAL_FACTORY); + factories.add(TypeAdapters.BIG_INTEGER_FACTORY); + // Add adapter for LazilyParsedNumber because user can obtain it from Gson and then try to + // serialize it again + factories.add(TypeAdapters.LAZILY_PARSED_NUMBER_FACTORY); + factories.add(TypeAdapters.URL_FACTORY); + factories.add(TypeAdapters.URI_FACTORY); + factories.add(TypeAdapters.UUID_FACTORY); + factories.add(TypeAdapters.CURRENCY_FACTORY); + factories.add(TypeAdapters.LOCALE_FACTORY); + factories.add(TypeAdapters.INET_ADDRESS_FACTORY); + factories.add(TypeAdapters.BIT_SET_FACTORY); + factories.add(DefaultDateTypeAdapter.DEFAULT_STYLE_FACTORY); + factories.add(TypeAdapters.CALENDAR_FACTORY); + factories.addAll(SqlTypesSupport.SQL_TYPE_FACTORIES); + factories.add(ArrayTypeAdapter.FACTORY); + factories.add(TypeAdapters.CLASS_FACTORY); + + // type adapters for composite and user-defined types + factories.add(new CollectionTypeAdapterFactory(constructorConstructor)); + factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization)); + factories.add(jsonAdapterFactory); + factories.add(TypeAdapters.ENUM_FACTORY); + factories.add( + new ReflectiveTypeAdapterFactory( + constructorConstructor, + fieldNamingPolicy, + excluder, + jsonAdapterFactory, + newImmutableList(reflectionFilters))); + + factories.trimToSize(); + return Collections.unmodifiableList(factories); + } + + static List newImmutableList(Collection collection) { + if (collection.isEmpty()) { + return Collections.emptyList(); + } + if (collection.size() == 1) { + return Collections.singletonList( + collection instanceof List + ? ((List) collection).get(0) + : collection.iterator().next()); + } + @SuppressWarnings("unchecked") + List list = (List) Collections.unmodifiableList(Arrays.asList(collection.toArray())); + return list; + } + + private TypeAdapter doubleAdapter() { + return serializeSpecialFloatingPointValues ? TypeAdapters.DOUBLE : TypeAdapters.DOUBLE_STRICT; + } + + private TypeAdapter floatAdapter() { + return serializeSpecialFloatingPointValues ? TypeAdapters.FLOAT : TypeAdapters.FLOAT_STRICT; + } + + private void addUserDefinedAdapters(List all) { + if (!this.factories.isEmpty()) { + List reversedFactories = new ArrayList<>(this.factories); + Collections.reverse(reversedFactories); + all.addAll(reversedFactories); + } + + if (!this.hierarchyFactories.isEmpty()) { + List reversedHierarchyFactories = + new ArrayList<>(this.hierarchyFactories); + Collections.reverse(reversedHierarchyFactories); + all.addAll(reversedHierarchyFactories); + } + } + + private void addDateTypeAdapters(List factories) { TypeAdapterFactory dateAdapterFactory; boolean sqlTypesSupported = SqlTypesSupport.SUPPORTS_SQL_TYPES; TypeAdapterFactory sqlTimestampAdapterFactory = null; diff --git a/gson/src/main/java/com/google/gson/LongSerializationPolicy.java b/gson/src/main/java/com/google/gson/LongSerializationPolicy.java index df7c8fa167..2e2f17a59a 100644 --- a/gson/src/main/java/com/google/gson/LongSerializationPolicy.java +++ b/gson/src/main/java/com/google/gson/LongSerializationPolicy.java @@ -16,6 +16,8 @@ package com.google.gson; +import com.google.gson.internal.bind.TypeAdapters; + /** * Defines the expected format for a {@code long} or {@code Long} type when it is serialized. * @@ -39,6 +41,11 @@ public JsonElement serialize(Long value) { } return new JsonPrimitive(value); } + + @Override + TypeAdapter typeAdapter() { + return TypeAdapters.LONG; + } }, /** @@ -55,6 +62,11 @@ public JsonElement serialize(Long value) { } return new JsonPrimitive(value.toString()); } + + @Override + TypeAdapter typeAdapter() { + return TypeAdapters.LONG_AS_STRING; + } }; /** @@ -64,4 +76,8 @@ public JsonElement serialize(Long value) { * @return the serialized version of {@code value} */ public abstract JsonElement serialize(Long value); + + /** Returns the corresponding {@link TypeAdapter} for this serialization policy. */ + // Internal method + abstract TypeAdapter typeAdapter(); } diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java index a76a0014e4..542c835349 100644 --- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java +++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java @@ -30,6 +30,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.EnumMap; import java.util.EnumSet; import java.util.LinkedHashMap; @@ -43,6 +44,11 @@ /** Returns a function that can construct an instance of a requested type. */ public final class ConstructorConstructor { + public static final boolean DEFAULT_USE_JDK_UNSAFE = true; + public static final ConstructorConstructor DEFAULT = + new ConstructorConstructor( + Collections.emptyMap(), DEFAULT_USE_JDK_UNSAFE, Collections.emptyList()); + private final Map> instanceCreators; private final boolean useJdkUnsafe; private final List reflectionFilters; diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 71f98c0034..0cbb96083b 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -43,11 +43,14 @@ import java.util.GregorianCalendar; import java.util.List; import java.util.Locale; +import java.util.Objects; import java.util.StringTokenizer; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerArray; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicLongArray; /** * Type adapters for basic types. More complex adapters exist as separate classes in the enclosing @@ -299,6 +302,22 @@ public void write(JsonWriter out, AtomicInteger value) throws IOException { public static final TypeAdapterFactory ATOMIC_INTEGER_FACTORY = newFactory(AtomicInteger.class, TypeAdapters.ATOMIC_INTEGER); + public static TypeAdapter atomicLongAdapter(TypeAdapter longAdapter) { + Objects.requireNonNull(longAdapter); + return new TypeAdapter() { + @Override + public AtomicLong read(JsonReader in) throws IOException { + Number value = longAdapter.read(in); + return new AtomicLong(value.longValue()); + } + + @Override + public void write(JsonWriter out, AtomicLong value) throws IOException { + longAdapter.write(out, value.get()); + } + }.nullSafe(); + } + public static final TypeAdapter ATOMIC_BOOLEAN = new TypeAdapter() { @Override @@ -349,6 +368,38 @@ public void write(JsonWriter out, AtomicIntegerArray value) throws IOException { public static final TypeAdapterFactory ATOMIC_INTEGER_ARRAY_FACTORY = newFactory(AtomicIntegerArray.class, TypeAdapters.ATOMIC_INTEGER_ARRAY); + public static TypeAdapter atomicLongArrayAdapter( + TypeAdapter longAdapter) { + Objects.requireNonNull(longAdapter); + return new TypeAdapter() { + @Override + public AtomicLongArray read(JsonReader in) throws IOException { + List list = new ArrayList<>(); + in.beginArray(); + while (in.hasNext()) { + long value = longAdapter.read(in).longValue(); + list.add(value); + } + in.endArray(); + int length = list.size(); + AtomicLongArray array = new AtomicLongArray(length); + for (int i = 0; i < length; ++i) { + array.set(i, list.get(i)); + } + return array; + } + + @Override + public void write(JsonWriter out, AtomicLongArray value) throws IOException { + out.beginArray(); + for (int i = 0, length = value.length(); i < length; i++) { + longAdapter.write(out, value.get(i)); + } + out.endArray(); + } + }.nullSafe(); + } + public static final TypeAdapter LONG = new TypeAdapter() { @Override @@ -374,7 +425,7 @@ public void write(JsonWriter out, Number value) throws IOException { } }; - public static final TypeAdapter FLOAT = + public static final TypeAdapter LONG_AS_STRING = new TypeAdapter() { @Override public Number read(JsonReader in) throws IOException { @@ -382,43 +433,96 @@ public Number read(JsonReader in) throws IOException { in.nextNull(); return null; } - return (float) in.nextDouble(); + return in.nextLong(); } @Override public void write(JsonWriter out, Number value) throws IOException { if (value == null) { out.nullValue(); - } else { - // For backward compatibility don't call `JsonWriter.value(float)` because that method - // has been newly added and not all custom JsonWriter implementations might override - // it yet - Number floatNumber = value instanceof Float ? value : value.floatValue(); - out.value(floatNumber); + return; } + out.value(value.toString()); } }; - public static final TypeAdapter DOUBLE = - new TypeAdapter() { - @Override - public Number read(JsonReader in) throws IOException { - if (in.peek() == JsonToken.NULL) { - in.nextNull(); - return null; - } - return in.nextDouble(); - } + private static class FloatAdapter extends TypeAdapter { + private final boolean strict; - @Override - public void write(JsonWriter out, Number value) throws IOException { - if (value == null) { - out.nullValue(); - } else { - out.value(value.doubleValue()); - } - } - }; + FloatAdapter(boolean strict) { + this.strict = strict; + } + + @Override + public Float read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return (float) in.nextDouble(); + } + + @Override + public void write(JsonWriter out, Number value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + float floatValue = value.floatValue(); + if (strict) { + checkValidFloatingPoint(floatValue); + } + // For backward compatibility don't call `JsonWriter.value(float)` because that method has + // been newly added and not all custom JsonWriter implementations might override it yet + Number floatNumber = value instanceof Float ? value : floatValue; + out.value(floatNumber); + } + } + + private static class DoubleAdapter extends TypeAdapter { + private final boolean strict; + + DoubleAdapter(boolean strict) { + this.strict = strict; + } + + @Override + public Double read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return in.nextDouble(); + } + + @Override + public void write(JsonWriter out, Number value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + double doubleValue = value.doubleValue(); + if (strict) { + checkValidFloatingPoint(doubleValue); + } + out.value(doubleValue); + } + } + + private static void checkValidFloatingPoint(double value) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + throw new IllegalArgumentException( + value + + " is not a valid double value as per JSON specification. To override this" + + " behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method."); + } + } + + public static final TypeAdapter FLOAT = new FloatAdapter(false); + public static final TypeAdapter FLOAT_STRICT = new FloatAdapter(true); + + public static final TypeAdapter DOUBLE = new DoubleAdapter(false); + public static final TypeAdapter DOUBLE_STRICT = new DoubleAdapter(true); public static final TypeAdapter CHARACTER = new TypeAdapter() { @@ -490,6 +594,9 @@ public void write(JsonWriter out, BigDecimal value) throws IOException { } }; + public static final TypeAdapterFactory BIG_DECIMAL_FACTORY = + newFactory(BigDecimal.class, BIG_DECIMAL); + public static final TypeAdapter BIG_INTEGER = new TypeAdapter() { @Override @@ -513,6 +620,9 @@ public void write(JsonWriter out, BigInteger value) throws IOException { } }; + public static final TypeAdapterFactory BIG_INTEGER_FACTORY = + newFactory(BigInteger.class, BIG_INTEGER); + public static final TypeAdapter LAZILY_PARSED_NUMBER = new TypeAdapter() { // Normally users should not be able to access and deserialize LazilyParsedNumber because @@ -533,6 +643,9 @@ public void write(JsonWriter out, LazilyParsedNumber value) throws IOException { } }; + public static final TypeAdapterFactory LAZILY_PARSED_NUMBER_FACTORY = + newFactory(LazilyParsedNumber.class, LAZILY_PARSED_NUMBER); + public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING); public static final TypeAdapter STRING_BUILDER = diff --git a/gson/src/main/java/com/google/gson/internal/sql/SqlTypesSupport.java b/gson/src/main/java/com/google/gson/internal/sql/SqlTypesSupport.java index e1a384b0b9..bbdcdd28ad 100644 --- a/gson/src/main/java/com/google/gson/internal/sql/SqlTypesSupport.java +++ b/gson/src/main/java/com/google/gson/internal/sql/SqlTypesSupport.java @@ -19,7 +19,10 @@ import com.google.gson.TypeAdapterFactory; import com.google.gson.internal.bind.DefaultDateTypeAdapter.DateType; import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Collections; import java.util.Date; +import java.util.List; /** * Encapsulates access to {@code java.sql} types, to allow Gson to work without the {@code java.sql} @@ -28,7 +31,8 @@ * *

If {@link #SUPPORTS_SQL_TYPES} is {@code true}, all other constants of this class will be * non-{@code null}. However, if it is {@code false} all other constants will be {@code null} and - * there will be no support for {@code java.sql} types. + * {@link #SQL_TYPE_FACTORIES} will be empty, and there will be no support for {@code java.sql} + * types. */ @SuppressWarnings("JavaUtilDate") public final class SqlTypesSupport { @@ -38,9 +42,11 @@ public final class SqlTypesSupport { public static final DateType DATE_DATE_TYPE; public static final DateType TIMESTAMP_DATE_TYPE; - public static final TypeAdapterFactory DATE_FACTORY; - public static final TypeAdapterFactory TIME_FACTORY; - public static final TypeAdapterFactory TIMESTAMP_FACTORY; + static final TypeAdapterFactory DATE_FACTORY; + static final TypeAdapterFactory TIME_FACTORY; + static final TypeAdapterFactory TIMESTAMP_FACTORY; + + public static final List SQL_TYPE_FACTORIES; static { boolean sqlTypesSupport; @@ -71,6 +77,10 @@ protected Timestamp deserialize(Date date) { DATE_FACTORY = SqlDateTypeAdapter.FACTORY; TIME_FACTORY = SqlTimeTypeAdapter.FACTORY; TIMESTAMP_FACTORY = SqlTimestampTypeAdapter.FACTORY; + + SQL_TYPE_FACTORIES = + Collections.unmodifiableList( + Arrays.asList(TIME_FACTORY, DATE_FACTORY, TIMESTAMP_FACTORY)); } else { DATE_DATE_TYPE = null; TIMESTAMP_DATE_TYPE = null; @@ -78,6 +88,8 @@ protected Timestamp deserialize(Date date) { DATE_FACTORY = null; TIME_FACTORY = null; TIMESTAMP_FACTORY = null; + + SQL_TYPE_FACTORIES = Collections.emptyList(); } } diff --git a/gson/src/test/java/com/google/gson/GsonTest.java b/gson/src/test/java/com/google/gson/GsonTest.java index 4798168984..2d00dd4d35 100644 --- a/gson/src/test/java/com/google/gson/GsonTest.java +++ b/gson/src/test/java/com/google/gson/GsonTest.java @@ -28,10 +28,6 @@ import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; -import java.text.DateFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -53,6 +49,17 @@ public final class GsonTest { private static final ToNumberStrategy CUSTOM_NUMBER_TO_NUMBER_STRATEGY = ToNumberPolicy.LAZILY_PARSED_NUMBER; + private static Gson createGson() { + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.excluder = CUSTOM_EXCLUDER; + gsonBuilder.fieldNamingPolicy = CUSTOM_FIELD_NAMING_STRATEGY; + gsonBuilder.serializeNulls = true; + gsonBuilder.disableHtmlEscaping(); + gsonBuilder.objectToNumberStrategy = CUSTOM_OBJECT_TO_NUMBER_STRATEGY; + gsonBuilder.numberToNumberStrategy = CUSTOM_NUMBER_TO_NUMBER_STRATEGY; + return gsonBuilder.create(); + } + @Test public void testStrictnessDefault() { assertThat(new Gson().strictness).isNull(); @@ -60,29 +67,7 @@ public void testStrictnessDefault() { @Test public void testOverridesDefaultExcluder() { - Gson gson = - new Gson( - CUSTOM_EXCLUDER, - CUSTOM_FIELD_NAMING_STRATEGY, - new HashMap<>(), - true, - false, - true, - false, - FormattingStyle.PRETTY, - Strictness.LENIENT, - false, - true, - LongSerializationPolicy.DEFAULT, - null, - DateFormat.DEFAULT, - DateFormat.DEFAULT, - new ArrayList<>(), - new ArrayList<>(), - new ArrayList<>(), - CUSTOM_OBJECT_TO_NUMBER_STRATEGY, - CUSTOM_NUMBER_TO_NUMBER_STRATEGY, - Collections.emptyList()); + Gson gson = createGson(); assertThat(gson.excluder).isEqualTo(CUSTOM_EXCLUDER); assertThat(gson.fieldNamingStrategy()).isEqualTo(CUSTOM_FIELD_NAMING_STRATEGY); @@ -92,29 +77,7 @@ public void testOverridesDefaultExcluder() { @Test public void testClonedTypeAdapterFactoryListsAreIndependent() { - Gson original = - new Gson( - CUSTOM_EXCLUDER, - CUSTOM_FIELD_NAMING_STRATEGY, - new HashMap<>(), - true, - false, - true, - false, - FormattingStyle.PRETTY, - Strictness.LENIENT, - false, - true, - LongSerializationPolicy.DEFAULT, - null, - DateFormat.DEFAULT, - DateFormat.DEFAULT, - new ArrayList<>(), - new ArrayList<>(), - new ArrayList<>(), - CUSTOM_OBJECT_TO_NUMBER_STRATEGY, - CUSTOM_NUMBER_TO_NUMBER_STRATEGY, - Collections.emptyList()); + Gson original = createGson(); Gson clone = original.newBuilder().registerTypeAdapter(int.class, new TestTypeAdapter()).create(); diff --git a/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java b/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java index 23778079c7..2334cd9647 100644 --- a/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java +++ b/gson/src/test/java/com/google/gson/internal/sql/SqlTypesSupportTest.java @@ -31,5 +31,11 @@ public void testSupported() { assertThat(SqlTypesSupport.DATE_FACTORY).isNotNull(); assertThat(SqlTypesSupport.TIME_FACTORY).isNotNull(); assertThat(SqlTypesSupport.TIMESTAMP_FACTORY).isNotNull(); + assertThat(SqlTypesSupport.SQL_TYPE_FACTORIES) + .containsExactly( + SqlTypesSupport.TIME_FACTORY, + SqlTypesSupport.DATE_FACTORY, + SqlTypesSupport.TIMESTAMP_FACTORY) + .inOrder(); } }