aboutsummaryrefslogtreecommitdiffstats
path: root/gson/src
diff options
context:
space:
mode:
Diffstat (limited to 'gson/src')
-rw-r--r--gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java119
-rw-r--r--gson/src/main/java/com/google/gson/ExclusionStrategy.java109
-rw-r--r--gson/src/main/java/com/google/gson/FieldAttributes.java157
-rw-r--r--gson/src/main/java/com/google/gson/FieldNamingPolicy.java169
-rw-r--r--gson/src/main/java/com/google/gson/FieldNamingStrategy.java40
-rw-r--r--gson/src/main/java/com/google/gson/Gson.java916
-rw-r--r--gson/src/main/java/com/google/gson/GsonBuilder.java566
-rw-r--r--gson/src/main/java/com/google/gson/InstanceCreator.java92
-rw-r--r--gson/src/main/java/com/google/gson/JsonArray.java373
-rw-r--r--gson/src/main/java/com/google/gson/JsonDeserializationContext.java44
-rw-r--r--gson/src/main/java/com/google/gson/JsonDeserializer.java91
-rw-r--r--gson/src/main/java/com/google/gson/JsonElement.java330
-rw-r--r--gson/src/main/java/com/google/gson/JsonIOException.java45
-rwxr-xr-xgson/src/main/java/com/google/gson/JsonNull.java63
-rw-r--r--gson/src/main/java/com/google/gson/JsonObject.java195
-rw-r--r--gson/src/main/java/com/google/gson/JsonParseException.java64
-rwxr-xr-xgson/src/main/java/com/google/gson/JsonParser.java93
-rw-r--r--gson/src/main/java/com/google/gson/JsonPrimitive.java341
-rw-r--r--gson/src/main/java/com/google/gson/JsonSerializationContext.java49
-rw-r--r--gson/src/main/java/com/google/gson/JsonSerializer.java89
-rw-r--r--gson/src/main/java/com/google/gson/JsonStreamParser.java122
-rw-r--r--gson/src/main/java/com/google/gson/JsonSyntaxException.java47
-rw-r--r--gson/src/main/java/com/google/gson/LongSerializationPolicy.java58
-rw-r--r--gson/src/main/java/com/google/gson/TreeTypeAdapter.java140
-rw-r--r--gson/src/main/java/com/google/gson/TypeAdapter.java290
-rw-r--r--gson/src/main/java/com/google/gson/TypeAdapterFactory.java170
-rw-r--r--gson/src/main/java/com/google/gson/annotations/Expose.java79
-rw-r--r--gson/src/main/java/com/google/gson/annotations/JsonAdapter.java97
-rw-r--r--gson/src/main/java/com/google/gson/annotations/SerializedName.java89
-rw-r--r--gson/src/main/java/com/google/gson/annotations/Since.java61
-rw-r--r--gson/src/main/java/com/google/gson/annotations/Until.java66
-rw-r--r--gson/src/main/java/com/google/gson/annotations/package-info.java6
-rw-r--r--gson/src/main/java/com/google/gson/internal/$Gson$Preconditions.java49
-rw-r--r--gson/src/main/java/com/google/gson/internal/$Gson$Types.java588
-rw-r--r--gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java220
-rw-r--r--gson/src/main/java/com/google/gson/internal/Excluder.java251
-rw-r--r--gson/src/main/java/com/google/gson/internal/JsonReaderInternalAccess.java32
-rw-r--r--gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java96
-rw-r--r--gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java861
-rw-r--r--gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java627
-rw-r--r--gson/src/main/java/com/google/gson/internal/ObjectConstructor.java33
-rw-r--r--gson/src/main/java/com/google/gson/internal/Primitives.java119
-rw-r--r--gson/src/main/java/com/google/gson/internal/Streams.java121
-rw-r--r--gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java104
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java97
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java101
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java93
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java67
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java226
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java200
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java264
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java109
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java249
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java67
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java66
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java81
-rw-r--r--gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java833
-rw-r--r--gson/src/main/java/com/google/gson/internal/package-info.java7
-rw-r--r--gson/src/main/java/com/google/gson/package-info.java11
-rw-r--r--gson/src/main/java/com/google/gson/reflect/TypeToken.java305
-rw-r--r--gson/src/main/java/com/google/gson/reflect/package-info.java6
-rw-r--r--gson/src/main/java/com/google/gson/stream/JsonReader.java1624
-rw-r--r--gson/src/main/java/com/google/gson/stream/JsonScope.java71
-rw-r--r--gson/src/main/java/com/google/gson/stream/JsonToken.java85
-rw-r--r--gson/src/main/java/com/google/gson/stream/JsonWriter.java651
-rw-r--r--gson/src/main/java/com/google/gson/stream/MalformedJsonException.java44
-rw-r--r--gson/src/test/java/com/google/gson/CommentsTest.java45
-rw-r--r--gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java163
-rw-r--r--gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java45
-rw-r--r--gson/src/test/java/com/google/gson/DefaultMapJsonSerializerTest.java64
-rw-r--r--gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java89
-rw-r--r--gson/src/test/java/com/google/gson/FieldAttributesTest.java82
-rw-r--r--gson/src/test/java/com/google/gson/GenericArrayTypeTest.java57
-rwxr-xr-xgson/src/test/java/com/google/gson/GsonBuilderTest.java87
-rw-r--r--gson/src/test/java/com/google/gson/GsonTypeAdapterTest.java149
-rw-r--r--gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java58
-rw-r--r--gson/src/test/java/com/google/gson/JavaSerializationTest.java75
-rw-r--r--gson/src/test/java/com/google/gson/JsonArrayTest.java102
-rw-r--r--gson/src/test/java/com/google/gson/JsonNullTest.java40
-rw-r--r--gson/src/test/java/com/google/gson/JsonObjectTest.java172
-rw-r--r--gson/src/test/java/com/google/gson/JsonParserTest.java126
-rw-r--r--gson/src/test/java/com/google/gson/JsonPrimitiveTest.java247
-rw-r--r--gson/src/test/java/com/google/gson/JsonStreamParserTest.java74
-rw-r--r--gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java64
-rw-r--r--gson/src/test/java/com/google/gson/MixedStreamTest.java239
-rw-r--r--gson/src/test/java/com/google/gson/MockExclusionStrategy.java41
-rw-r--r--gson/src/test/java/com/google/gson/ObjectTypeAdapterTest.java63
-rw-r--r--gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java77
-rw-r--r--gson/src/test/java/com/google/gson/ParameterizedTypeFixtures.java177
-rw-r--r--gson/src/test/java/com/google/gson/ParameterizedTypeTest.java57
-rw-r--r--gson/src/test/java/com/google/gson/PrimitiveTypeAdapter.java73
-rw-r--r--gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java55
-rw-r--r--gson/src/test/java/com/google/gson/common/MoreAsserts.java73
-rw-r--r--gson/src/test/java/com/google/gson/common/TestTypes.java418
-rw-r--r--gson/src/test/java/com/google/gson/functional/ArrayTest.java268
-rw-r--r--gson/src/test/java/com/google/gson/functional/CircularReferenceTest.java126
-rw-r--r--gson/src/test/java/com/google/gson/functional/CollectionTest.java360
-rwxr-xr-xgson/src/test/java/com/google/gson/functional/ConcurrencyTest.java140
-rw-r--r--gson/src/test/java/com/google/gson/functional/CustomDeserializerTest.java207
-rw-r--r--gson/src/test/java/com/google/gson/functional/CustomSerializerTest.java102
-rw-r--r--gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java456
-rw-r--r--gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java724
-rw-r--r--gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java92
-rw-r--r--gson/src/test/java/com/google/gson/functional/EnumTest.java202
-rw-r--r--gson/src/test/java/com/google/gson/functional/EscapingTest.java92
-rw-r--r--gson/src/test/java/com/google/gson/functional/ExclusionStrategyFunctionalTest.java202
-rw-r--r--gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java169
-rw-r--r--gson/src/test/java/com/google/gson/functional/FieldExclusionTest.java92
-rw-r--r--gson/src/test/java/com/google/gson/functional/FieldNamingTest.java89
-rw-r--r--gson/src/test/java/com/google/gson/functional/InheritanceTest.java277
-rw-r--r--gson/src/test/java/com/google/gson/functional/InstanceCreatorTest.java127
-rw-r--r--gson/src/test/java/com/google/gson/functional/InterfaceTest.java72
-rw-r--r--gson/src/test/java/com/google/gson/functional/InternationalizationTest.java71
-rw-r--r--gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java229
-rw-r--r--gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java203
-rw-r--r--gson/src/test/java/com/google/gson/functional/JsonArrayTest.java162
-rw-r--r--gson/src/test/java/com/google/gson/functional/JsonParserTest.java143
-rw-r--r--gson/src/test/java/com/google/gson/functional/JsonTreeTest.java89
-rw-r--r--gson/src/test/java/com/google/gson/functional/MapAsArrayTypeAdapterTest.java141
-rwxr-xr-xgson/src/test/java/com/google/gson/functional/MapTest.java582
-rw-r--r--gson/src/test/java/com/google/gson/functional/MoreSpecificTypeSerializationTest.java178
-rw-r--r--gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java188
-rwxr-xr-xgson/src/test/java/com/google/gson/functional/NullObjectAndFieldTest.java240
-rw-r--r--gson/src/test/java/com/google/gson/functional/ObjectTest.java501
-rw-r--r--gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java503
-rw-r--r--gson/src/test/java/com/google/gson/functional/PrettyPrintingTest.java123
-rw-r--r--gson/src/test/java/com/google/gson/functional/PrimitiveCharacterTest.java55
-rw-r--r--gson/src/test/java/com/google/gson/functional/PrimitiveTest.java821
-rw-r--r--gson/src/test/java/com/google/gson/functional/PrintFormattingTest.java84
-rw-r--r--gson/src/test/java/com/google/gson/functional/RawSerializationTest.java101
-rw-r--r--gson/src/test/java/com/google/gson/functional/ReadersWritersTest.java135
-rw-r--r--gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java205
-rw-r--r--gson/src/test/java/com/google/gson/functional/SecurityTest.java86
-rw-r--r--gson/src/test/java/com/google/gson/functional/SerializedNameTest.java54
-rw-r--r--gson/src/test/java/com/google/gson/functional/StreamingTypeAdaptersTest.java261
-rw-r--r--gson/src/test/java/com/google/gson/functional/StringTest.java140
-rw-r--r--gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java65
-rw-r--r--gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java176
-rw-r--r--gson/src/test/java/com/google/gson/functional/TypeAdapterPrecedenceTest.java149
-rw-r--r--gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java221
-rw-r--r--gson/src/test/java/com/google/gson/functional/TypeVariableTest.java140
-rw-r--r--gson/src/test/java/com/google/gson/functional/UncategorizedTest.java134
-rw-r--r--gson/src/test/java/com/google/gson/functional/VersioningTest.java170
-rw-r--r--gson/src/test/java/com/google/gson/internal/GsonTypesTest.java74
-rw-r--r--gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java32
-rw-r--r--gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java290
-rw-r--r--gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java150
-rw-r--r--gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java312
-rw-r--r--gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java175
-rw-r--r--gson/src/test/java/com/google/gson/metrics/PerformanceTest.java346
-rw-r--r--gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java82
-rw-r--r--gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java217
-rw-r--r--gson/src/test/java/com/google/gson/stream/JsonReaderTest.java1775
-rw-r--r--gson/src/test/java/com/google/gson/stream/JsonWriterTest.java579
154 files changed, 30719 insertions, 0 deletions
diff --git a/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java
new file mode 100644
index 00000000..aa253340
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/DefaultDateTypeAdapter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * This type adapter supports three subclasses of date: Date, Timestamp, and
+ * java.sql.Date.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+final class DefaultDateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
+
+ // TODO: migrate to streaming adapter
+
+ private final DateFormat enUsFormat;
+ private final DateFormat localFormat;
+ private final DateFormat iso8601Format;
+
+ DefaultDateTypeAdapter() {
+ this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US),
+ DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT));
+ }
+
+ DefaultDateTypeAdapter(String datePattern) {
+ this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern));
+ }
+
+ DefaultDateTypeAdapter(int style) {
+ this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style));
+ }
+
+ public DefaultDateTypeAdapter(int dateStyle, int timeStyle) {
+ this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US),
+ DateFormat.getDateTimeInstance(dateStyle, timeStyle));
+ }
+
+ DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) {
+ this.enUsFormat = enUsFormat;
+ this.localFormat = localFormat;
+ this.iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ // These methods need to be synchronized since JDK DateFormat classes are not thread-safe
+ // See issue 162
+ public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
+ synchronized (localFormat) {
+ String dateFormatAsString = enUsFormat.format(src);
+ return new JsonPrimitive(dateFormatAsString);
+ }
+ }
+
+ public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!(json instanceof JsonPrimitive)) {
+ throw new JsonParseException("The date should be a string value");
+ }
+ Date date = deserializeToDate(json);
+ if (typeOfT == Date.class) {
+ return date;
+ } else if (typeOfT == Timestamp.class) {
+ return new Timestamp(date.getTime());
+ } else if (typeOfT == java.sql.Date.class) {
+ return new java.sql.Date(date.getTime());
+ } else {
+ throw new IllegalArgumentException(getClass() + " cannot deserialize to " + typeOfT);
+ }
+ }
+
+ private Date deserializeToDate(JsonElement json) {
+ synchronized (localFormat) {
+ try {
+ return localFormat.parse(json.getAsString());
+ } catch (ParseException ignored) {
+ }
+ try {
+ return enUsFormat.parse(json.getAsString());
+ } catch (ParseException ignored) {
+ }
+ try {
+ return iso8601Format.parse(json.getAsString());
+ } catch (ParseException e) {
+ throw new JsonSyntaxException(json.getAsString(), e);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DefaultDateTypeAdapter.class.getSimpleName());
+ sb.append('(').append(localFormat.getClass().getSimpleName()).append(')');
+ return sb.toString();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/ExclusionStrategy.java b/gson/src/main/java/com/google/gson/ExclusionStrategy.java
new file mode 100644
index 00000000..6a3f43fb
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/ExclusionStrategy.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+/**
+ * A strategy (or policy) definition that is used to decide whether or not a field or top-level
+ * class should be serialized or deserialized as part of the JSON output/input. For serialization,
+ * if the {@link #shouldSkipClass(Class)} method returns true then that class or field type
+ * will not be part of the JSON output. For deserialization, if {@link #shouldSkipClass(Class)}
+ * returns true, then it will not be set as part of the Java object structure.
+ *
+ * <p>The following are a few examples that shows how you can use this exclusion mechanism.
+ *
+ * <p><strong>Exclude fields and objects based on a particular class type:</strong>
+ * <pre class="code">
+ * private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
+ * private final Class&lt;?&gt; excludedThisClass;
+ *
+ * public SpecificClassExclusionStrategy(Class&lt;?&gt; excludedThisClass) {
+ * this.excludedThisClass = excludedThisClass;
+ * }
+ *
+ * public boolean shouldSkipClass(Class&lt;?&gt; clazz) {
+ * return excludedThisClass.equals(clazz);
+ * }
+ *
+ * public boolean shouldSkipField(FieldAttributes f) {
+ * return excludedThisClass.equals(f.getDeclaredClass());
+ * }
+ * }
+ * </pre>
+ *
+ * <p><strong>Excludes fields and objects based on a particular annotation:</strong>
+ * <pre class="code">
+ * public &#64interface FooAnnotation {
+ * // some implementation here
+ * }
+ *
+ * // Excludes any field (or class) that is tagged with an "&#64FooAnnotation"
+ * private static class FooAnnotationExclusionStrategy implements ExclusionStrategy {
+ * public boolean shouldSkipClass(Class&lt;?&gt; clazz) {
+ * return clazz.getAnnotation(FooAnnotation.class) != null;
+ * }
+ *
+ * public boolean shouldSkipField(FieldAttributes f) {
+ * return f.getAnnotation(FooAnnotation.class) != null;
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then
+ * the {@code GsonBuilder} is required. The following is an example of how you can use the
+ * {@code GsonBuilder} to configure Gson to use one of the above sample:
+ * <pre class="code">
+ * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
+ * Gson gson = new GsonBuilder()
+ * .setExclusionStrategies(excludeStrings)
+ * .create();
+ * </pre>
+ *
+ * <p>For certain model classes, you may only want to serialize a field, but exclude it for
+ * deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal;
+ * however, you would register it with the
+ * {@link GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method.
+ * For example:
+ * <pre class="code">
+ * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
+ * Gson gson = new GsonBuilder()
+ * .addDeserializationExclusionStrategy(excludeStrings)
+ * .create();
+ * </pre>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ *
+ * @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...)
+ * @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)
+ * @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy)
+ *
+ * @since 1.4
+ */
+public interface ExclusionStrategy {
+
+ /**
+ * @param f the field object that is under test
+ * @return true if the field should be ignored; otherwise false
+ */
+ public boolean shouldSkipField(FieldAttributes f);
+
+ /**
+ * @param clazz the class object that is under test
+ * @return true if the class should be ignored; otherwise false
+ */
+ public boolean shouldSkipClass(Class<?> clazz);
+}
diff --git a/gson/src/main/java/com/google/gson/FieldAttributes.java b/gson/src/main/java/com/google/gson/FieldAttributes.java
new file mode 100644
index 00000000..cb89ff11
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/FieldAttributes.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import com.google.gson.internal.$Gson$Preconditions;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * A data object that stores attributes of a field.
+ *
+ * <p>This class is immutable; therefore, it can be safely shared across threads.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ *
+ * @since 1.4
+ */
+public final class FieldAttributes {
+ private final Field field;
+
+ /**
+ * Constructs a Field Attributes object from the {@code f}.
+ *
+ * @param f the field to pull attributes from
+ */
+ public FieldAttributes(Field f) {
+ $Gson$Preconditions.checkNotNull(f);
+ this.field = f;
+ }
+
+ /**
+ * @return the declaring class that contains this field
+ */
+ public Class<?> getDeclaringClass() {
+ return field.getDeclaringClass();
+ }
+
+ /**
+ * @return the name of the field
+ */
+ public String getName() {
+ return field.getName();
+ }
+
+ /**
+ * <p>For example, assume the following class definition:
+ * <pre class="code">
+ * public class Foo {
+ * private String bar;
+ * private List&lt;String&gt; red;
+ * }
+ *
+ * Type listParmeterizedType = new TypeToken&lt;List&lt;String&gt;&gt;() {}.getType();
+ * </pre>
+ *
+ * <p>This method would return {@code String.class} for the {@code bar} field and
+ * {@code listParameterizedType} for the {@code red} field.
+ *
+ * @return the specific type declared for this field
+ */
+ public Type getDeclaredType() {
+ return field.getGenericType();
+ }
+
+ /**
+ * Returns the {@code Class} object that was declared for this field.
+ *
+ * <p>For example, assume the following class definition:
+ * <pre class="code">
+ * public class Foo {
+ * private String bar;
+ * private List&lt;String&gt; red;
+ * }
+ * </pre>
+ *
+ * <p>This method would return {@code String.class} for the {@code bar} field and
+ * {@code List.class} for the {@code red} field.
+ *
+ * @return the specific class object that was declared for the field
+ */
+ public Class<?> getDeclaredClass() {
+ return field.getType();
+ }
+
+ /**
+ * Return the {@code T} annotation object from this field if it exist; otherwise returns
+ * {@code null}.
+ *
+ * @param annotation the class of the annotation that will be retrieved
+ * @return the annotation instance if it is bound to the field; otherwise {@code null}
+ */
+ public <T extends Annotation> T getAnnotation(Class<T> annotation) {
+ return field.getAnnotation(annotation);
+ }
+
+ /**
+ * Return the annotations that are present on this field.
+ *
+ * @return an array of all the annotations set on the field
+ * @since 1.4
+ */
+ public Collection<Annotation> getAnnotations() {
+ return Arrays.asList(field.getAnnotations());
+ }
+
+ /**
+ * Returns {@code true} if the field is defined with the {@code modifier}.
+ *
+ * <p>This method is meant to be called as:
+ * <pre class="code">
+ * boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
+ * </pre>
+ *
+ * @see java.lang.reflect.Modifier
+ */
+ public boolean hasModifier(int modifier) {
+ return (field.getModifiers() & modifier) != 0;
+ }
+
+ /**
+ * This is exposed internally only for the removing synthetic fields from the JSON output.
+ *
+ * @return true if the field is synthetic; otherwise false
+ * @throws IllegalAccessException
+ * @throws IllegalArgumentException
+ */
+ Object get(Object instance) throws IllegalAccessException {
+ return field.get(instance);
+ }
+
+ /**
+ * This is exposed internally only for the removing synthetic fields from the JSON output.
+ *
+ * @return true if the field is synthetic; otherwise false
+ */
+ boolean isSynthetic() {
+ return field.isSynthetic();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/FieldNamingPolicy.java b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java
new file mode 100644
index 00000000..6b4c72ca
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/FieldNamingPolicy.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Field;
+import java.util.Locale;
+
+/**
+ * An enumeration that defines a few standard naming conventions for JSON field names.
+ * This enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder}
+ * to configure a {@link com.google.gson.Gson} instance to properly translate Java field
+ * names into the desired JSON field names.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public enum FieldNamingPolicy implements FieldNamingStrategy {
+
+ /**
+ * Using this naming policy with Gson will ensure that the field name is
+ * unchanged.
+ */
+ IDENTITY() {
+ public String translateName(Field f) {
+ return f.getName();
+ }
+ },
+
+ /**
+ * Using this naming policy with Gson will ensure that the first "letter" of the Java
+ * field name is capitalized when serialized to its JSON form.
+ *
+ * <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
+ * <ul>
+ * <li>someFieldName ---> SomeFieldName</li>
+ * <li>_someFieldName ---> _SomeFieldName</li>
+ * </ul>
+ */
+ UPPER_CAMEL_CASE() {
+ public String translateName(Field f) {
+ return upperCaseFirstLetter(f.getName());
+ }
+ },
+
+ /**
+ * Using this naming policy with Gson will ensure that the first "letter" of the Java
+ * field name is capitalized when serialized to its JSON form and the words will be
+ * separated by a space.
+ *
+ * <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
+ * <ul>
+ * <li>someFieldName ---> Some Field Name</li>
+ * <li>_someFieldName ---> _Some Field Name</li>
+ * </ul>
+ *
+ * @since 1.4
+ */
+ UPPER_CAMEL_CASE_WITH_SPACES() {
+ public String translateName(Field f) {
+ return upperCaseFirstLetter(separateCamelCase(f.getName(), " "));
+ }
+ },
+
+ /**
+ * Using this naming policy with Gson will modify the Java Field name from its camel cased
+ * form to a lower case field name where each word is separated by an underscore (_).
+ *
+ * <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
+ * <ul>
+ * <li>someFieldName ---> some_field_name</li>
+ * <li>_someFieldName ---> _some_field_name</li>
+ * <li>aStringField ---> a_string_field</li>
+ * <li>aURL ---> a_u_r_l</li>
+ * </ul>
+ */
+ LOWER_CASE_WITH_UNDERSCORES() {
+ public String translateName(Field f) {
+ return separateCamelCase(f.getName(), "_").toLowerCase(Locale.ENGLISH);
+ }
+ },
+
+ /**
+ * Using this naming policy with Gson will modify the Java Field name from its camel cased
+ * form to a lower case field name where each word is separated by a dash (-).
+ *
+ * <p>Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":</p>
+ * <ul>
+ * <li>someFieldName ---> some-field-name</li>
+ * <li>_someFieldName ---> _some-field-name</li>
+ * <li>aStringField ---> a-string-field</li>
+ * <li>aURL ---> a-u-r-l</li>
+ * </ul>
+ * Using dashes in JavaScript is not recommended since dash is also used for a minus sign in
+ * expressions. This requires that a field named with dashes is always accessed as a quoted
+ * property like {@code myobject['my-field']}. Accessing it as an object field
+ * {@code myobject.my-field} will result in an unintended javascript expression.
+ * @since 1.4
+ */
+ LOWER_CASE_WITH_DASHES() {
+ public String translateName(Field f) {
+ return separateCamelCase(f.getName(), "-").toLowerCase(Locale.ENGLISH);
+ }
+ };
+
+ /**
+ * Converts the field name that uses camel-case define word separation into
+ * separate words that are separated by the provided {@code separatorString}.
+ */
+ private static String separateCamelCase(String name, String separator) {
+ StringBuilder translation = new StringBuilder();
+ for (int i = 0; i < name.length(); i++) {
+ char character = name.charAt(i);
+ if (Character.isUpperCase(character) && translation.length() != 0) {
+ translation.append(separator);
+ }
+ translation.append(character);
+ }
+ return translation.toString();
+ }
+
+ /**
+ * Ensures the JSON field names begins with an upper case letter.
+ */
+ private static String upperCaseFirstLetter(String name) {
+ StringBuilder fieldNameBuilder = new StringBuilder();
+ int index = 0;
+ char firstCharacter = name.charAt(index);
+
+ while (index < name.length() - 1) {
+ if (Character.isLetter(firstCharacter)) {
+ break;
+ }
+
+ fieldNameBuilder.append(firstCharacter);
+ firstCharacter = name.charAt(++index);
+ }
+
+ if (index == name.length()) {
+ return fieldNameBuilder.toString();
+ }
+
+ if (!Character.isUpperCase(firstCharacter)) {
+ String modifiedTarget = modifyString(Character.toUpperCase(firstCharacter), name, ++index);
+ return fieldNameBuilder.append(modifiedTarget).toString();
+ } else {
+ return name;
+ }
+ }
+
+ private static String modifyString(char firstCharacter, String srcString, int indexOfSubstring) {
+ return (indexOfSubstring < srcString.length())
+ ? firstCharacter + srcString.substring(indexOfSubstring)
+ : String.valueOf(firstCharacter);
+ }
+} \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/FieldNamingStrategy.java b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java
new file mode 100644
index 00000000..9be453ad
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/FieldNamingStrategy.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Field;
+
+/**
+ * A mechanism for providing custom field naming in Gson. This allows the client code to translate
+ * field names into a particular convention that is not supported as a normal Java field
+ * declaration rules. For example, Java does not support "-" characters in a field name.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @since 1.3
+ */
+public interface FieldNamingStrategy {
+
+ /**
+ * Translates the field name into its JSON field name representation.
+ *
+ * @param f the field object that we are translating
+ * @return the translated field name.
+ * @since 1.3
+ */
+ public String translateName(Field f);
+}
diff --git a/gson/src/main/java/com/google/gson/Gson.java b/gson/src/main/java/com/google/gson/Gson.java
new file mode 100644
index 00000000..d3b172a8
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/Gson.java
@@ -0,0 +1,916 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.internal.ConstructorConstructor;
+import com.google.gson.internal.Excluder;
+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.DateTypeAdapter;
+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.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.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import com.google.gson.stream.MalformedJsonException;
+
+/**
+ * This is the main class for using Gson. Gson is typically used by first constructing a
+ * Gson instance and then invoking {@link #toJson(Object)} or {@link #fromJson(String, Class)}
+ * methods on it. Gson instances are Thread-safe so you can reuse them freely across multiple
+ * threads.
+ *
+ * <p>You can create a Gson instance by invoking {@code new Gson()} if the default configuration
+ * is all you need. You can also use {@link GsonBuilder} to build a Gson instance with various
+ * configuration options such as versioning support, pretty printing, custom
+ * {@link JsonSerializer}s, {@link JsonDeserializer}s, and {@link InstanceCreator}s.</p>
+ *
+ * <p>Here is an example of how Gson is used for a simple Class:
+ *
+ * <pre>
+ * Gson gson = new Gson(); // Or use new GsonBuilder().create();
+ * MyType target = new MyType();
+ * String json = gson.toJson(target); // serializes target to Json
+ * MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
+ * </pre></p>
+ *
+ * <p>If the object that your are serializing/deserializing is a {@code ParameterizedType}
+ * (i.e. contains at least one type parameter and may be an array) then you must use the
+ * {@link #toJson(Object, Type)} or {@link #fromJson(String, Type)} method. Here is an
+ * example for serializing and deserialing a {@code ParameterizedType}:
+ *
+ * <pre>
+ * Type listType = new TypeToken&lt;List&lt;String&gt;&gt;() {}.getType();
+ * List&lt;String&gt; target = new LinkedList&lt;String&gt;();
+ * target.add("blah");
+ *
+ * Gson gson = new Gson();
+ * String json = gson.toJson(target, listType);
+ * List&lt;String&gt; target2 = gson.fromJson(json, listType);
+ * </pre></p>
+ *
+ * <p>See the <a href="https://sites.google.com/site/gson/gson-user-guide">Gson User Guide</a>
+ * for a more complete set of examples.</p>
+ *
+ * @see com.google.gson.reflect.TypeToken
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @author Jesse Wilson
+ */
+public final class Gson {
+ static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
+
+ private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
+
+ /**
+ * This thread local guards against reentrant calls to getAdapter(). In
+ * certain object graphs, creating an adapter for a type may recursively
+ * require an adapter for the same type! Without intervention, the recursive
+ * lookup would stack overflow. We cheat by returning a proxy type adapter.
+ * The proxy is wired up once the initial adapter has been created.
+ */
+ private final ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>> calls
+ = new ThreadLocal<Map<TypeToken<?>, FutureTypeAdapter<?>>>();
+
+ private final Map<TypeToken<?>, TypeAdapter<?>> typeTokenCache
+ = Collections.synchronizedMap(new HashMap<TypeToken<?>, TypeAdapter<?>>());
+
+ private final List<TypeAdapterFactory> factories;
+ private final ConstructorConstructor constructorConstructor;
+
+ private final boolean serializeNulls;
+ private final boolean htmlSafe;
+ private final boolean generateNonExecutableJson;
+ private final boolean prettyPrinting;
+
+ final JsonDeserializationContext deserializationContext = new JsonDeserializationContext() {
+ @SuppressWarnings("unchecked")
+ public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException {
+ return (T) fromJson(json, typeOfT);
+ }
+ };
+
+ final JsonSerializationContext serializationContext = new JsonSerializationContext() {
+ public JsonElement serialize(Object src) {
+ return toJsonTree(src);
+ }
+ public JsonElement serialize(Object src, Type typeOfSrc) {
+ return toJsonTree(src, typeOfSrc);
+ }
+ };
+
+ /**
+ * Constructs a Gson object with default configuration. The default configuration has the
+ * following settings:
+ * <ul>
+ * <li>The JSON generated by <code>toJson</code> methods is in compact representation. This
+ * means that all the unneeded white-space is removed. You can change this behavior with
+ * {@link GsonBuilder#setPrettyPrinting()}. </li>
+ * <li>The generated JSON omits all the fields that are null. Note that nulls in arrays are
+ * kept as is since an array is an ordered list. Moreover, if a field is not null, but its
+ * generated JSON is empty, the field is kept. You can configure Gson to serialize null values
+ * by setting {@link GsonBuilder#serializeNulls()}.</li>
+ * <li>Gson provides default serialization and deserialization for Enums, {@link Map},
+ * {@link java.net.URL}, {@link java.net.URI}, {@link java.util.Locale}, {@link java.util.Date},
+ * {@link java.math.BigDecimal}, and {@link java.math.BigInteger} classes. If you would prefer
+ * to change the default representation, you can do so by registering a type adapter through
+ * {@link GsonBuilder#registerTypeAdapter(Type, Object)}. </li>
+ * <li>The default Date format is same as {@link java.text.DateFormat#DEFAULT}. This format
+ * ignores the millisecond portion of the date during serialization. You can change
+ * this by invoking {@link GsonBuilder#setDateFormat(int)} or
+ * {@link GsonBuilder#setDateFormat(String)}. </li>
+ * <li>By default, Gson ignores the {@link com.google.gson.annotations.Expose} annotation.
+ * You can enable Gson to serialize/deserialize only those fields marked with this annotation
+ * through {@link GsonBuilder#excludeFieldsWithoutExposeAnnotation()}. </li>
+ * <li>By default, Gson ignores the {@link com.google.gson.annotations.Since} annotation. You
+ * can enable Gson to use this annotation through {@link GsonBuilder#setVersion(double)}.</li>
+ * <li>The default field naming policy for the output Json is same as in Java. So, a Java class
+ * field <code>versionNumber</code> will be output as <code>&quot;versionNumber&quot;</code> in
+ * Json. The same rules are applied for mapping incoming Json to the Java classes. You can
+ * change this policy through {@link GsonBuilder#setFieldNamingPolicy(FieldNamingPolicy)}.</li>
+ * <li>By default, Gson excludes <code>transient</code> or <code>static</code> fields from
+ * consideration for serialization and deserialization. You can change this behavior through
+ * {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.</li>
+ * </ul>
+ */
+ public Gson() {
+ this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY,
+ Collections.<Type, InstanceCreator<?>>emptyMap(), false, false, DEFAULT_JSON_NON_EXECUTABLE,
+ true, false, false, LongSerializationPolicy.DEFAULT,
+ Collections.<TypeAdapterFactory>emptyList());
+ }
+
+ Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy,
+ final Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
+ boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
+ boolean prettyPrinting, boolean serializeSpecialFloatingPointValues,
+ LongSerializationPolicy longSerializationPolicy,
+ List<TypeAdapterFactory> typeAdapterFactories) {
+ this.constructorConstructor = new ConstructorConstructor(instanceCreators);
+ this.serializeNulls = serializeNulls;
+ this.generateNonExecutableJson = generateNonExecutableGson;
+ this.htmlSafe = htmlSafe;
+ this.prettyPrinting = prettyPrinting;
+
+ List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
+
+ // built-in type adapters that cannot be overridden
+ factories.add(TypeAdapters.JSON_ELEMENT_FACTORY);
+ factories.add(ObjectTypeAdapter.FACTORY);
+
+ // the excluder must precede all adapters that handle user-defined types
+ factories.add(excluder);
+
+ // user's type adapters
+ factories.addAll(typeAdapterFactories);
+
+ // 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);
+ factories.add(TypeAdapters.newFactory(long.class, Long.class,
+ longAdapter(longSerializationPolicy)));
+ factories.add(TypeAdapters.newFactory(double.class, Double.class,
+ doubleAdapter(serializeSpecialFloatingPointValues)));
+ factories.add(TypeAdapters.newFactory(float.class, Float.class,
+ floatAdapter(serializeSpecialFloatingPointValues)));
+ factories.add(TypeAdapters.NUMBER_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));
+ factories.add(TypeAdapters.URL_FACTORY);
+ factories.add(TypeAdapters.URI_FACTORY);
+ factories.add(TypeAdapters.UUID_FACTORY);
+ factories.add(TypeAdapters.LOCALE_FACTORY);
+ factories.add(TypeAdapters.INET_ADDRESS_FACTORY);
+ 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);
+ 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(new JsonAdapterAnnotationTypeAdapterFactory(constructorConstructor));
+ factories.add(TypeAdapters.ENUM_FACTORY);
+ factories.add(new ReflectiveTypeAdapterFactory(
+ constructorConstructor, fieldNamingPolicy, excluder));
+
+ this.factories = Collections.unmodifiableList(factories);
+ }
+
+ private TypeAdapter<Number> doubleAdapter(boolean serializeSpecialFloatingPointValues) {
+ if (serializeSpecialFloatingPointValues) {
+ return TypeAdapters.DOUBLE;
+ }
+ return new TypeAdapter<Number>() {
+ @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(value);
+ }
+ };
+ }
+
+ private TypeAdapter<Number> floatAdapter(boolean serializeSpecialFloatingPointValues) {
+ if (serializeSpecialFloatingPointValues) {
+ return TypeAdapters.FLOAT;
+ }
+ return new TypeAdapter<Number>() {
+ @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);
+ out.value(value);
+ }
+ };
+ }
+
+ private 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 TypeAdapter<Number> longAdapter(LongSerializationPolicy longSerializationPolicy) {
+ if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) {
+ return TypeAdapters.LONG;
+ }
+ return new TypeAdapter<Number>() {
+ @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());
+ }
+ };
+ }
+
+ /**
+ * Returns the type adapter for {@code} type.
+ *
+ * @throws IllegalArgumentException if this GSON cannot serialize and
+ * deserialize {@code type}.
+ */
+ @SuppressWarnings("unchecked")
+ public <T> TypeAdapter<T> getAdapter(TypeToken<T> type) {
+ TypeAdapter<?> cached = typeTokenCache.get(type);
+ if (cached != null) {
+ return (TypeAdapter<T>) cached;
+ }
+
+ Map<TypeToken<?>, FutureTypeAdapter<?>> threadCalls = calls.get();
+ boolean requiresThreadLocalCleanup = false;
+ if (threadCalls == null) {
+ threadCalls = new HashMap<TypeToken<?>, FutureTypeAdapter<?>>();
+ calls.set(threadCalls);
+ requiresThreadLocalCleanup = true;
+ }
+
+ // the key and value type parameters always agree
+ FutureTypeAdapter<T> ongoingCall = (FutureTypeAdapter<T>) threadCalls.get(type);
+ if (ongoingCall != null) {
+ return ongoingCall;
+ }
+
+ try {
+ FutureTypeAdapter<T> call = new FutureTypeAdapter<T>();
+ threadCalls.put(type, call);
+
+ for (TypeAdapterFactory factory : factories) {
+ TypeAdapter<T> candidate = factory.create(this, type);
+ if (candidate != null) {
+ call.setDelegate(candidate);
+ typeTokenCache.put(type, candidate);
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException("GSON cannot handle " + type);
+ } finally {
+ threadCalls.remove(type);
+
+ if (requiresThreadLocalCleanup) {
+ calls.remove();
+ }
+ }
+ }
+
+ /**
+ * This method is used to get an alternate type adapter for the specified type. This is used
+ * to access a type adapter that is overridden by a {@link TypeAdapterFactory} that you
+ * may have registered. This features is typically used when you want to register a type
+ * adapter that does a little bit of work but then delegates further processing to the Gson
+ * default type adapter. Here is an example:
+ * <p>Let's say we want to write a type adapter that counts the number of objects being read
+ * from or written to JSON. We can achieve this by writing a type adapter factory that uses
+ * the <code>getDelegateAdapter</code> method:
+ * <pre> {@code
+ * class StatsTypeAdapterFactory implements TypeAdapterFactory {
+ * public int numReads = 0;
+ * public int numWrites = 0;
+ * public &lt;T&gt; TypeAdapter&lt;T&gt; create(Gson gson, TypeToken&lt;T&gt; type) {
+ * final TypeAdapter&lt;T&gt; delegate = gson.getDelegateAdapter(this, type);
+ * return new TypeAdapter&lt;T&gt;() {
+ * public void write(JsonWriter out, T value) throws IOException {
+ * ++numWrites;
+ * delegate.write(out, value);
+ * }
+ * public T read(JsonReader in) throws IOException {
+ * ++numReads;
+ * return delegate.read(in);
+ * }
+ * };
+ * }
+ * }
+ * } </pre>
+ * This factory can now be used like this:
+ * <pre> {@code
+ * StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
+ * Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
+ * // Call gson.toJson() and fromJson methods on objects
+ * System.out.println("Num JSON reads" + stats.numReads);
+ * System.out.println("Num JSON writes" + stats.numWrites);
+ * }</pre>
+ * Note that this call will skip all factories registered before {@code skipPast}. In case of
+ * multiple TypeAdapterFactories registered it is up to the caller of this function to insure
+ * that the order of registration does not prevent this method from reaching a factory they
+ * would expect to reply from this call.
+ * Note that since you can not override type adapter factories for String and Java primitive
+ * types, our stats factory will not count the number of String or primitives that will be
+ * read or written.
+ * @param skipPast The type adapter factory that needs to be skipped while searching for
+ * a matching type adapter. In most cases, you should just pass <i>this</i> (the type adapter
+ * factory from where {@link #getDelegateAdapter} method is being invoked).
+ * @param type Type for which the delegate adapter is being searched for.
+ *
+ * @since 2.2
+ */
+ public <T> TypeAdapter<T> getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken<T> type) {
+ boolean skipPastFound = false;
+ // Skip past if and only if the specified factory is present in the factories.
+ // This is useful because the factories created through JsonAdapter annotations are not
+ // registered in this list.
+ if (!factories.contains(skipPast)) skipPastFound = true;
+
+ for (TypeAdapterFactory factory : factories) {
+ if (!skipPastFound) {
+ if (factory == skipPast) {
+ skipPastFound = true;
+ }
+ continue;
+ }
+
+ TypeAdapter<T> candidate = factory.create(this, type);
+ if (candidate != null) {
+ return candidate;
+ }
+ }
+ throw new IllegalArgumentException("GSON cannot serialize " + type);
+ }
+
+ /**
+ * Returns the type adapter for {@code} type.
+ *
+ * @throws IllegalArgumentException if this GSON cannot serialize and
+ * deserialize {@code type}.
+ */
+ public <T> TypeAdapter<T> getAdapter(Class<T> type) {
+ return getAdapter(TypeToken.get(type));
+ }
+
+ /**
+ * This method serializes the specified object into its equivalent representation as a tree of
+ * {@link JsonElement}s. This method should be used when the specified object is not a generic
+ * type. This method uses {@link Class#getClass()} to get the type for the specified object, but
+ * the {@code getClass()} loses the generic type information because of the Type Erasure feature
+ * of Java. Note that this method works fine if the any of the object fields are of generic type,
+ * just the object itself should not be of a generic type. If the object is of generic type, use
+ * {@link #toJsonTree(Object, Type)} instead.
+ *
+ * @param src the object for which Json representation is to be created setting for Gson
+ * @return Json representation of {@code src}.
+ * @since 1.4
+ */
+ public JsonElement toJsonTree(Object src) {
+ if (src == null) {
+ return JsonNull.INSTANCE;
+ }
+ return toJsonTree(src, src.getClass());
+ }
+
+ /**
+ * This method serializes the specified object, including those of generic types, into its
+ * equivalent representation as a tree of {@link JsonElement}s. This method must be used if the
+ * specified object is a generic type. For non-generic objects, use {@link #toJsonTree(Object)}
+ * instead.
+ *
+ * @param src the object for which JSON representation is to be created
+ * @param typeOfSrc The specific genericized type of src. You can obtain
+ * this type by using the {@link com.google.gson.reflect.TypeToken} class. For example,
+ * to get the type for {@code Collection<Foo>}, you should use:
+ * <pre>
+ * Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
+ * </pre>
+ * @return Json representation of {@code src}
+ * @since 1.4
+ */
+ public JsonElement toJsonTree(Object src, Type typeOfSrc) {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ toJson(src, typeOfSrc, writer);
+ return writer.get();
+ }
+
+ /**
+ * This method serializes the specified object into its equivalent Json representation.
+ * This method should be used when the specified object is not a generic type. This method uses
+ * {@link Class#getClass()} to get the type for the specified object, but the
+ * {@code getClass()} loses the generic type information because of the Type Erasure feature
+ * of Java. Note that this method works fine if the any of the object fields are of generic type,
+ * just the object itself should not be of a generic type. If the object is of generic type, use
+ * {@link #toJson(Object, Type)} instead. If you want to write out the object to a
+ * {@link Writer}, use {@link #toJson(Object, Appendable)} instead.
+ *
+ * @param src the object for which Json representation is to be created setting for Gson
+ * @return Json representation of {@code src}.
+ */
+ public String toJson(Object src) {
+ if (src == null) {
+ return toJson(JsonNull.INSTANCE);
+ }
+ return toJson(src, src.getClass());
+ }
+
+ /**
+ * This method serializes the specified object, including those of generic types, into its
+ * equivalent Json representation. This method must be used if the specified object is a generic
+ * type. For non-generic objects, use {@link #toJson(Object)} instead. If you want to write out
+ * the object to a {@link Appendable}, use {@link #toJson(Object, Type, Appendable)} instead.
+ *
+ * @param src the object for which JSON representation is to be created
+ * @param typeOfSrc The specific genericized type of src. You can obtain
+ * this type by using the {@link com.google.gson.reflect.TypeToken} class. For example,
+ * to get the type for {@code Collection<Foo>}, you should use:
+ * <pre>
+ * Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
+ * </pre>
+ * @return Json representation of {@code src}
+ */
+ public String toJson(Object src, Type typeOfSrc) {
+ StringWriter writer = new StringWriter();
+ toJson(src, typeOfSrc, writer);
+ return writer.toString();
+ }
+
+ /**
+ * This method serializes the specified object into its equivalent Json representation.
+ * This method should be used when the specified object is not a generic type. This method uses
+ * {@link Class#getClass()} to get the type for the specified object, but the
+ * {@code getClass()} loses the generic type information because of the Type Erasure feature
+ * of Java. Note that this method works fine if the any of the object fields are of generic type,
+ * just the object itself should not be of a generic type. If the object is of generic type, use
+ * {@link #toJson(Object, Type, Appendable)} instead.
+ *
+ * @param src the object for which Json representation is to be created setting for Gson
+ * @param writer Writer to which the Json representation needs to be written
+ * @throws JsonIOException if there was a problem writing to the writer
+ * @since 1.2
+ */
+ public void toJson(Object src, Appendable writer) throws JsonIOException {
+ if (src != null) {
+ toJson(src, src.getClass(), writer);
+ } else {
+ toJson(JsonNull.INSTANCE, writer);
+ }
+ }
+
+ /**
+ * This method serializes the specified object, including those of generic types, into its
+ * equivalent Json representation. This method must be used if the specified object is a generic
+ * type. For non-generic objects, use {@link #toJson(Object, Appendable)} instead.
+ *
+ * @param src the object for which JSON representation is to be created
+ * @param typeOfSrc The specific genericized type of src. You can obtain
+ * this type by using the {@link com.google.gson.reflect.TypeToken} class. For example,
+ * to get the type for {@code Collection<Foo>}, you should use:
+ * <pre>
+ * Type typeOfSrc = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
+ * </pre>
+ * @param writer Writer to which the Json representation of src needs to be written.
+ * @throws JsonIOException if there was a problem writing to the writer
+ * @since 1.2
+ */
+ public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException {
+ try {
+ JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
+ toJson(src, typeOfSrc, jsonWriter);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+
+ /**
+ * Writes the JSON representation of {@code src} of type {@code typeOfSrc} to
+ * {@code writer}.
+ * @throws JsonIOException if there was a problem writing to the writer
+ */
+ @SuppressWarnings("unchecked")
+ public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException {
+ TypeAdapter<?> adapter = getAdapter(TypeToken.get(typeOfSrc));
+ boolean oldLenient = writer.isLenient();
+ writer.setLenient(true);
+ boolean oldHtmlSafe = writer.isHtmlSafe();
+ writer.setHtmlSafe(htmlSafe);
+ boolean oldSerializeNulls = writer.getSerializeNulls();
+ writer.setSerializeNulls(serializeNulls);
+ try {
+ ((TypeAdapter<Object>) adapter).write(writer, src);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ } finally {
+ writer.setLenient(oldLenient);
+ writer.setHtmlSafe(oldHtmlSafe);
+ writer.setSerializeNulls(oldSerializeNulls);
+ }
+ }
+
+ /**
+ * Converts a tree of {@link JsonElement}s into its equivalent JSON representation.
+ *
+ * @param jsonElement root of a tree of {@link JsonElement}s
+ * @return JSON String representation of the tree
+ * @since 1.4
+ */
+ public String toJson(JsonElement jsonElement) {
+ StringWriter writer = new StringWriter();
+ toJson(jsonElement, writer);
+ return writer.toString();
+ }
+
+ /**
+ * Writes out the equivalent JSON for a tree of {@link JsonElement}s.
+ *
+ * @param jsonElement root of a tree of {@link JsonElement}s
+ * @param writer Writer to which the Json representation needs to be written
+ * @throws JsonIOException if there was a problem writing to the writer
+ * @since 1.4
+ */
+ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOException {
+ try {
+ JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer));
+ toJson(jsonElement, jsonWriter);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Returns a new JSON writer configured for the settings on this Gson instance.
+ */
+ public JsonWriter newJsonWriter(Writer writer) throws IOException {
+ if (generateNonExecutableJson) {
+ writer.write(JSON_NON_EXECUTABLE_PREFIX);
+ }
+ JsonWriter jsonWriter = new JsonWriter(writer);
+ if (prettyPrinting) {
+ jsonWriter.setIndent(" ");
+ }
+ jsonWriter.setSerializeNulls(serializeNulls);
+ return jsonWriter;
+ }
+
+ /**
+ * Writes the JSON for {@code jsonElement} to {@code writer}.
+ * @throws JsonIOException if there was a problem writing to the writer
+ */
+ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException {
+ boolean oldLenient = writer.isLenient();
+ writer.setLenient(true);
+ boolean oldHtmlSafe = writer.isHtmlSafe();
+ writer.setHtmlSafe(htmlSafe);
+ boolean oldSerializeNulls = writer.getSerializeNulls();
+ writer.setSerializeNulls(serializeNulls);
+ try {
+ Streams.write(jsonElement, writer);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ } finally {
+ writer.setLenient(oldLenient);
+ writer.setHtmlSafe(oldHtmlSafe);
+ writer.setSerializeNulls(oldSerializeNulls);
+ }
+ }
+
+ /**
+ * This method deserializes the specified Json into an object of the specified class. It is not
+ * suitable to use if the specified class is a generic type since it will not have the generic
+ * type information because of the Type Erasure feature of Java. Therefore, this method should not
+ * be used if the desired type is a generic type. Note that this method works fine if the any of
+ * the fields of the specified object are generics, just the object itself should not be a
+ * generic type. For the cases when the object is of generic type, invoke
+ * {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of
+ * a String, use {@link #fromJson(Reader, Class)} instead.
+ *
+ * @param <T> the type of the desired object
+ * @param json the string from which the object is to be deserialized
+ * @param classOfT the class of T
+ * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type
+ * classOfT
+ */
+ public <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
+ Object object = fromJson(json, (Type) classOfT);
+ return Primitives.wrap(classOfT).cast(object);
+ }
+
+ /**
+ * This method deserializes the specified Json into an object of the specified type. This method
+ * is useful if the specified object is a generic type. For non-generic objects, use
+ * {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of
+ * a String, use {@link #fromJson(Reader, Type)} instead.
+ *
+ * @param <T> the type of the desired object
+ * @param json the string from which the object is to be deserialized
+ * @param typeOfT The specific genericized type of src. You can obtain this type by using the
+ * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
+ * {@code Collection<Foo>}, you should use:
+ * <pre>
+ * Type typeOfT = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
+ * </pre>
+ * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null}.
+ * @throws JsonParseException if json is not a valid representation for an object of type typeOfT
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T fromJson(String json, Type typeOfT) throws JsonSyntaxException {
+ if (json == null) {
+ return null;
+ }
+ StringReader reader = new StringReader(json);
+ T target = (T) fromJson(reader, typeOfT);
+ return target;
+ }
+
+ /**
+ * This method deserializes the Json read from the specified reader into an object of the
+ * specified class. It is not suitable to use if the specified class is a generic type since it
+ * will not have the generic type information because of the Type Erasure feature of Java.
+ * Therefore, this method should not be used if the desired type is a generic type. Note that
+ * this method works fine if the any of the fields of the specified object are generics, just the
+ * object itself should not be a generic type. For the cases when the object is of generic type,
+ * invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a
+ * {@link Reader}, use {@link #fromJson(String, Class)} instead.
+ *
+ * @param <T> the type of the desired object
+ * @param json the reader producing the Json from which the object is to be deserialized.
+ * @param classOfT the class of T
+ * @return an object of type T from the string. Returns {@code null} if {@code json} is at EOF.
+ * @throws JsonIOException if there was a problem reading from the Reader
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type
+ * @since 1.2
+ */
+ public <T> T fromJson(Reader json, Class<T> classOfT) throws JsonSyntaxException, JsonIOException {
+ JsonReader jsonReader = new JsonReader(json);
+ Object object = fromJson(jsonReader, classOfT);
+ assertFullConsumption(object, jsonReader);
+ return Primitives.wrap(classOfT).cast(object);
+ }
+
+ /**
+ * This method deserializes the Json read from the specified reader into an object of the
+ * specified type. This method is useful if the specified object is a generic type. For
+ * non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a
+ * String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead.
+ *
+ * @param <T> the type of the desired object
+ * @param json the reader producing Json from which the object is to be deserialized
+ * @param typeOfT The specific genericized type of src. You can obtain this type by using the
+ * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
+ * {@code Collection<Foo>}, you should use:
+ * <pre>
+ * Type typeOfT = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
+ * </pre>
+ * @return an object of type T from the json. Returns {@code null} if {@code json} is at EOF.
+ * @throws JsonIOException if there was a problem reading from the Reader
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type
+ * @since 1.2
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException {
+ JsonReader jsonReader = new JsonReader(json);
+ T object = (T) fromJson(jsonReader, typeOfT);
+ assertFullConsumption(object, jsonReader);
+ return object;
+ }
+
+ private static void assertFullConsumption(Object obj, JsonReader reader) {
+ try {
+ if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) {
+ throw new JsonIOException("JSON document was not fully consumed.");
+ }
+ } catch (MalformedJsonException e) {
+ throw new JsonSyntaxException(e);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+
+ /**
+ * Reads the next JSON value from {@code reader} and convert it to an object
+ * of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF.
+ * Since Type is not parameterized by T, this method is type unsafe and should be used carefully
+ *
+ * @throws JsonIOException if there was a problem writing to the Reader
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException {
+ boolean isEmpty = true;
+ boolean oldLenient = reader.isLenient();
+ reader.setLenient(true);
+ try {
+ reader.peek();
+ isEmpty = false;
+ TypeToken<T> typeToken = (TypeToken<T>) TypeToken.get(typeOfT);
+ TypeAdapter<T> typeAdapter = getAdapter(typeToken);
+ T object = typeAdapter.read(reader);
+ return object;
+ } catch (EOFException e) {
+ /*
+ * For compatibility with JSON 1.5 and earlier, we return null for empty
+ * documents instead of throwing.
+ */
+ if (isEmpty) {
+ return null;
+ }
+ throw new JsonSyntaxException(e);
+ } catch (IllegalStateException e) {
+ throw new JsonSyntaxException(e);
+ } catch (IOException e) {
+ // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException
+ throw new JsonSyntaxException(e);
+ } finally {
+ reader.setLenient(oldLenient);
+ }
+ }
+
+ /**
+ * This method deserializes the Json read from the specified parse tree into an object of the
+ * specified type. It is not suitable to use if the specified class is a generic type since it
+ * will not have the generic type information because of the Type Erasure feature of Java.
+ * Therefore, this method should not be used if the desired type is a generic type. Note that
+ * this method works fine if the any of the fields of the specified object are generics, just the
+ * object itself should not be a generic type. For the cases when the object is of generic type,
+ * invoke {@link #fromJson(JsonElement, Type)}.
+ * @param <T> the type of the desired object
+ * @param json the root of the parse tree of {@link JsonElement}s from which the object is to
+ * be deserialized
+ * @param classOfT The class of T
+ * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null}.
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT
+ * @since 1.3
+ */
+ public <T> T fromJson(JsonElement json, Class<T> classOfT) throws JsonSyntaxException {
+ Object object = fromJson(json, (Type) classOfT);
+ return Primitives.wrap(classOfT).cast(object);
+ }
+
+ /**
+ * This method deserializes the Json read from the specified parse tree into an object of the
+ * specified type. This method is useful if the specified object is a generic type. For
+ * non-generic objects, use {@link #fromJson(JsonElement, Class)} instead.
+ *
+ * @param <T> the type of the desired object
+ * @param json the root of the parse tree of {@link JsonElement}s from which the object is to
+ * be deserialized
+ * @param typeOfT The specific genericized type of src. You can obtain this type by using the
+ * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for
+ * {@code Collection<Foo>}, you should use:
+ * <pre>
+ * Type typeOfT = new TypeToken&lt;Collection&lt;Foo&gt;&gt;(){}.getType();
+ * </pre>
+ * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null}.
+ * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT
+ * @since 1.3
+ */
+ @SuppressWarnings("unchecked")
+ public <T> T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException {
+ if (json == null) {
+ return null;
+ }
+ return (T) fromJson(new JsonTreeReader(json), typeOfT);
+ }
+
+ static class FutureTypeAdapter<T> extends TypeAdapter<T> {
+ private TypeAdapter<T> delegate;
+
+ public void setDelegate(TypeAdapter<T> typeAdapter) {
+ if (delegate != null) {
+ throw new AssertionError();
+ }
+ delegate = typeAdapter;
+ }
+
+ @Override public T read(JsonReader in) throws IOException {
+ if (delegate == null) {
+ throw new IllegalStateException();
+ }
+ return delegate.read(in);
+ }
+
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ if (delegate == null) {
+ throw new IllegalStateException();
+ }
+ delegate.write(out, value);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder("{serializeNulls:")
+ .append(serializeNulls)
+ .append("factories:").append(factories)
+ .append(",instanceCreators:").append(constructorConstructor)
+ .append("}")
+ .toString();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/GsonBuilder.java b/gson/src/main/java/com/google/gson/GsonBuilder.java
new file mode 100644
index 00000000..e6c0b8c0
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/GsonBuilder.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.internal.$Gson$Preconditions;
+import com.google.gson.internal.Excluder;
+import com.google.gson.internal.bind.TypeAdapters;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * <p>Use this builder to construct a {@link Gson} instance when you need to set configuration
+ * options other than the default. For {@link Gson} with default configuration, it is simpler to
+ * use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its
+ * various configuration methods, and finally calling create.</p>
+ *
+ * <p>The following is an example shows how to use the {@code GsonBuilder} to construct a Gson
+ * instance:
+ *
+ * <pre>
+ * Gson gson = new GsonBuilder()
+ * .registerTypeAdapter(Id.class, new IdTypeAdapter())
+ * .enableComplexMapKeySerialization()
+ * .serializeNulls()
+ * .setDateFormat(DateFormat.LONG)
+ * .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
+ * .setPrettyPrinting()
+ * .setVersion(1.0)
+ * .create();
+ * </pre></p>
+ *
+ * <p>NOTES:
+ * <ul>
+ * <li> the order of invocation of configuration methods does not matter.</li>
+ * <li> The default serialization of {@link Date} and its subclasses in Gson does
+ * not contain time-zone information. So, if you are using date/time instances,
+ * use {@code GsonBuilder} and its {@code setDateFormat} methods.</li>
+ * </ul>
+ * </p>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @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<Type, InstanceCreator<?>> instanceCreators
+ = new HashMap<Type, InstanceCreator<?>>();
+ private final List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
+ /** tree-style hierarchy factories. These come after factories for backwards compatibility. */
+ private final List<TypeAdapterFactory> hierarchyFactories = new ArrayList<TypeAdapterFactory>();
+ private boolean serializeNulls;
+ private String datePattern;
+ private int dateStyle = DateFormat.DEFAULT;
+ private int timeStyle = DateFormat.DEFAULT;
+ private boolean complexMapKeySerialization;
+ private boolean serializeSpecialFloatingPointValues;
+ private boolean escapeHtmlChars = true;
+ private boolean prettyPrinting;
+ private boolean generateNonExecutableJson;
+
+ /**
+ * Creates a GsonBuilder instance that can be used to build Gson with various configuration
+ * settings. GsonBuilder follows the builder pattern, and it is typically used by first
+ * invoking various configuration methods to set desired options, and finally calling
+ * {@link #create()}.
+ */
+ public GsonBuilder() {
+ }
+
+ /**
+ * Configures Gson to enable versioning support.
+ *
+ * @param ignoreVersionsAfter any field or type marked with a version higher than this value
+ * are ignored during serialization or deserialization.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public GsonBuilder setVersion(double ignoreVersionsAfter) {
+ excluder = excluder.withVersion(ignoreVersionsAfter);
+ return this;
+ }
+
+ /**
+ * Configures Gson to excludes all class fields that have the specified modifiers. By default,
+ * Gson will exclude all fields marked transient or static. This method will override that
+ * behavior.
+ *
+ * @param modifiers the field modifiers. You must use the modifiers specified in the
+ * {@link java.lang.reflect.Modifier} class. For example,
+ * {@link java.lang.reflect.Modifier#TRANSIENT},
+ * {@link java.lang.reflect.Modifier#STATIC}.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public GsonBuilder excludeFieldsWithModifiers(int... modifiers) {
+ excluder = excluder.withModifiers(modifiers);
+ return this;
+ }
+
+ /**
+ * Makes the output JSON non-executable in Javascript by prefixing the generated JSON with some
+ * special text. This prevents attacks from third-party sites through script sourcing. See
+ * <a href="http://code.google.com/p/google-gson/issues/detail?id=42">Gson Issue 42</a>
+ * for details.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public GsonBuilder generateNonExecutableJson() {
+ this.generateNonExecutableJson = true;
+ return this;
+ }
+
+ /**
+ * Configures Gson to exclude all fields from consideration for serialization or deserialization
+ * that do not have the {@link com.google.gson.annotations.Expose} annotation.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public GsonBuilder excludeFieldsWithoutExposeAnnotation() {
+ excluder = excluder.excludeFieldsWithoutExposeAnnotation();
+ return this;
+ }
+
+ /**
+ * Configure Gson to serialize null fields. By default, Gson omits all fields that are null
+ * during serialization.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public GsonBuilder serializeNulls() {
+ this.serializeNulls = true;
+ return this;
+ }
+
+ /**
+ * Enabling this feature will only change the serialized form if the map key is
+ * a complex type (i.e. non-primitive) in its <strong>serialized</strong> JSON
+ * form. The default implementation of map serialization uses {@code toString()}
+ * on the key; however, when this is called then one of the following cases
+ * apply:
+ *
+ * <h3>Maps as JSON objects</h3>
+ * For this case, assume that a type adapter is registered to serialize and
+ * deserialize some {@code Point} class, which contains an x and y coordinate,
+ * to/from the JSON Primitive string value {@code "(x,y)"}. The Java map would
+ * then be serialized as a {@link JsonObject}.
+ *
+ * <p>Below is an example:
+ * <pre> {@code
+ * Gson gson = new GsonBuilder()
+ * .register(Point.class, new MyPointTypeAdapter())
+ * .enableComplexMapKeySerialization()
+ * .create();
+ *
+ * Map<Point, String> original = new LinkedHashMap<Point, String>();
+ * original.put(new Point(5, 6), "a");
+ * original.put(new Point(8, 8), "b");
+ * System.out.println(gson.toJson(original, type));
+ * }</pre>
+ * The above code prints this JSON object:<pre> {@code
+ * {
+ * "(5,6)": "a",
+ * "(8,8)": "b"
+ * }
+ * }</pre>
+ *
+ * <h3>Maps as JSON arrays</h3>
+ * For this case, assume that a type adapter was NOT registered for some
+ * {@code Point} class, but rather the default Gson serialization is applied.
+ * In this case, some {@code new Point(2,3)} would serialize as {@code
+ * {"x":2,"y":5}}.
+ *
+ * <p>Given the assumption above, a {@code Map<Point, String>} will be
+ * serialize as an array of arrays (can be viewed as an entry set of pairs).
+ *
+ * <p>Below is an example of serializing complex types as JSON arrays:
+ * <pre> {@code
+ * Gson gson = new GsonBuilder()
+ * .enableComplexMapKeySerialization()
+ * .create();
+ *
+ * Map<Point, String> original = new LinkedHashMap<Point, String>();
+ * original.put(new Point(5, 6), "a");
+ * original.put(new Point(8, 8), "b");
+ * System.out.println(gson.toJson(original, type));
+ * }
+ *
+ * The JSON output would look as follows:
+ * <pre> {@code
+ * [
+ * [
+ * {
+ * "x": 5,
+ * "y": 6
+ * },
+ * "a"
+ * ],
+ * [
+ * {
+ * "x": 8,
+ * "y": 8
+ * },
+ * "b"
+ * ]
+ * ]
+ * }</pre>
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.7
+ */
+ public GsonBuilder enableComplexMapKeySerialization() {
+ complexMapKeySerialization = true;
+ return this;
+ }
+
+ /**
+ * Configures Gson to exclude inner classes during serialization.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public GsonBuilder disableInnerClassSerialization() {
+ excluder = excluder.disableInnerClassSerialization();
+ return this;
+ }
+
+ /**
+ * Configures Gson to apply a specific serialization policy for {@code Long} and {@code long}
+ * objects.
+ *
+ * @param serializationPolicy the particular policy to use for serializing longs.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public GsonBuilder setLongSerializationPolicy(LongSerializationPolicy serializationPolicy) {
+ this.longSerializationPolicy = serializationPolicy;
+ return this;
+ }
+
+ /**
+ * Configures Gson to apply a specific naming policy to an object's field during serialization
+ * and deserialization.
+ *
+ * @param namingConvention the JSON field naming convention to use for serialization and
+ * deserialization.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public GsonBuilder setFieldNamingPolicy(FieldNamingPolicy namingConvention) {
+ this.fieldNamingPolicy = namingConvention;
+ return this;
+ }
+
+ /**
+ * Configures Gson to apply a specific naming policy strategy to an object's field during
+ * serialization and deserialization.
+ *
+ * @param fieldNamingStrategy the actual naming strategy to apply to the fields
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public GsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) {
+ this.fieldNamingPolicy = fieldNamingStrategy;
+ return this;
+ }
+
+ /**
+ * Configures Gson to apply a set of exclusion strategies during both serialization and
+ * deserialization. Each of the {@code strategies} will be applied as a disjunction rule.
+ * This means that if one of the {@code strategies} suggests that a field (or class) should be
+ * skipped then that field (or object) is skipped during serializaiton/deserialization.
+ *
+ * @param strategies the set of strategy object to apply during object (de)serialization.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.4
+ */
+ public GsonBuilder setExclusionStrategies(ExclusionStrategy... strategies) {
+ for (ExclusionStrategy strategy : strategies) {
+ excluder = excluder.withExclusionStrategy(strategy, true, true);
+ }
+ return this;
+ }
+
+ /**
+ * Configures Gson to apply the passed in exclusion strategy during serialization.
+ * If this method is invoked numerous times with different exclusion strategy objects
+ * then the exclusion strategies that were added will be applied as a disjunction rule.
+ * This means that if one of the added exclusion strategies suggests that a field (or
+ * class) should be skipped then that field (or object) is skipped during its
+ * serialization.
+ *
+ * @param strategy an exclusion strategy to apply during serialization.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.7
+ */
+ public GsonBuilder addSerializationExclusionStrategy(ExclusionStrategy strategy) {
+ excluder = excluder.withExclusionStrategy(strategy, true, false);
+ return this;
+ }
+
+ /**
+ * Configures Gson to apply the passed in exclusion strategy during deserialization.
+ * If this method is invoked numerous times with different exclusion strategy objects
+ * then the exclusion strategies that were added will be applied as a disjunction rule.
+ * This means that if one of the added exclusion strategies suggests that a field (or
+ * class) should be skipped then that field (or object) is skipped during its
+ * deserialization.
+ *
+ * @param strategy an exclusion strategy to apply during deserialization.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.7
+ */
+ public GsonBuilder addDeserializationExclusionStrategy(ExclusionStrategy strategy) {
+ excluder = excluder.withExclusionStrategy(strategy, false, true);
+ return this;
+ }
+
+ /**
+ * Configures Gson to output Json that fits in a page for pretty printing. This option only
+ * affects Json serialization.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public GsonBuilder setPrettyPrinting() {
+ prettyPrinting = true;
+ return this;
+ }
+
+ /**
+ * By default, Gson escapes HTML characters such as &lt; &gt; etc. Use this option to configure
+ * Gson to pass-through HTML characters as is.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public GsonBuilder disableHtmlEscaping() {
+ this.escapeHtmlChars = false;
+ return this;
+ }
+
+ /**
+ * Configures Gson to serialize {@code Date} objects according to the pattern provided. You can
+ * 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>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
+ * valid date and time patterns.</p>
+ *
+ * @param pattern the pattern that dates will be serialized/deserialized to/from
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public GsonBuilder setDateFormat(String pattern) {
+ // TODO(Joel): Make this fail fast if it is an invalid date format
+ this.datePattern = pattern;
+ return this;
+ }
+
+ /**
+ * Configures Gson to to serialize {@code Date} objects according to the style value provided.
+ * You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
+ * invocation will be used to decide the serialization format.
+ *
+ * <p>Note that this style value should be one of the predefined constants in the
+ * {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
+ * information on the valid style constants.</p>
+ *
+ * @param style the predefined date style that date objects will be serialized/deserialized
+ * to/from
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public GsonBuilder setDateFormat(int style) {
+ this.dateStyle = style;
+ this.datePattern = null;
+ return this;
+ }
+
+ /**
+ * Configures Gson to to serialize {@code Date} objects according to the style value provided.
+ * You can call this method or {@link #setDateFormat(String)} multiple times, but only the last
+ * invocation will be used to decide the serialization format.
+ *
+ * <p>Note that this style value should be one of the predefined constants in the
+ * {@code DateFormat} class. See the documentation in {@link java.text.DateFormat} for more
+ * information on the valid style constants.</p>
+ *
+ * @param dateStyle the predefined date style that date objects will be serialized/deserialized
+ * to/from
+ * @param timeStyle the predefined style for the time portion of the date objects
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public GsonBuilder setDateFormat(int dateStyle, int timeStyle) {
+ this.dateStyle = dateStyle;
+ this.timeStyle = timeStyle;
+ this.datePattern = null;
+ return this;
+ }
+
+ /**
+ * Configures Gson for custom serialization or deserialization. This method combines the
+ * registration of an {@link TypeAdapter}, {@link InstanceCreator}, {@link JsonSerializer}, and a
+ * {@link JsonDeserializer}. It is best used when a single object {@code typeAdapter} implements
+ * all the required interfaces for custom serialization with Gson. If a type adapter was
+ * previously registered for the specified {@code type}, it is overwritten.
+ *
+ * <p>This registers the type specified and no other types: you must manually register related
+ * types! For example, applications registering {@code boolean.class} should also register {@code
+ * Boolean.class}.
+ *
+ * @param type the type definition for the type adapter being registered
+ * @param typeAdapter This object must implement at least one of the {@link TypeAdapter},
+ * {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
+ $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
+ || typeAdapter instanceof JsonDeserializer<?>
+ || typeAdapter instanceof InstanceCreator<?>
+ || typeAdapter instanceof TypeAdapter<?>);
+ if (typeAdapter instanceof InstanceCreator<?>) {
+ instanceCreators.put(type, (InstanceCreator) typeAdapter);
+ }
+ if (typeAdapter instanceof JsonSerializer<?> || typeAdapter instanceof JsonDeserializer<?>) {
+ TypeToken<?> typeToken = TypeToken.get(type);
+ factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter));
+ }
+ if (typeAdapter instanceof TypeAdapter<?>) {
+ factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter));
+ }
+ return this;
+ }
+
+ /**
+ * Register a factory for type adapters. Registering a factory is useful when the type
+ * adapter needs to be configured based on the type of the field being processed. Gson
+ * is designed to handle a large number of factories, so you should consider registering
+ * them to be at par with registering an individual type adapter.
+ *
+ * @since 2.1
+ */
+ public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) {
+ factories.add(factory);
+ return this;
+ }
+
+ /**
+ * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
+ * This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and
+ * a {@link JsonDeserializer}. If a type adapter was previously registered for the specified
+ * type hierarchy, it is overridden. If a type adapter is registered for a specific type in
+ * the type hierarchy, it will be invoked instead of the one registered for the type hierarchy.
+ *
+ * @param baseType the class definition for the type adapter being registered for the base class
+ * or interface
+ * @param typeAdapter This object must implement at least one of {@link TypeAdapter},
+ * {@link JsonSerializer} or {@link JsonDeserializer} interfaces.
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.7
+ */
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public GsonBuilder registerTypeHierarchyAdapter(Class<?> baseType, Object typeAdapter) {
+ $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer<?>
+ || typeAdapter instanceof JsonDeserializer<?>
+ || typeAdapter instanceof TypeAdapter<?>);
+ if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) {
+ hierarchyFactories.add(0,
+ TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter));
+ }
+ if (typeAdapter instanceof TypeAdapter<?>) {
+ factories.add(TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter));
+ }
+ return this;
+ }
+
+ /**
+ * Section 2.4 of <a href="http://www.ietf.org/rfc/rfc4627.txt">JSON specification</a> disallows
+ * special double values (NaN, Infinity, -Infinity). However,
+ * <a href="http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf">Javascript
+ * specification</a> (see section 4.3.20, 4.3.22, 4.3.23) allows these values as valid Javascript
+ * values. Moreover, most JavaScript engines will accept these special values in JSON without
+ * problem. So, at a practical level, it makes sense to accept these values as valid JSON even
+ * though JSON specification disallows them.
+ *
+ * <p>Gson always accepts these special values during deserialization. However, it outputs
+ * strictly compliant JSON. Hence, if it encounters a float value {@link Float#NaN},
+ * {@link Float#POSITIVE_INFINITY}, {@link Float#NEGATIVE_INFINITY}, or a double value
+ * {@link Double#NaN}, {@link Double#POSITIVE_INFINITY}, {@link Double#NEGATIVE_INFINITY}, it
+ * will throw an {@link IllegalArgumentException}. This method provides a way to override the
+ * default behavior when you know that the JSON receiver will be able to handle these special
+ * values.
+ *
+ * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public GsonBuilder serializeSpecialFloatingPointValues() {
+ this.serializeSpecialFloatingPointValues = true;
+ return this;
+ }
+
+ /**
+ * Creates a {@link Gson} instance based on the current configuration. This method is free of
+ * side-effects to this {@code GsonBuilder} instance and hence can be called multiple times.
+ *
+ * @return an instance of Gson configured with the options currently set in this builder
+ */
+ public Gson create() {
+ List<TypeAdapterFactory> factories = new ArrayList<TypeAdapterFactory>();
+ factories.addAll(this.factories);
+ Collections.reverse(factories);
+ factories.addAll(this.hierarchyFactories);
+ addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories);
+
+ return new Gson(excluder, fieldNamingPolicy, instanceCreators,
+ serializeNulls, complexMapKeySerialization,
+ generateNonExecutableJson, escapeHtmlChars, prettyPrinting,
+ serializeSpecialFloatingPointValues, longSerializationPolicy, factories);
+ }
+
+ private void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
+ List<TypeAdapterFactory> factories) {
+ DefaultDateTypeAdapter dateTypeAdapter;
+ if (datePattern != null && !"".equals(datePattern.trim())) {
+ dateTypeAdapter = new DefaultDateTypeAdapter(datePattern);
+ } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
+ dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle);
+ } else {
+ return;
+ }
+
+ factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Date.class), dateTypeAdapter));
+ factories.add(TreeTypeAdapter.newFactory(TypeToken.get(Timestamp.class), dateTypeAdapter));
+ factories.add(TreeTypeAdapter.newFactory(TypeToken.get(java.sql.Date.class), dateTypeAdapter));
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/InstanceCreator.java b/gson/src/main/java/com/google/gson/InstanceCreator.java
new file mode 100644
index 00000000..d5096a07
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/InstanceCreator.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+
+/**
+ * This interface is implemented to create instances of a class that does not define a no-args
+ * constructor. If you can modify the class, you should instead add a private, or public
+ * no-args constructor. However, that is not possible for library classes, such as JDK classes, or
+ * a third-party library that you do not have source-code of. In such cases, you should define an
+ * instance creator for the class. Implementations of this interface should be registered with
+ * {@link GsonBuilder#registerTypeAdapter(Type, Object)} method before Gson will be able to use
+ * them.
+ * <p>Let us look at an example where defining an InstanceCreator might be useful. The
+ * {@code Id} class defined below does not have a default no-args constructor.</p>
+ *
+ * <pre>
+ * public class Id&lt;T&gt; {
+ * private final Class&lt;T&gt; clazz;
+ * private final long value;
+ * public Id(Class&lt;T&gt; clazz, long value) {
+ * this.clazz = clazz;
+ * this.value = value;
+ * }
+ * }
+ * </pre>
+ *
+ * <p>If Gson encounters an object of type {@code Id} during deserialization, it will throw an
+ * exception. The easiest way to solve this problem will be to add a (public or private) no-args
+ * constructor as follows:</p>
+ *
+ * <pre>
+ * private Id() {
+ * this(Object.class, 0L);
+ * }
+ * </pre>
+ *
+ * <p>However, let us assume that the developer does not have access to the source-code of the
+ * {@code Id} class, or does not want to define a no-args constructor for it. The developer
+ * can solve this problem by defining an {@code InstanceCreator} for {@code Id}:</p>
+ *
+ * <pre>
+ * class IdInstanceCreator implements InstanceCreator&lt;Id&gt; {
+ * public Id createInstance(Type type) {
+ * return new Id(Object.class, 0L);
+ * }
+ * }
+ * </pre>
+ *
+ * <p>Note that it does not matter what the fields of the created instance contain since Gson will
+ * overwrite them with the deserialized values specified in Json. You should also ensure that a
+ * <i>new</i> object is returned, not a common object since its fields will be overwritten.
+ * The developer will need to register {@code IdInstanceCreator} with Gson as follows:</p>
+ *
+ * <pre>
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
+ * </pre>
+ *
+ * @param <T> the type of object that will be created by this implementation.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public interface InstanceCreator<T> {
+
+ /**
+ * Gson invokes this call-back method during deserialization to create an instance of the
+ * specified type. The fields of the returned instance are overwritten with the data present
+ * in the Json. Since the prior contents of the object are destroyed and overwritten, do not
+ * return an instance that is useful elsewhere. In particular, do not return a common instance,
+ * always use {@code new} to create a new instance.
+ *
+ * @param type the parameterized T represented as a {@link Type}.
+ * @return a default object instance of type T.
+ */
+ public T createInstance(Type type);
+}
diff --git a/gson/src/main/java/com/google/gson/JsonArray.java b/gson/src/main/java/com/google/gson/JsonArray.java
new file mode 100644
index 00000000..c664a5e1
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonArray.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A class representing an array type in Json. An array is a list of {@link JsonElement}s each of
+ * which can be of a different type. This is an ordered list, meaning that the order in which
+ * elements are added is preserved.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class JsonArray extends JsonElement implements Iterable<JsonElement> {
+ private final List<JsonElement> elements;
+
+ /**
+ * Creates an empty JsonArray.
+ */
+ public JsonArray() {
+ elements = new ArrayList<JsonElement>();
+ }
+
+ @Override
+ JsonArray deepCopy() {
+ JsonArray result = new JsonArray();
+ for (JsonElement element : elements) {
+ result.add(element.deepCopy());
+ }
+ return result;
+ }
+
+ /**
+ * Adds the specified boolean to self.
+ *
+ * @param bool the boolean that needs to be added to the array.
+ */
+ public void add(Boolean bool) {
+ elements.add(bool == null ? JsonNull.INSTANCE : new JsonPrimitive(bool));
+ }
+
+ /**
+ * Adds the specified character to self.
+ *
+ * @param character the character that needs to be added to the array.
+ */
+ public void add(Character character) {
+ elements.add(character == null ? JsonNull.INSTANCE : new JsonPrimitive(character));
+ }
+
+ /**
+ * Adds the specified number to self.
+ *
+ * @param number the number that needs to be added to the array.
+ */
+ public void add(Number number) {
+ elements.add(number == null ? JsonNull.INSTANCE : new JsonPrimitive(number));
+ }
+
+ /**
+ * Adds the specified string to self.
+ *
+ * @param string the string that needs to be added to the array.
+ */
+ public void add(String string) {
+ elements.add(string == null ? JsonNull.INSTANCE : new JsonPrimitive(string));
+ }
+
+ /**
+ * Adds the specified element to self.
+ *
+ * @param element the element that needs to be added to the array.
+ */
+ public void add(JsonElement element) {
+ if (element == null) {
+ element = JsonNull.INSTANCE;
+ }
+ elements.add(element);
+ }
+
+ /**
+ * Adds all the elements of the specified array to self.
+ *
+ * @param array the array whose elements need to be added to the array.
+ */
+ public void addAll(JsonArray array) {
+ elements.addAll(array.elements);
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the specified element.
+ * Element can be null.
+ * @param index index of the element to replace
+ * @param element element to be stored at the specified position
+ * @return the element previously at the specified position
+ * @throws IndexOutOfBoundsException if the specified index is outside the array bounds
+ */
+ public JsonElement set(int index, JsonElement element) {
+ return elements.set(index, element);
+ }
+
+ /**
+ * Removes the first occurrence of the specified element from this array, if it is present.
+ * If the array does not contain the element, it is unchanged.
+ * @param element element to be removed from this array, if present
+ * @return true if this array contained the specified element, false otherwise
+ * @since 2.3
+ */
+ public boolean remove(JsonElement element) {
+ return elements.remove(element);
+ }
+
+ /**
+ * Removes the element at the specified position in this array. Shifts any subsequent elements
+ * to the left (subtracts one from their indices). Returns the element that was removed from
+ * the array.
+ * @param index index the index of the element to be removed
+ * @return the element previously at the specified position
+ * @throws IndexOutOfBoundsException if the specified index is outside the array bounds
+ * @since 2.3
+ */
+ public JsonElement remove(int index) {
+ return elements.remove(index);
+ }
+
+ /**
+ * Returns true if this array contains the specified element.
+ * @return true if this array contains the specified element.
+ * @param element whose presence in this array is to be tested
+ * @since 2.3
+ */
+ public boolean contains(JsonElement element) {
+ return elements.contains(element);
+ }
+
+ /**
+ * Returns the number of elements in the array.
+ *
+ * @return the number of elements in the array.
+ */
+ public int size() {
+ return elements.size();
+ }
+
+ /**
+ * Returns an iterator to navigate the elemetns of the array. Since the array is an ordered list,
+ * the iterator navigates the elements in the order they were inserted.
+ *
+ * @return an iterator to navigate the elements of the array.
+ */
+ public Iterator<JsonElement> iterator() {
+ return elements.iterator();
+ }
+
+ /**
+ * Returns the ith element of the array.
+ *
+ * @param i the index of the element that is being sought.
+ * @return the element present at the ith index.
+ * @throws IndexOutOfBoundsException if i is negative or greater than or equal to the
+ * {@link #size()} of the array.
+ */
+ public JsonElement get(int i) {
+ return elements.get(i);
+ }
+
+ /**
+ * convenience method to get this array as a {@link Number} if it contains a single element.
+ *
+ * @return get this element as a number if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid Number.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public Number getAsNumber() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsNumber();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a {@link String} if it contains a single element.
+ *
+ * @return get this element as a String if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid String.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public String getAsString() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsString();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a double if it contains a single element.
+ *
+ * @return get this element as a double if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid double.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public double getAsDouble() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsDouble();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a {@link BigDecimal} if it contains a single element.
+ *
+ * @return get this element as a {@link BigDecimal} if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}.
+ * @throws NumberFormatException if the element at index 0 is not a valid {@link BigDecimal}.
+ * @throws IllegalStateException if the array has more than one element.
+ * @since 1.2
+ */
+ @Override
+ public BigDecimal getAsBigDecimal() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsBigDecimal();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a {@link BigInteger} if it contains a single element.
+ *
+ * @return get this element as a {@link BigInteger} if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive}.
+ * @throws NumberFormatException if the element at index 0 is not a valid {@link BigInteger}.
+ * @throws IllegalStateException if the array has more than one element.
+ * @since 1.2
+ */
+ @Override
+ public BigInteger getAsBigInteger() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsBigInteger();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a float if it contains a single element.
+ *
+ * @return get this element as a float if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid float.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public float getAsFloat() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsFloat();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a long if it contains a single element.
+ *
+ * @return get this element as a long if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid long.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public long getAsLong() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsLong();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as an integer if it contains a single element.
+ *
+ * @return get this element as an integer if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid integer.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public int getAsInt() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsInt();
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public byte getAsByte() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsByte();
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public char getAsCharacter() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsCharacter();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a primitive short if it contains a single element.
+ *
+ * @return get this element as a primitive short if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid short.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public short getAsShort() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsShort();
+ }
+ throw new IllegalStateException();
+ }
+
+ /**
+ * convenience method to get this array as a boolean if it contains a single element.
+ *
+ * @return get this element as a boolean if it is single element array.
+ * @throws ClassCastException if the element in the array is of not a {@link JsonPrimitive} and
+ * is not a valid boolean.
+ * @throws IllegalStateException if the array has more than one element.
+ */
+ @Override
+ public boolean getAsBoolean() {
+ if (elements.size() == 1) {
+ return elements.get(0).getAsBoolean();
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o == this) || (o instanceof JsonArray && ((JsonArray) o).elements.equals(elements));
+ }
+
+ @Override
+ public int hashCode() {
+ return elements.hashCode();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonDeserializationContext.java b/gson/src/main/java/com/google/gson/JsonDeserializationContext.java
new file mode 100644
index 00000000..00c75054
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonDeserializationContext.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+
+/**
+ * Context for deserialization that is passed to a custom deserializer during invocation of its
+ * {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)}
+ * method.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public interface JsonDeserializationContext {
+
+ /**
+ * Invokes default deserialization on the specified object. It should never be invoked on
+ * the element received as a parameter of the
+ * {@link JsonDeserializer#deserialize(JsonElement, Type, JsonDeserializationContext)} method. Doing
+ * so will result in an infinite loop since Gson will in-turn call the custom deserializer again.
+ *
+ * @param json the parse tree.
+ * @param typeOfT type of the expected return value.
+ * @param <T> The type of the deserialized object.
+ * @return An object of type typeOfT.
+ * @throws JsonParseException if the parse tree does not contain expected data.
+ */
+ public <T> T deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
+} \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/JsonDeserializer.java b/gson/src/main/java/com/google/gson/JsonDeserializer.java
new file mode 100644
index 00000000..0589eb28
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonDeserializer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+
+/**
+ * <p>Interface representing a custom deserializer for Json. You should write a custom
+ * deserializer, if you are not happy with the default deserialization done by Gson. You will
+ * also need to register this deserializer through
+ * {@link GsonBuilder#registerTypeAdapter(Type, Object)}.</p>
+ *
+ * <p>Let us look at example where defining a deserializer will be useful. The {@code Id} class
+ * defined below has two fields: {@code clazz} and {@code value}.</p>
+ *
+ * <pre>
+ * public class Id&lt;T&gt; {
+ * private final Class&lt;T&gt; clazz;
+ * private final long value;
+ * public Id(Class&lt;T&gt; clazz, long value) {
+ * this.clazz = clazz;
+ * this.value = value;
+ * }
+ * public long getValue() {
+ * return value;
+ * }
+ * }
+ * </pre>
+ *
+ * <p>The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the
+ * Json string to be <code>{"clazz":com.foo.MyObject,"value":20}</code>. Suppose, you already know
+ * the type of the field that the {@code Id} will be deserialized into, and hence just want to
+ * deserialize it from a Json string {@code 20}. You can achieve that by writing a custom
+ * deserializer:</p>
+ *
+ * <pre>
+ * class IdDeserializer implements JsonDeserializer&lt;Id&gt;() {
+ * public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ * throws JsonParseException {
+ * return new Id((Class)typeOfT, id.getValue());
+ * }
+ * </pre>
+ *
+ * <p>You will also need to register {@code IdDeserializer} with Gson as follows:</p>
+ *
+ * <pre>
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
+ * </pre>
+ *
+ * <p>New applications should prefer {@link TypeAdapter}, whose streaming API
+ * is more efficient than this interface's tree API.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ *
+ * @param <T> type for which the deserializer is being registered. It is possible that a
+ * deserializer may be asked to deserialize a specific generic type of the T.
+ */
+public interface JsonDeserializer<T> {
+
+ /**
+ * Gson invokes this call-back method during deserialization when it encounters a field of the
+ * specified type.
+ * <p>In the implementation of this call-back method, you should consider invoking
+ * {@link JsonDeserializationContext#deserialize(JsonElement, Type)} method to create objects
+ * for any non-trivial field of the returned object. However, you should never invoke it on the
+ * the same type passing {@code json} since that will cause an infinite loop (Gson will call your
+ * call-back method again).
+ *
+ * @param json The Json data being deserialized
+ * @param typeOfT The type of the Object to deserialize to
+ * @return a deserialized object of the specified type typeOfT which is a subclass of {@code T}
+ * @throws JsonParseException if json is not in the expected format of {@code typeofT}
+ */
+ public T deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException;
+}
diff --git a/gson/src/main/java/com/google/gson/JsonElement.java b/gson/src/main/java/com/google/gson/JsonElement.java
new file mode 100644
index 00000000..d9cd9184
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonElement.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * A class representing an element of Json. It could either be a {@link JsonObject}, a
+ * {@link JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public abstract class JsonElement {
+ /**
+ * Returns a deep copy of this element. Immutable elements like primitives
+ * and nulls are not copied.
+ */
+ abstract JsonElement deepCopy();
+
+ /**
+ * provides check for verifying if this element is an array or not.
+ *
+ * @return true if this element is of type {@link JsonArray}, false otherwise.
+ */
+ public boolean isJsonArray() {
+ return this instanceof JsonArray;
+ }
+
+ /**
+ * provides check for verifying if this element is a Json object or not.
+ *
+ * @return true if this element is of type {@link JsonObject}, false otherwise.
+ */
+ public boolean isJsonObject() {
+ return this instanceof JsonObject;
+ }
+
+ /**
+ * provides check for verifying if this element is a primitive or not.
+ *
+ * @return true if this element is of type {@link JsonPrimitive}, false otherwise.
+ */
+ public boolean isJsonPrimitive() {
+ return this instanceof JsonPrimitive;
+ }
+
+ /**
+ * provides check for verifying if this element represents a null value or not.
+ *
+ * @return true if this element is of type {@link JsonNull}, false otherwise.
+ * @since 1.2
+ */
+ public boolean isJsonNull() {
+ return this instanceof JsonNull;
+ }
+
+ /**
+ * convenience method to get this element as a {@link JsonObject}. If the element is of some
+ * other type, a {@link IllegalStateException} will result. Hence it is best to use this method
+ * after ensuring that this element is of the desired type by calling {@link #isJsonObject()}
+ * first.
+ *
+ * @return get this element as a {@link JsonObject}.
+ * @throws IllegalStateException if the element is of another type.
+ */
+ public JsonObject getAsJsonObject() {
+ if (isJsonObject()) {
+ return (JsonObject) this;
+ }
+ throw new IllegalStateException("Not a JSON Object: " + this);
+ }
+
+ /**
+ * convenience method to get this element as a {@link JsonArray}. If the element is of some
+ * other type, a {@link IllegalStateException} will result. Hence it is best to use this method
+ * after ensuring that this element is of the desired type by calling {@link #isJsonArray()}
+ * first.
+ *
+ * @return get this element as a {@link JsonArray}.
+ * @throws IllegalStateException if the element is of another type.
+ */
+ public JsonArray getAsJsonArray() {
+ if (isJsonArray()) {
+ return (JsonArray) this;
+ }
+ throw new IllegalStateException("This is not a JSON Array.");
+ }
+
+ /**
+ * convenience method to get this element as a {@link JsonPrimitive}. If the element is of some
+ * other type, a {@link IllegalStateException} will result. Hence it is best to use this method
+ * after ensuring that this element is of the desired type by calling {@link #isJsonPrimitive()}
+ * first.
+ *
+ * @return get this element as a {@link JsonPrimitive}.
+ * @throws IllegalStateException if the element is of another type.
+ */
+ public JsonPrimitive getAsJsonPrimitive() {
+ if (isJsonPrimitive()) {
+ return (JsonPrimitive) this;
+ }
+ throw new IllegalStateException("This is not a JSON Primitive.");
+ }
+
+ /**
+ * convenience method to get this element as a {@link JsonNull}. If the element is of some
+ * other type, a {@link IllegalStateException} will result. Hence it is best to use this method
+ * after ensuring that this element is of the desired type by calling {@link #isJsonNull()}
+ * first.
+ *
+ * @return get this element as a {@link JsonNull}.
+ * @throws IllegalStateException if the element is of another type.
+ * @since 1.2
+ */
+ public JsonNull getAsJsonNull() {
+ if (isJsonNull()) {
+ return (JsonNull) this;
+ }
+ throw new IllegalStateException("This is not a JSON Null.");
+ }
+
+ /**
+ * convenience method to get this element as a boolean value.
+ *
+ * @return get this element as a primitive boolean value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * boolean value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public boolean getAsBoolean() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a {@link Boolean} value.
+ *
+ * @return get this element as a {@link Boolean} value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * boolean value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ Boolean getAsBooleanWrapper() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a {@link Number}.
+ *
+ * @return get this element as a {@link Number}.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * number.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public Number getAsNumber() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a string value.
+ *
+ * @return get this element as a string value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * string value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public String getAsString() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive double value.
+ *
+ * @return get this element as a primitive double value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * double value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public double getAsDouble() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive float value.
+ *
+ * @return get this element as a primitive float value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * float value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public float getAsFloat() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive long value.
+ *
+ * @return get this element as a primitive long value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * long value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public long getAsLong() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive integer value.
+ *
+ * @return get this element as a primitive integer value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * integer value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public int getAsInt() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive byte value.
+ *
+ * @return get this element as a primitive byte value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * byte value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ * @since 1.3
+ */
+ public byte getAsByte() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive character value.
+ *
+ * @return get this element as a primitive char value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * char value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ * @since 1.3
+ */
+ public char getAsCharacter() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a {@link BigDecimal}.
+ *
+ * @return get this element as a {@link BigDecimal}.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive}.
+ * * @throws NumberFormatException if the element is not a valid {@link BigDecimal}.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ * @since 1.2
+ */
+ public BigDecimal getAsBigDecimal() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a {@link BigInteger}.
+ *
+ * @return get this element as a {@link BigInteger}.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive}.
+ * @throws NumberFormatException if the element is not a valid {@link BigInteger}.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ * @since 1.2
+ */
+ public BigInteger getAsBigInteger() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * convenience method to get this element as a primitive short value.
+ *
+ * @return get this element as a primitive short value.
+ * @throws ClassCastException if the element is of not a {@link JsonPrimitive} and is not a valid
+ * short value.
+ * @throws IllegalStateException if the element is of the type {@link JsonArray} but contains
+ * more than a single element.
+ */
+ public short getAsShort() {
+ throw new UnsupportedOperationException(getClass().getSimpleName());
+ }
+
+ /**
+ * Returns a String representation of this element.
+ */
+ @Override
+ public String toString() {
+ try {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setLenient(true);
+ Streams.write(this, jsonWriter);
+ return stringWriter.toString();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonIOException.java b/gson/src/main/java/com/google/gson/JsonIOException.java
new file mode 100644
index 00000000..dfeccd8e
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonIOException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+/**
+ * This exception is raised when Gson was unable to read an input stream
+ * or write to one.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class JsonIOException extends JsonParseException {
+ private static final long serialVersionUID = 1L;
+
+ public JsonIOException(String msg) {
+ super(msg);
+ }
+
+ public JsonIOException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ /**
+ * Creates exception with the specified cause. Consider using
+ * {@link #JsonIOException(String, Throwable)} instead if you can describe what happened.
+ *
+ * @param cause root exception that caused this exception to be thrown.
+ */
+ public JsonIOException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonNull.java b/gson/src/main/java/com/google/gson/JsonNull.java
new file mode 100755
index 00000000..56883369
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonNull.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+/**
+ * A class representing a Json {@code null} value.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @since 1.2
+ */
+public final class JsonNull extends JsonElement {
+ /**
+ * singleton for JsonNull
+ *
+ * @since 1.8
+ */
+ public static final JsonNull INSTANCE = new JsonNull();
+
+ /**
+ * Creates a new JsonNull object.
+ * Deprecated since Gson version 1.8. Use {@link #INSTANCE} instead
+ */
+ @Deprecated
+ public JsonNull() {
+ // Do nothing
+ }
+
+ @Override
+ JsonNull deepCopy() {
+ return INSTANCE;
+ }
+
+ /**
+ * All instances of JsonNull have the same hash code since they are indistinguishable
+ */
+ @Override
+ public int hashCode() {
+ return JsonNull.class.hashCode();
+ }
+
+ /**
+ * All instances of JsonNull are the same
+ */
+ @Override
+ public boolean equals(Object other) {
+ return this == other || other instanceof JsonNull;
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonObject.java b/gson/src/main/java/com/google/gson/JsonObject.java
new file mode 100644
index 00000000..78c7a177
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonObject.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.LinkedTreeMap;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class representing an object type in Json. An object consists of name-value pairs where names
+ * are strings, and values are any other type of {@link JsonElement}. This allows for a creating a
+ * tree of JsonElements. The member elements of this object are maintained in order they were added.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class JsonObject extends JsonElement {
+ private final LinkedTreeMap<String, JsonElement> members =
+ new LinkedTreeMap<String, JsonElement>();
+
+ @Override
+ JsonObject deepCopy() {
+ JsonObject result = new JsonObject();
+ for (Map.Entry<String, JsonElement> entry : members.entrySet()) {
+ result.add(entry.getKey(), entry.getValue().deepCopy());
+ }
+ return result;
+ }
+
+ /**
+ * Adds a member, which is a name-value pair, to self. The name must be a String, but the value
+ * can be an arbitrary JsonElement, thereby allowing you to build a full tree of JsonElements
+ * rooted at this node.
+ *
+ * @param property name of the member.
+ * @param value the member object.
+ */
+ public void add(String property, JsonElement value) {
+ if (value == null) {
+ value = JsonNull.INSTANCE;
+ }
+ members.put(property, value);
+ }
+
+ /**
+ * Removes the {@code property} from this {@link JsonObject}.
+ *
+ * @param property name of the member that should be removed.
+ * @return the {@link JsonElement} object that is being removed.
+ * @since 1.3
+ */
+ public JsonElement remove(String property) {
+ return members.remove(property);
+ }
+
+ /**
+ * Convenience method to add a primitive member. The specified value is converted to a
+ * JsonPrimitive of String.
+ *
+ * @param property name of the member.
+ * @param value the string value associated with the member.
+ */
+ public void addProperty(String property, String value) {
+ add(property, createJsonElement(value));
+ }
+
+ /**
+ * Convenience method to add a primitive member. The specified value is converted to a
+ * JsonPrimitive of Number.
+ *
+ * @param property name of the member.
+ * @param value the number value associated with the member.
+ */
+ public void addProperty(String property, Number value) {
+ add(property, createJsonElement(value));
+ }
+
+ /**
+ * Convenience method to add a boolean member. The specified value is converted to a
+ * JsonPrimitive of Boolean.
+ *
+ * @param property name of the member.
+ * @param value the number value associated with the member.
+ */
+ public void addProperty(String property, Boolean value) {
+ add(property, createJsonElement(value));
+ }
+
+ /**
+ * Convenience method to add a char member. The specified value is converted to a
+ * JsonPrimitive of Character.
+ *
+ * @param property name of the member.
+ * @param value the number value associated with the member.
+ */
+ public void addProperty(String property, Character value) {
+ add(property, createJsonElement(value));
+ }
+
+ /**
+ * Creates the proper {@link JsonElement} object from the given {@code value} object.
+ *
+ * @param value the object to generate the {@link JsonElement} for
+ * @return a {@link JsonPrimitive} if the {@code value} is not null, otherwise a {@link JsonNull}
+ */
+ private JsonElement createJsonElement(Object value) {
+ return value == null ? JsonNull.INSTANCE : new JsonPrimitive(value);
+ }
+
+ /**
+ * Returns a set of members of this object. The set is ordered, and the order is in which the
+ * elements were added.
+ *
+ * @return a set of members of this object.
+ */
+ public Set<Map.Entry<String, JsonElement>> entrySet() {
+ return members.entrySet();
+ }
+
+ /**
+ * Convenience method to check if a member with the specified name is present in this object.
+ *
+ * @param memberName name of the member that is being checked for presence.
+ * @return true if there is a member with the specified name, false otherwise.
+ */
+ public boolean has(String memberName) {
+ return members.containsKey(memberName);
+ }
+
+ /**
+ * Returns the member with the specified name.
+ *
+ * @param memberName name of the member that is being requested.
+ * @return the member matching the name. Null if no such member exists.
+ */
+ public JsonElement get(String memberName) {
+ return members.get(memberName);
+ }
+
+ /**
+ * Convenience method to get the specified member as a JsonPrimitive element.
+ *
+ * @param memberName name of the member being requested.
+ * @return the JsonPrimitive corresponding to the specified member.
+ */
+ public JsonPrimitive getAsJsonPrimitive(String memberName) {
+ return (JsonPrimitive) members.get(memberName);
+ }
+
+ /**
+ * Convenience method to get the specified member as a JsonArray.
+ *
+ * @param memberName name of the member being requested.
+ * @return the JsonArray corresponding to the specified member.
+ */
+ public JsonArray getAsJsonArray(String memberName) {
+ return (JsonArray) members.get(memberName);
+ }
+
+ /**
+ * Convenience method to get the specified member as a JsonObject.
+ *
+ * @param memberName name of the member being requested.
+ * @return the JsonObject corresponding to the specified member.
+ */
+ public JsonObject getAsJsonObject(String memberName) {
+ return (JsonObject) members.get(memberName);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o == this) || (o instanceof JsonObject
+ && ((JsonObject) o).members.equals(members));
+ }
+
+ @Override
+ public int hashCode() {
+ return members.hashCode();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonParseException.java b/gson/src/main/java/com/google/gson/JsonParseException.java
new file mode 100644
index 00000000..084f6612
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonParseException.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+/**
+ * This exception is raised if there is a serious issue that occurs during parsing of a Json
+ * string. One of the main usages for this class is for the Gson infrastructure. If the incoming
+ * Json is bad/malicious, an instance of this exception is raised.
+ *
+ * <p>This exception is a {@link RuntimeException} because it is exposed to the client. Using a
+ * {@link RuntimeException} avoids bad coding practices on the client side where they catch the
+ * exception and do nothing. It is often the case that you want to blow up if there is a parsing
+ * error (i.e. often clients do not know how to recover from a {@link JsonParseException}.</p>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class JsonParseException extends RuntimeException {
+ static final long serialVersionUID = -4086729973971783390L;
+
+ /**
+ * Creates exception with the specified message. If you are wrapping another exception, consider
+ * using {@link #JsonParseException(String, Throwable)} instead.
+ *
+ * @param msg error message describing a possible cause of this exception.
+ */
+ public JsonParseException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Creates exception with the specified message and cause.
+ *
+ * @param msg error message describing what happened.
+ * @param cause root exception that caused this exception to be thrown.
+ */
+ public JsonParseException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ /**
+ * Creates exception with the specified cause. Consider using
+ * {@link #JsonParseException(String, Throwable)} instead if you can describe what happened.
+ *
+ * @param cause root exception that caused this exception to be thrown.
+ */
+ public JsonParseException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java
new file mode 100755
index 00000000..a8ae337b
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonParser.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.MalformedJsonException;
+
+/**
+ * A parser to parse Json into a parse tree of {@link JsonElement}s
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @since 1.3
+ */
+public final class JsonParser {
+
+ /**
+ * Parses the specified JSON string into a parse tree
+ *
+ * @param json JSON text
+ * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
+ * @throws JsonParseException if the specified text is not valid JSON
+ * @since 1.3
+ */
+ public JsonElement parse(String json) throws JsonSyntaxException {
+ return parse(new StringReader(json));
+ }
+
+ /**
+ * Parses the specified JSON string into a parse tree
+ *
+ * @param json JSON text
+ * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON
+ * @throws JsonParseException if the specified text is not valid JSON
+ * @since 1.3
+ */
+ public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException {
+ try {
+ JsonReader jsonReader = new JsonReader(json);
+ JsonElement element = parse(jsonReader);
+ if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) {
+ throw new JsonSyntaxException("Did not consume the entire document.");
+ }
+ return element;
+ } catch (MalformedJsonException e) {
+ throw new JsonSyntaxException(e);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+
+ /**
+ * Returns the next value from the JSON stream as a parse tree.
+ *
+ * @throws JsonParseException if there is an IOException or if the specified
+ * text is not valid JSON
+ * @since 1.6
+ */
+ public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException {
+ boolean lenient = json.isLenient();
+ json.setLenient(true);
+ try {
+ return Streams.parse(json);
+ } catch (StackOverflowError e) {
+ throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e);
+ } catch (OutOfMemoryError e) {
+ throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e);
+ } finally {
+ json.setLenient(lenient);
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonPrimitive.java b/gson/src/main/java/com/google/gson/JsonPrimitive.java
new file mode 100644
index 00000000..e2443d43
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonPrimitive.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import com.google.gson.internal.$Gson$Preconditions;
+import com.google.gson.internal.LazilyParsedNumber;
+
+/**
+ * A class representing a Json primitive value. A primitive value
+ * is either a String, a Java primitive, or a Java primitive
+ * wrapper type.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class JsonPrimitive extends JsonElement {
+
+ private static final Class<?>[] PRIMITIVE_TYPES = { int.class, long.class, short.class,
+ float.class, double.class, byte.class, boolean.class, char.class, Integer.class, Long.class,
+ Short.class, Float.class, Double.class, Byte.class, Boolean.class, Character.class };
+
+ private Object value;
+
+ /**
+ * Create a primitive containing a boolean value.
+ *
+ * @param bool the value to create the primitive with.
+ */
+ public JsonPrimitive(Boolean bool) {
+ setValue(bool);
+ }
+
+ /**
+ * Create a primitive containing a {@link Number}.
+ *
+ * @param number the value to create the primitive with.
+ */
+ public JsonPrimitive(Number number) {
+ setValue(number);
+ }
+
+ /**
+ * Create a primitive containing a String value.
+ *
+ * @param string the value to create the primitive with.
+ */
+ public JsonPrimitive(String string) {
+ setValue(string);
+ }
+
+ /**
+ * Create a primitive containing a character. The character is turned into a one character String
+ * since Json only supports String.
+ *
+ * @param c the value to create the primitive with.
+ */
+ public JsonPrimitive(Character c) {
+ setValue(c);
+ }
+
+ /**
+ * Create a primitive using the specified Object. It must be an instance of {@link Number}, a
+ * Java primitive type, or a String.
+ *
+ * @param primitive the value to create the primitive with.
+ */
+ JsonPrimitive(Object primitive) {
+ setValue(primitive);
+ }
+
+ @Override
+ JsonPrimitive deepCopy() {
+ return this;
+ }
+
+ void setValue(Object primitive) {
+ if (primitive instanceof Character) {
+ // convert characters to strings since in JSON, characters are represented as a single
+ // character string
+ char c = ((Character) primitive).charValue();
+ this.value = String.valueOf(c);
+ } else {
+ $Gson$Preconditions.checkArgument(primitive instanceof Number
+ || isPrimitiveOrString(primitive));
+ this.value = primitive;
+ }
+ }
+
+ /**
+ * Check whether this primitive contains a boolean value.
+ *
+ * @return true if this primitive contains a boolean value, false otherwise.
+ */
+ public boolean isBoolean() {
+ return value instanceof Boolean;
+ }
+
+ /**
+ * convenience method to get this element as a {@link Boolean}.
+ *
+ * @return get this element as a {@link Boolean}.
+ */
+ @Override
+ Boolean getAsBooleanWrapper() {
+ return (Boolean) value;
+ }
+
+ /**
+ * convenience method to get this element as a boolean value.
+ *
+ * @return get this element as a primitive boolean value.
+ */
+ @Override
+ public boolean getAsBoolean() {
+ if (isBoolean()) {
+ return getAsBooleanWrapper().booleanValue();
+ } else {
+ // Check to see if the value as a String is "true" in any case.
+ return Boolean.parseBoolean(getAsString());
+ }
+ }
+
+ /**
+ * Check whether this primitive contains a Number.
+ *
+ * @return true if this primitive contains a Number, false otherwise.
+ */
+ public boolean isNumber() {
+ return value instanceof Number;
+ }
+
+ /**
+ * convenience method to get this element as a Number.
+ *
+ * @return get this element as a Number.
+ * @throws NumberFormatException if the value contained is not a valid Number.
+ */
+ @Override
+ public Number getAsNumber() {
+ return value instanceof String ? new LazilyParsedNumber((String) value) : (Number) value;
+ }
+
+ /**
+ * Check whether this primitive contains a String value.
+ *
+ * @return true if this primitive contains a String value, false otherwise.
+ */
+ public boolean isString() {
+ return value instanceof String;
+ }
+
+ /**
+ * convenience method to get this element as a String.
+ *
+ * @return get this element as a String.
+ */
+ @Override
+ public String getAsString() {
+ if (isNumber()) {
+ return getAsNumber().toString();
+ } else if (isBoolean()) {
+ return getAsBooleanWrapper().toString();
+ } else {
+ return (String) value;
+ }
+ }
+
+ /**
+ * convenience method to get this element as a primitive double.
+ *
+ * @return get this element as a primitive double.
+ * @throws NumberFormatException if the value contained is not a valid double.
+ */
+ @Override
+ public double getAsDouble() {
+ return isNumber() ? getAsNumber().doubleValue() : Double.parseDouble(getAsString());
+ }
+
+ /**
+ * convenience method to get this element as a {@link BigDecimal}.
+ *
+ * @return get this element as a {@link BigDecimal}.
+ * @throws NumberFormatException if the value contained is not a valid {@link BigDecimal}.
+ */
+ @Override
+ public BigDecimal getAsBigDecimal() {
+ return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString());
+ }
+
+ /**
+ * convenience method to get this element as a {@link BigInteger}.
+ *
+ * @return get this element as a {@link BigInteger}.
+ * @throws NumberFormatException if the value contained is not a valid {@link BigInteger}.
+ */
+ @Override
+ public BigInteger getAsBigInteger() {
+ return value instanceof BigInteger ?
+ (BigInteger) value : new BigInteger(value.toString());
+ }
+
+ /**
+ * convenience method to get this element as a float.
+ *
+ * @return get this element as a float.
+ * @throws NumberFormatException if the value contained is not a valid float.
+ */
+ @Override
+ public float getAsFloat() {
+ return isNumber() ? getAsNumber().floatValue() : Float.parseFloat(getAsString());
+ }
+
+ /**
+ * convenience method to get this element as a primitive long.
+ *
+ * @return get this element as a primitive long.
+ * @throws NumberFormatException if the value contained is not a valid long.
+ */
+ @Override
+ public long getAsLong() {
+ return isNumber() ? getAsNumber().longValue() : Long.parseLong(getAsString());
+ }
+
+ /**
+ * convenience method to get this element as a primitive short.
+ *
+ * @return get this element as a primitive short.
+ * @throws NumberFormatException if the value contained is not a valid short value.
+ */
+ @Override
+ public short getAsShort() {
+ return isNumber() ? getAsNumber().shortValue() : Short.parseShort(getAsString());
+ }
+
+ /**
+ * convenience method to get this element as a primitive integer.
+ *
+ * @return get this element as a primitive integer.
+ * @throws NumberFormatException if the value contained is not a valid integer.
+ */
+ @Override
+ public int getAsInt() {
+ return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString());
+ }
+
+ @Override
+ public byte getAsByte() {
+ return isNumber() ? getAsNumber().byteValue() : Byte.parseByte(getAsString());
+ }
+
+ @Override
+ public char getAsCharacter() {
+ return getAsString().charAt(0);
+ }
+
+ private static boolean isPrimitiveOrString(Object target) {
+ if (target instanceof String) {
+ return true;
+ }
+
+ Class<?> classOfPrimitive = target.getClass();
+ for (Class<?> standardPrimitive : PRIMITIVE_TYPES) {
+ if (standardPrimitive.isAssignableFrom(classOfPrimitive)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (value == null) {
+ return 31;
+ }
+ // Using recommended hashing algorithm from Effective Java for longs and doubles
+ if (isIntegral(this)) {
+ long value = getAsNumber().longValue();
+ return (int) (value ^ (value >>> 32));
+ }
+ if (value instanceof Number) {
+ long value = Double.doubleToLongBits(getAsNumber().doubleValue());
+ return (int) (value ^ (value >>> 32));
+ }
+ return value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ JsonPrimitive other = (JsonPrimitive)obj;
+ if (value == null) {
+ return other.value == null;
+ }
+ if (isIntegral(this) && isIntegral(other)) {
+ return getAsNumber().longValue() == other.getAsNumber().longValue();
+ }
+ if (value instanceof Number && other.value instanceof Number) {
+ double a = getAsNumber().doubleValue();
+ // Java standard types other than double return true for two NaN. So, need
+ // special handling for double.
+ double b = other.getAsNumber().doubleValue();
+ return a == b || (Double.isNaN(a) && Double.isNaN(b));
+ }
+ return value.equals(other.value);
+ }
+
+ /**
+ * Returns true if the specified number is an integral type
+ * (Long, Integer, Short, Byte, BigInteger)
+ */
+ private static boolean isIntegral(JsonPrimitive primitive) {
+ if (primitive.value instanceof Number) {
+ Number number = (Number) primitive.value;
+ return number instanceof BigInteger || number instanceof Long || number instanceof Integer
+ || number instanceof Short || number instanceof Byte;
+ }
+ return false;
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonSerializationContext.java b/gson/src/main/java/com/google/gson/JsonSerializationContext.java
new file mode 100644
index 00000000..ca3ec4f9
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonSerializationContext.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+
+/**
+ * Context for serialization that is passed to a custom serializer during invocation of its
+ * {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public interface JsonSerializationContext {
+
+ /**
+ * Invokes default serialization on the specified object.
+ *
+ * @param src the object that needs to be serialized.
+ * @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}.
+ */
+ public JsonElement serialize(Object src);
+
+ /**
+ * Invokes default serialization on the specified object passing the specific type information.
+ * It should never be invoked on the element received as a parameter of the
+ * {@link JsonSerializer#serialize(Object, Type, JsonSerializationContext)} method. Doing
+ * so will result in an infinite loop since Gson will in-turn call the custom serializer again.
+ *
+ * @param src the object that needs to be serialized.
+ * @param typeOfSrc the actual genericized type of src object.
+ * @return a tree of {@link JsonElement}s corresponding to the serialized form of {@code src}.
+ */
+ public JsonElement serialize(Object src, Type typeOfSrc);
+}
diff --git a/gson/src/main/java/com/google/gson/JsonSerializer.java b/gson/src/main/java/com/google/gson/JsonSerializer.java
new file mode 100644
index 00000000..a6050033
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonSerializer.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+
+/**
+ * Interface representing a custom serializer for Json. You should write a custom serializer, if
+ * you are not happy with the default serialization done by Gson. You will also need to register
+ * this serializer through {@link com.google.gson.GsonBuilder#registerTypeAdapter(Type, Object)}.
+ *
+ * <p>Let us look at example where defining a serializer will be useful. The {@code Id} class
+ * defined below has two fields: {@code clazz} and {@code value}.</p>
+ *
+ * <p><pre>
+ * public class Id&lt;T&gt; {
+ * private final Class&lt;T&gt; clazz;
+ * private final long value;
+ *
+ * public Id(Class&lt;T&gt; clazz, long value) {
+ * this.clazz = clazz;
+ * this.value = value;
+ * }
+ *
+ * public long getValue() {
+ * return value;
+ * }
+ * }
+ * </pre></p>
+ *
+ * <p>The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be
+ * <code>{"clazz":com.foo.MyObject,"value":20}</code>. Suppose, you just want the output to be
+ * the value instead, which is {@code 20} in this case. You can achieve that by writing a custom
+ * serializer:</p>
+ *
+ * <p><pre>
+ * class IdSerializer implements JsonSerializer&lt;Id&gt;() {
+ * public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
+ * return new JsonPrimitive(id.getValue());
+ * }
+ * }
+ * </pre></p>
+ *
+ * <p>You will also need to register {@code IdSerializer} with Gson as follows:</p>
+ * <pre>
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
+ * </pre>
+ *
+ * <p>New applications should prefer {@link TypeAdapter}, whose streaming API
+ * is more efficient than this interface's tree API.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ *
+ * @param <T> type for which the serializer is being registered. It is possible that a serializer
+ * may be asked to serialize a specific generic type of the T.
+ */
+public interface JsonSerializer<T> {
+
+ /**
+ * Gson invokes this call-back method during serialization when it encounters a field of the
+ * specified type.
+ *
+ * <p>In the implementation of this call-back method, you should consider invoking
+ * {@link JsonSerializationContext#serialize(Object, Type)} method to create JsonElements for any
+ * non-trivial field of the {@code src} object. However, you should never invoke it on the
+ * {@code src} object itself since that will cause an infinite loop (Gson will call your
+ * call-back method again).</p>
+ *
+ * @param src the object that needs to be converted to Json.
+ * @param typeOfSrc the actual type (fully genericized version) of the source object.
+ * @return a JsonElement corresponding to the specified object.
+ */
+ public JsonElement serialize(T src, Type typeOfSrc, JsonSerializationContext context);
+}
diff --git a/gson/src/main/java/com/google/gson/JsonStreamParser.java b/gson/src/main/java/com/google/gson/JsonStreamParser.java
new file mode 100644
index 00000000..f0438db3
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonStreamParser.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.MalformedJsonException;
+
+/**
+ * A streaming parser that allows reading of multiple {@link JsonElement}s from the specified reader
+ * asynchronously.
+ *
+ * <p>This class is conditionally thread-safe (see Item 70, Effective Java second edition). To
+ * properly use this class across multiple threads, you will need to add some external
+ * synchronization. For example:
+ *
+ * <pre>
+ * JsonStreamParser parser = new JsonStreamParser("['first'] {'second':10} 'third'");
+ * JsonElement element;
+ * synchronized (parser) { // synchronize on an object shared by threads
+ * if (parser.hasNext()) {
+ * element = parser.next();
+ * }
+ * }
+ * </pre>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @since 1.4
+ */
+public final class JsonStreamParser implements Iterator<JsonElement> {
+ private final JsonReader parser;
+ private final Object lock;
+
+ /**
+ * @param json The string containing JSON elements concatenated to each other.
+ * @since 1.4
+ */
+ public JsonStreamParser(String json) {
+ this(new StringReader(json));
+ }
+
+ /**
+ * @param reader The data stream containing JSON elements concatenated to each other.
+ * @since 1.4
+ */
+ public JsonStreamParser(Reader reader) {
+ parser = new JsonReader(reader);
+ parser.setLenient(true);
+ lock = new Object();
+ }
+
+ /**
+ * Returns the next available {@link JsonElement} on the reader. Null if none available.
+ *
+ * @return the next available {@link JsonElement} on the reader. Null if none available.
+ * @throws JsonParseException if the incoming stream is malformed JSON.
+ * @since 1.4
+ */
+ public JsonElement next() throws JsonParseException {
+ if (!hasNext()) {
+ throw new NoSuchElementException();
+ }
+
+ try {
+ return Streams.parse(parser);
+ } catch (StackOverflowError e) {
+ throw new JsonParseException("Failed parsing JSON source to Json", e);
+ } catch (OutOfMemoryError e) {
+ throw new JsonParseException("Failed parsing JSON source to Json", e);
+ } catch (JsonParseException e) {
+ throw e.getCause() instanceof EOFException ? new NoSuchElementException() : e;
+ }
+ }
+
+ /**
+ * Returns true if a {@link JsonElement} is available on the input for consumption
+ * @return true if a {@link JsonElement} is available on the input, false otherwise
+ * @since 1.4
+ */
+ public boolean hasNext() {
+ synchronized (lock) {
+ try {
+ return parser.peek() != JsonToken.END_DOCUMENT;
+ } catch (MalformedJsonException e) {
+ throw new JsonSyntaxException(e);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+ }
+
+ /**
+ * This optional {@link Iterator} method is not relevant for stream parsing and hence is not
+ * implemented.
+ * @since 1.4
+ */
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/JsonSyntaxException.java b/gson/src/main/java/com/google/gson/JsonSyntaxException.java
new file mode 100644
index 00000000..17c1d3d3
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/JsonSyntaxException.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson;
+
+/**
+ * This exception is raised when Gson attempts to read (or write) a malformed
+ * JSON element.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class JsonSyntaxException extends JsonParseException {
+
+ private static final long serialVersionUID = 1L;
+
+ public JsonSyntaxException(String msg) {
+ super(msg);
+ }
+
+ public JsonSyntaxException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
+
+ /**
+ * Creates exception with the specified cause. Consider using
+ * {@link #JsonSyntaxException(String, Throwable)} instead if you can
+ * describe what actually happened.
+ *
+ * @param cause root exception that caused this exception to be thrown.
+ */
+ public JsonSyntaxException(Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/LongSerializationPolicy.java b/gson/src/main/java/com/google/gson/LongSerializationPolicy.java
new file mode 100644
index 00000000..3d9a2da1
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/LongSerializationPolicy.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+/**
+ * Defines the expected format for a {@code long} or {@code Long} type when its serialized.
+ *
+ * @since 1.3
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public enum LongSerializationPolicy {
+ /**
+ * This is the "default" serialization policy that will output a {@code long} object as a JSON
+ * number. For example, assume an object has a long field named "f" then the serialized output
+ * would be:
+ * {@code {"f":123}}.
+ */
+ DEFAULT() {
+ public JsonElement serialize(Long value) {
+ return new JsonPrimitive(value);
+ }
+ },
+
+ /**
+ * Serializes a long value as a quoted string. For example, assume an object has a long field
+ * named "f" then the serialized output would be:
+ * {@code {"f":"123"}}.
+ */
+ STRING() {
+ public JsonElement serialize(Long value) {
+ return new JsonPrimitive(String.valueOf(value));
+ }
+ };
+
+ /**
+ * Serialize this {@code value} using this serialization policy.
+ *
+ * @param value the long value to be serialized into a {@link JsonElement}
+ * @return the serialized version of {@code value}
+ */
+ public abstract JsonElement serialize(Long value);
+}
diff --git a/gson/src/main/java/com/google/gson/TreeTypeAdapter.java b/gson/src/main/java/com/google/gson/TreeTypeAdapter.java
new file mode 100644
index 00000000..a05c1fe0
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/TreeTypeAdapter.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import com.google.gson.internal.$Gson$Preconditions;
+import com.google.gson.internal.Streams;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+
+/**
+ * Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the
+ * tree adapter may be serialization-only or deserialization-only, this class
+ * has a facility to lookup a delegate type adapter on demand.
+ */
+final class TreeTypeAdapter<T> extends TypeAdapter<T> {
+ private final JsonSerializer<T> serializer;
+ private final JsonDeserializer<T> deserializer;
+ private final Gson gson;
+ private final TypeToken<T> typeToken;
+ private final TypeAdapterFactory skipPast;
+
+ /** The delegate is lazily created because it may not be needed, and creating it may fail. */
+ private TypeAdapter<T> delegate;
+
+ private TreeTypeAdapter(JsonSerializer<T> serializer, JsonDeserializer<T> deserializer,
+ Gson gson, TypeToken<T> typeToken, TypeAdapterFactory skipPast) {
+ this.serializer = serializer;
+ this.deserializer = deserializer;
+ this.gson = gson;
+ this.typeToken = typeToken;
+ this.skipPast = skipPast;
+ }
+
+ @Override public T read(JsonReader in) throws IOException {
+ if (deserializer == null) {
+ return delegate().read(in);
+ }
+ JsonElement value = Streams.parse(in);
+ if (value.isJsonNull()) {
+ return null;
+ }
+ return deserializer.deserialize(value, typeToken.getType(), gson.deserializationContext);
+ }
+
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ if (serializer == null) {
+ delegate().write(out, value);
+ return;
+ }
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+ JsonElement tree = serializer.serialize(value, typeToken.getType(), gson.serializationContext);
+ Streams.write(tree, out);
+ }
+
+ private TypeAdapter<T> delegate() {
+ TypeAdapter<T> d = delegate;
+ return d != null
+ ? d
+ : (delegate = gson.getDelegateAdapter(skipPast, typeToken));
+ }
+
+ /**
+ * Returns a new factory that will match each type against {@code exactType}.
+ */
+ public static TypeAdapterFactory newFactory(TypeToken<?> exactType, Object typeAdapter) {
+ return new SingleTypeFactory(typeAdapter, exactType, false, null);
+ }
+
+ /**
+ * Returns a new factory that will match each type and its raw type against
+ * {@code exactType}.
+ */
+ public static TypeAdapterFactory newFactoryWithMatchRawType(
+ TypeToken<?> exactType, Object typeAdapter) {
+ // only bother matching raw types if exact type is a raw type
+ boolean matchRawType = exactType.getType() == exactType.getRawType();
+ return new SingleTypeFactory(typeAdapter, exactType, matchRawType, null);
+ }
+
+ /**
+ * Returns a new factory that will match each type's raw type for assignability
+ * to {@code hierarchyType}.
+ */
+ public static TypeAdapterFactory newTypeHierarchyFactory(
+ Class<?> hierarchyType, Object typeAdapter) {
+ return new SingleTypeFactory(typeAdapter, null, false, hierarchyType);
+ }
+
+ private static class SingleTypeFactory implements TypeAdapterFactory {
+ private final TypeToken<?> exactType;
+ private final boolean matchRawType;
+ private final Class<?> hierarchyType;
+ private final JsonSerializer<?> serializer;
+ private final JsonDeserializer<?> deserializer;
+
+ private SingleTypeFactory(Object typeAdapter, TypeToken<?> exactType, boolean matchRawType,
+ Class<?> hierarchyType) {
+ serializer = typeAdapter instanceof JsonSerializer
+ ? (JsonSerializer<?>) typeAdapter
+ : null;
+ deserializer = typeAdapter instanceof JsonDeserializer
+ ? (JsonDeserializer<?>) typeAdapter
+ : null;
+ $Gson$Preconditions.checkArgument(serializer != null || deserializer != null);
+ this.exactType = exactType;
+ this.matchRawType = matchRawType;
+ this.hierarchyType = hierarchyType;
+ }
+
+ @SuppressWarnings("unchecked") // guarded by typeToken.equals() call
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ boolean matches = exactType != null
+ ? exactType.equals(type) || matchRawType && exactType.getType() == type.getRawType()
+ : hierarchyType.isAssignableFrom(type.getRawType());
+ return matches
+ ? new TreeTypeAdapter<T>((JsonSerializer<T>) serializer,
+ (JsonDeserializer<T>) deserializer, gson, type, this)
+ : null;
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/TypeAdapter.java b/gson/src/main/java/com/google/gson/TypeAdapter.java
new file mode 100644
index 00000000..4646d271
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/TypeAdapter.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import com.google.gson.internal.bind.JsonTreeWriter;
+import com.google.gson.internal.bind.JsonTreeReader;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Converts Java objects to and from JSON.
+ *
+ * <h3>Defining a type's JSON form</h3>
+ * By default Gson converts application classes to JSON using its built-in type
+ * adapters. If Gson's default JSON conversion isn't appropriate for a type,
+ * extend this class to customize the conversion. Here's an example of a type
+ * adapter for an (X,Y) coordinate point: <pre> {@code
+ *
+ * public class PointAdapter extends TypeAdapter<Point> {
+ * public Point read(JsonReader reader) throws IOException {
+ * if (reader.peek() == JsonToken.NULL) {
+ * reader.nextNull();
+ * return null;
+ * }
+ * String xy = reader.nextString();
+ * String[] parts = xy.split(",");
+ * int x = Integer.parseInt(parts[0]);
+ * int y = Integer.parseInt(parts[1]);
+ * return new Point(x, y);
+ * }
+ * public void write(JsonWriter writer, Point value) throws IOException {
+ * if (value == null) {
+ * writer.nullValue();
+ * return;
+ * }
+ * String xy = value.getX() + "," + value.getY();
+ * writer.value(xy);
+ * }
+ * }}</pre>
+ * With this type adapter installed, Gson will convert {@code Points} to JSON as
+ * strings like {@code "5,8"} rather than objects like {@code {"x":5,"y":8}}. In
+ * this case the type adapter binds a rich Java class to a compact JSON value.
+ *
+ * <p>The {@link #read(JsonReader) read()} method must read exactly one value
+ * and {@link #write(JsonWriter,Object) write()} must write exactly one value.
+ * For primitive types this is means readers should make exactly one call to
+ * {@code nextBoolean()}, {@code nextDouble()}, {@code nextInt()}, {@code
+ * nextLong()}, {@code nextString()} or {@code nextNull()}. Writers should make
+ * exactly one call to one of <code>value()</code> or <code>nullValue()</code>.
+ * For arrays, type adapters should start with a call to {@code beginArray()},
+ * convert all elements, and finish with a call to {@code endArray()}. For
+ * objects, they should start with {@code beginObject()}, convert the object,
+ * and finish with {@code endObject()}. Failing to convert a value or converting
+ * too many values may cause the application to crash.
+ *
+ * <p>Type adapters should be prepared to read null from the stream and write it
+ * to the stream. Alternatively, they should use {@link #nullSafe()} method while
+ * registering the type adapter with Gson. If your {@code Gson} instance
+ * has been configured to {@link GsonBuilder#serializeNulls()}, these nulls will be
+ * written to the final document. Otherwise the value (and the corresponding name
+ * when writing to a JSON object) will be omitted automatically. In either case
+ * your type adapter must handle null.
+ *
+ * <p>To use a custom type adapter with Gson, you must <i>register</i> it with a
+ * {@link GsonBuilder}: <pre> {@code
+ *
+ * GsonBuilder builder = new GsonBuilder();
+ * builder.registerTypeAdapter(Point.class, new PointAdapter());
+ * // if PointAdapter didn't check for nulls in its read/write methods, you should instead use
+ * // builder.registerTypeAdapter(Point.class, new PointAdapter().nullSafe());
+ * ...
+ * Gson gson = builder.create();
+ * }</pre>
+ *
+ * @since 2.1
+ */
+// non-Javadoc:
+//
+// <h3>JSON Conversion</h3>
+// <p>A type adapter registered with Gson is automatically invoked while serializing
+// or deserializing JSON. However, you can also use type adapters directly to serialize
+// and deserialize JSON. Here is an example for deserialization: <pre> {@code
+//
+// String json = "{'origin':'0,0','points':['1,2','3,4']}";
+// TypeAdapter<Graph> graphAdapter = gson.getAdapter(Graph.class);
+// Graph graph = graphAdapter.fromJson(json);
+// }</pre>
+// And an example for serialization: <pre> {@code
+//
+// Graph graph = new Graph(...);
+// TypeAdapter<Graph> graphAdapter = gson.getAdapter(Graph.class);
+// String json = graphAdapter.toJson(graph);
+// }</pre>
+//
+// <p>Type adapters are <strong>type-specific</strong>. For example, a {@code
+// TypeAdapter<Date>} can convert {@code Date} instances to JSON and JSON to
+// instances of {@code Date}, but cannot convert any other types.
+//
+public abstract class TypeAdapter<T> {
+
+ /**
+ * Writes one JSON value (an array, object, string, number, boolean or null)
+ * for {@code value}.
+ *
+ * @param value the Java object to write. May be null.
+ */
+ public abstract void write(JsonWriter out, T value) throws IOException;
+
+ /**
+ * Converts {@code value} to a JSON document and writes it to {@code out}.
+ * Unlike Gson's similar {@link Gson#toJson(JsonElement, Appendable) toJson}
+ * method, this write is strict. Create a {@link
+ * JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
+ * {@link #write(com.google.gson.stream.JsonWriter, Object)} for lenient
+ * writing.
+ *
+ * @param value the Java object to convert. May be null.
+ * @since 2.2
+ */
+ public final void toJson(Writer out, T value) throws IOException {
+ JsonWriter writer = new JsonWriter(out);
+ write(writer, value);
+ }
+
+ /**
+ * This wrapper method is used to make a type adapter null tolerant. In general, a
+ * type adapter is required to handle nulls in write and read methods. Here is how this
+ * is typically done:<br>
+ * <pre> {@code
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
+ * new TypeAdapter<Foo>() {
+ * public Foo read(JsonReader in) throws IOException {
+ * if (in.peek() == JsonToken.NULL) {
+ * in.nextNull();
+ * return null;
+ * }
+ * // read a Foo from in and return it
+ * }
+ * public void write(JsonWriter out, Foo src) throws IOException {
+ * if (src == null) {
+ * out.nullValue();
+ * return;
+ * }
+ * // write src as JSON to out
+ * }
+ * }).create();
+ * }</pre>
+ * You can avoid this boilerplate handling of nulls by wrapping your type adapter with
+ * this method. Here is how we will rewrite the above example:
+ * <pre> {@code
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
+ * new TypeAdapter<Foo>() {
+ * public Foo read(JsonReader in) throws IOException {
+ * // read a Foo from in and return it
+ * }
+ * public void write(JsonWriter out, Foo src) throws IOException {
+ * // write src as JSON to out
+ * }
+ * }.nullSafe()).create();
+ * }</pre>
+ * Note that we didn't need to check for nulls in our type adapter after we used nullSafe.
+ */
+ public final TypeAdapter<T> nullSafe() {
+ return new TypeAdapter<T>() {
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ } else {
+ TypeAdapter.this.write(out, value);
+ }
+ }
+ @Override public T read(JsonReader reader) throws IOException {
+ if (reader.peek() == JsonToken.NULL) {
+ reader.nextNull();
+ return null;
+ }
+ return TypeAdapter.this.read(reader);
+ }
+ };
+ }
+
+ /**
+ * Converts {@code value} to a JSON document. Unlike Gson's similar {@link
+ * Gson#toJson(Object) toJson} method, this write is strict. Create a {@link
+ * JsonWriter#setLenient(boolean) lenient} {@code JsonWriter} and call
+ * {@link #write(com.google.gson.stream.JsonWriter, Object)} for lenient
+ * writing.
+ *
+ * @param value the Java object to convert. May be null.
+ * @since 2.2
+ */
+ public final String toJson(T value) {
+ StringWriter stringWriter = new StringWriter();
+ try {
+ toJson(stringWriter, value);
+ } catch (IOException e) {
+ throw new AssertionError(e); // No I/O writing to a StringWriter.
+ }
+ return stringWriter.toString();
+ }
+
+ /**
+ * Converts {@code value} to a JSON tree.
+ *
+ * @param value the Java object to convert. May be null.
+ * @return the converted JSON tree. May be {@link JsonNull}.
+ * @since 2.2
+ */
+ public final JsonElement toJsonTree(T value) {
+ try {
+ JsonTreeWriter jsonWriter = new JsonTreeWriter();
+ write(jsonWriter, value);
+ return jsonWriter.get();
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+
+ /**
+ * Reads one JSON value (an array, object, string, number, boolean or null)
+ * and converts it to a Java object. Returns the converted object.
+ *
+ * @return the converted Java object. May be null.
+ */
+ public abstract T read(JsonReader in) throws IOException;
+
+ /**
+ * Converts the JSON document in {@code in} to a Java object. Unlike Gson's
+ * similar {@link Gson#fromJson(java.io.Reader, Class) fromJson} method, this
+ * read is strict. Create a {@link JsonReader#setLenient(boolean) lenient}
+ * {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading.
+ *
+ * @return the converted Java object. May be null.
+ * @since 2.2
+ */
+ public final T fromJson(Reader in) throws IOException {
+ JsonReader reader = new JsonReader(in);
+ return read(reader);
+ }
+
+ /**
+ * Converts the JSON document in {@code json} to a Java object. Unlike Gson's
+ * similar {@link Gson#fromJson(String, Class) fromJson} method, this read is
+ * strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code
+ * JsonReader} and call {@link #read(JsonReader)} for lenient reading.
+ *
+ * @return the converted Java object. May be null.
+ * @since 2.2
+ */
+ public final T fromJson(String json) throws IOException {
+ return fromJson(new StringReader(json));
+ }
+
+ /**
+ * Converts {@code jsonTree} to a Java object.
+ *
+ * @param jsonTree the Java object to convert. May be {@link JsonNull}.
+ * @since 2.2
+ */
+ public final T fromJsonTree(JsonElement jsonTree) {
+ try {
+ JsonReader jsonReader = new JsonTreeReader(jsonTree);
+ return read(jsonReader);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/TypeAdapterFactory.java b/gson/src/main/java/com/google/gson/TypeAdapterFactory.java
new file mode 100644
index 00000000..e12a72dc
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/TypeAdapterFactory.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Creates type adapters for set of related types. Type adapter factories are
+ * most useful when several types share similar structure in their JSON form.
+ *
+ * <h3>Example: Converting enums to lowercase</h3>
+ * In this example, we implement a factory that creates type adapters for all
+ * enums. The type adapters will write enums in lowercase, despite the fact
+ * that they're defined in {@code CONSTANT_CASE} in the corresponding Java
+ * model: <pre> {@code
+ *
+ * public class LowercaseEnumTypeAdapterFactory implements TypeAdapterFactory {
+ * public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ * Class<T> rawType = (Class<T>) type.getRawType();
+ * if (!rawType.isEnum()) {
+ * return null;
+ * }
+ *
+ * final Map<String, T> lowercaseToConstant = new HashMap<String, T>();
+ * for (T constant : rawType.getEnumConstants()) {
+ * lowercaseToConstant.put(toLowercase(constant), constant);
+ * }
+ *
+ * return new TypeAdapter<T>() {
+ * public void write(JsonWriter out, T value) throws IOException {
+ * if (value == null) {
+ * out.nullValue();
+ * } else {
+ * out.value(toLowercase(value));
+ * }
+ * }
+ *
+ * public T read(JsonReader reader) throws IOException {
+ * if (reader.peek() == JsonToken.NULL) {
+ * reader.nextNull();
+ * return null;
+ * } else {
+ * return lowercaseToConstant.get(reader.nextString());
+ * }
+ * }
+ * };
+ * }
+ *
+ * private String toLowercase(Object o) {
+ * return o.toString().toLowerCase(Locale.US);
+ * }
+ * }
+ * }</pre>
+ *
+ * <p>Type adapter factories select which types they provide type adapters
+ * for. If a factory cannot support a given type, it must return null when
+ * that type is passed to {@link #create}. Factories should expect {@code
+ * create()} to be called on them for many types and should return null for
+ * most of those types. In the above example the factory returns null for
+ * calls to {@code create()} where {@code type} is not an enum.
+ *
+ * <p>A factory is typically called once per type, but the returned type
+ * adapter may be used many times. It is most efficient to do expensive work
+ * like reflection in {@code create()} so that the type adapter's {@code
+ * read()} and {@code write()} methods can be very fast. In this example the
+ * mapping from lowercase name to enum value is computed eagerly.
+ *
+ * <p>As with type adapters, factories must be <i>registered</i> with a {@link
+ * com.google.gson.GsonBuilder} for them to take effect: <pre> {@code
+ *
+ * GsonBuilder builder = new GsonBuilder();
+ * builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
+ * ...
+ * Gson gson = builder.create();
+ * }</pre>
+ * If multiple factories support the same type, the factory registered earlier
+ * takes precedence.
+ *
+ * <h3>Example: composing other type adapters</h3>
+ * In this example we implement a factory for Guava's {@code Multiset}
+ * collection type. The factory can be used to create type adapters for
+ * multisets of any element type: the type adapter for {@code
+ * Multiset<String>} is different from the type adapter for {@code
+ * Multiset<URL>}.
+ *
+ * <p>The type adapter <i>delegates</i> to another type adapter for the
+ * multiset elements. It figures out the element type by reflecting on the
+ * multiset's type token. A {@code Gson} is passed in to {@code create} for
+ * just this purpose: <pre> {@code
+ *
+ * public class MultisetTypeAdapterFactory implements TypeAdapterFactory {
+ * public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ * Type type = typeToken.getType();
+ * if (typeToken.getRawType() != Multiset.class
+ * || !(type instanceof ParameterizedType)) {
+ * return null;
+ * }
+ *
+ * Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
+ * TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
+ * return (TypeAdapter<T>) newMultisetAdapter(elementAdapter);
+ * }
+ *
+ * private <E> TypeAdapter<Multiset<E>> newMultisetAdapter(
+ * final TypeAdapter<E> elementAdapter) {
+ * return new TypeAdapter<Multiset<E>>() {
+ * public void write(JsonWriter out, Multiset<E> value) throws IOException {
+ * if (value == null) {
+ * out.nullValue();
+ * return;
+ * }
+ *
+ * out.beginArray();
+ * for (Multiset.Entry<E> entry : value.entrySet()) {
+ * out.value(entry.getCount());
+ * elementAdapter.write(out, entry.getElement());
+ * }
+ * out.endArray();
+ * }
+ *
+ * public Multiset<E> read(JsonReader in) throws IOException {
+ * if (in.peek() == JsonToken.NULL) {
+ * in.nextNull();
+ * return null;
+ * }
+ *
+ * Multiset<E> result = LinkedHashMultiset.create();
+ * in.beginArray();
+ * while (in.hasNext()) {
+ * int count = in.nextInt();
+ * E element = elementAdapter.read(in);
+ * result.add(element, count);
+ * }
+ * in.endArray();
+ * return result;
+ * }
+ * };
+ * }
+ * }
+ * }</pre>
+ * Delegating from one type adapter to another is extremely powerful; it's
+ * the foundation of how Gson converts Java objects and collections. Whenever
+ * possible your factory should retrieve its delegate type adapter in the
+ * {@code create()} method; this ensures potentially-expensive type adapter
+ * creation happens only once.
+ *
+ * @since 2.1
+ */
+public interface TypeAdapterFactory {
+
+ /**
+ * Returns a type adapter for {@code type}, or null if this factory doesn't
+ * support {@code type}.
+ */
+ <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type);
+}
diff --git a/gson/src/main/java/com/google/gson/annotations/Expose.java b/gson/src/main/java/com/google/gson/annotations/Expose.java
new file mode 100644
index 00000000..1b9c70df
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/annotations/Expose.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that indicates this member should be exposed for JSON
+ * serialization or deserialization.
+ *
+ * <p>This annotation has no effect unless you build {@link com.google.gson.Gson}
+ * with a {@link com.google.gson.GsonBuilder} and invoke
+ * {@link com.google.gson.GsonBuilder#excludeFieldsWithoutExposeAnnotation()}
+ * method.</p>
+ *
+ * <p>Here is an example of how this annotation is meant to be used:
+ * <p><pre>
+ * public class User {
+ * &#64Expose private String firstName;
+ * &#64Expose(serialize = false) private String lastName;
+ * &#64Expose (serialize = false, deserialize = false) private String emailAddress;
+ * private String password;
+ * }
+ * </pre></p>
+ * If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
+ * methods will use the {@code password} field along-with {@code firstName}, {@code lastName},
+ * and {@code emailAddress} for serialization and deserialization. However, if you created Gson
+ * with {@code Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create()}
+ * then the {@code toJson()} and {@code fromJson()} methods of Gson will exclude the
+ * {@code password} field. This is because the {@code password} field is not marked with the
+ * {@code @Expose} annotation. Gson will also exclude {@code lastName} and {@code emailAddress}
+ * from serialization since {@code serialize} is set to {@code false}. Similarly, Gson will
+ * exclude {@code emailAddress} from deserialization since {@code deserialize} is set to false.
+ *
+ * <p>Note that another way to achieve the same effect would have been to just mark the
+ * {@code password} field as {@code transient}, and Gson would have excluded it even with default
+ * settings. The {@code @Expose} annotation is useful in a style of programming where you want to
+ * explicitly specify all fields that should get considered for serialization or deserialization.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Expose {
+
+ /**
+ * If {@code true}, the field marked with this annotation is written out in the JSON while
+ * serializing. If {@code false}, the field marked with this annotation is skipped from the
+ * serialized output. Defaults to {@code true}.
+ * @since 1.4
+ */
+ public boolean serialize() default true;
+
+ /**
+ * If {@code true}, the field marked with this annotation is deserialized from the JSON.
+ * If {@code false}, the field marked with this annotation is skipped during deserialization.
+ * Defaults to {@code true}.
+ * @since 1.4
+ */
+ public boolean deserialize() default true;
+}
diff --git a/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java
new file mode 100644
index 00000000..2ee3e682
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/annotations/JsonAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 Google 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 com.google.gson.annotations;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that indicates the Gson {@link TypeAdapter} to use with a class
+ * or field.
+ *
+ * <p>Here is an example of how this annotation is used:</p>
+ * <pre>
+ * &#64JsonAdapter(UserJsonAdapter.class)
+ * public class User {
+ * public final String firstName, lastName;
+ * private User(String firstName, String lastName) {
+ * this.firstName = firstName;
+ * this.lastName = lastName;
+ * }
+ * }
+ * public class UserJsonAdapter extends TypeAdapter&lt;User&gt; {
+ * &#64Override public void write(JsonWriter out, User user) throws IOException {
+ * // implement write: combine firstName and lastName into name
+ * out.beginObject();
+ * out.name("name");
+ * out.value(user.firstName + " " + user.lastName);
+ * out.endObject();
+ * // implement the write method
+ * }
+ * &#64Override public User read(JsonReader in) throws IOException {
+ * // implement read: split name into firstName and lastName
+ * in.beginObject();
+ * in.nextName();
+ * String[] nameParts = in.nextString().split(" ");
+ * in.endObject();
+ * return new User(nameParts[0], nameParts[1]);
+ * }
+ * }
+ * </pre>
+ *
+ * Since User class specified UserJsonAdapter.class in &#64JsonAdapter annotation, it
+ * will automatically be invoked to serialize/deserialize User instances. <br>
+ *
+ * <p> Here is an example of how to apply this annotation to a field.
+ * <pre>
+ * private static final class Gadget {
+ * &#64JsonAdapter(UserJsonAdapter2.class)
+ * final User user;
+ * Gadget(User user) {
+ * this.user = user;
+ * }
+ * }
+ * </pre>
+ *
+ * It's possible to specify different type adapters on a field, that
+ * field's type, and in the {@link com.google.gson.GsonBuilder}. Field
+ * annotations take precedence over {@code GsonBuilder}-registered type
+ * adapters, which in turn take precedence over annotated types.
+ *
+ * <p>The class referenced by this annotation must be either a {@link
+ * TypeAdapter} or a {@link TypeAdapterFactory}. Using the factory interface
+ * makes it possible to delegate to the enclosing {@code Gson} instance.
+ *
+ * @since 2.3
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @author Jesse Wilson
+ */
+// Note that the above example is taken from AdaptAnnotationTest.
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.FIELD})
+public @interface JsonAdapter {
+
+ /** Either a {@link TypeAdapter} or {@link TypeAdapterFactory}. */
+ Class<?> value();
+
+}
diff --git a/gson/src/main/java/com/google/gson/annotations/SerializedName.java b/gson/src/main/java/com/google/gson/annotations/SerializedName.java
new file mode 100644
index 00000000..06c0a14c
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/annotations/SerializedName.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that indicates this member should be serialized to JSON with
+ * the provided name value as its field name.
+ *
+ * <p>This annotation will override any {@link com.google.gson.FieldNamingPolicy}, including
+ * the default field naming policy, that may have been set on the {@link com.google.gson.Gson}
+ * instance. A different naming policy can set using the {@code GsonBuilder} class. See
+ * {@link com.google.gson.GsonBuilder#setFieldNamingPolicy(com.google.gson.FieldNamingPolicy)}
+ * for more information.</p>
+ *
+ * <p>Here is an example of how this annotation is meant to be used:</p>
+ * <pre>
+ * public class MyClass {
+ * &#64SerializedName("name") String a;
+ * &#64SerializedName(value="name1", alternate={"name2", "name3"}) String b;
+ * String c;
+ *
+ * public MyClass(String a, String b, String c) {
+ * this.a = a;
+ * this.b = b;
+ * this.c = c;
+ * }
+ * }
+ * </pre>
+ *
+ * <p>The following shows the output that is generated when serializing an instance of the
+ * above example class:</p>
+ * <pre>
+ * MyClass target = new MyClass("v1", "v2", "v3");
+ * Gson gson = new Gson();
+ * String json = gson.toJson(target);
+ * System.out.println(json);
+ *
+ * ===== OUTPUT =====
+ * {"name":"v1","name1":"v2","c":"v3"}
+ * </pre>
+ *
+ * <p>NOTE: The value you specify in this annotation must be a valid JSON field name.</p>
+ * While deserializing, all values specified in the annotation will be deserialized into the field.
+ * For example:
+ * <pre>
+ * MyClass target = gson.fromJson("{'name1':'v1'}", MyClass.class);
+ * assertEquals("v1", target.b);
+ * target = gson.fromJson("{'name2':'v2'}", MyClass.class);
+ * assertEquals("v2", target.b);
+ * target = gson.fromJson("{'name3':'v3'}", MyClass.class);
+ * assertEquals("v3", target.b);
+ * </pre>
+ * Note that MyClass.b is now deserialized from either name1, name2 or name3.
+ *
+ * @see com.google.gson.FieldNamingPolicy
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.METHOD})
+public @interface SerializedName {
+
+ /**
+ * @return the desired names of the field when it is deserialized or serialized. All of the specified names will be deserialized from.
+ * The specified first name is what is used for serialization.
+ */
+ String value();
+ String[] alternate() default {};
+}
diff --git a/gson/src/main/java/com/google/gson/annotations/Since.java b/gson/src/main/java/com/google/gson/annotations/Since.java
new file mode 100644
index 00000000..541f154b
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/annotations/Since.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that indicates the version number since a member or a type has been present.
+ * This annotation is useful to manage versioning of your Json classes for a web-service.
+ *
+ * <p>
+ * This annotation has no effect unless you build {@link com.google.gson.Gson} with a
+ * {@link com.google.gson.GsonBuilder} and invoke
+ * {@link com.google.gson.GsonBuilder#setVersion(double)} method.
+ *
+ * <p>Here is an example of how this annotation is meant to be used:</p>
+ * <pre>
+ * public class User {
+ * private String firstName;
+ * private String lastName;
+ * &#64Since(1.0) private String emailAddress;
+ * &#64Since(1.0) private String password;
+ * &#64Since(1.1) private Address address;
+ * }
+ * </pre>
+ *
+ * <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
+ * methods will use all the fields for serialization and deserialization. However, if you created
+ * Gson with {@code Gson gson = new GsonBuilder().setVersion(1.0).create()} then the
+ * {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code address} field
+ * since it's version number is set to {@code 1.1}.</p>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.TYPE})
+public @interface Since {
+ /**
+ * the value indicating a version number since this member
+ * or type has been present.
+ */
+ double value();
+}
diff --git a/gson/src/main/java/com/google/gson/annotations/Until.java b/gson/src/main/java/com/google/gson/annotations/Until.java
new file mode 100644
index 00000000..4648b8a2
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/annotations/Until.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * An annotation that indicates the version number until a member or a type should be present.
+ * Basically, if Gson is created with a version number that exceeds the value stored in the
+ * {@code Until} annotation then the field will be ignored from the JSON output. This annotation
+ * is useful to manage versioning of your JSON classes for a web-service.
+ *
+ * <p>
+ * This annotation has no effect unless you build {@link com.google.gson.Gson} with a
+ * {@link com.google.gson.GsonBuilder} and invoke
+ * {@link com.google.gson.GsonBuilder#setVersion(double)} method.
+ *
+ * <p>Here is an example of how this annotation is meant to be used:</p>
+ * <pre>
+ * public class User {
+ * private String firstName;
+ * private String lastName;
+ * &#64Until(1.1) private String emailAddress;
+ * &#64Until(1.1) private String password;
+ * }
+ * </pre>
+ *
+ * <p>If you created Gson with {@code new Gson()}, the {@code toJson()} and {@code fromJson()}
+ * methods will use all the fields for serialization and deserialization. However, if you created
+ * Gson with {@code Gson gson = new GsonBuilder().setVersion(1.2).create()} then the
+ * {@code toJson()} and {@code fromJson()} methods of Gson will exclude the {@code emailAddress}
+ * and {@code password} fields from the example above, because the version number passed to the
+ * GsonBuilder, {@code 1.2}, exceeds the version number set on the {@code Until} annotation,
+ * {@code 1.1}, for those fields.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @since 1.3
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.TYPE})
+public @interface Until {
+
+ /**
+ * the value indicating a version number until this member
+ * or type should be ignored.
+ */
+ double value();
+}
diff --git a/gson/src/main/java/com/google/gson/annotations/package-info.java b/gson/src/main/java/com/google/gson/annotations/package-info.java
new file mode 100644
index 00000000..1c461fd6
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/annotations/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * This package provides annotations that can be used with {@link com.google.gson.Gson}.
+ *
+ * @author Inderjeet Singh, Joel Leitch
+ */
+package com.google.gson.annotations; \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Preconditions.java b/gson/src/main/java/com/google/gson/internal/$Gson$Preconditions.java
new file mode 100644
index 00000000..f0e7d3fa
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/$Gson$Preconditions.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.internal;
+
+/**
+ * A simple utility class used to check method Preconditions.
+ *
+ * <pre>
+ * public long divideBy(long value) {
+ * Preconditions.checkArgument(value != 0);
+ * return this.value / value;
+ * }
+ * </pre>
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class $Gson$Preconditions {
+ private $Gson$Preconditions() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static <T> T checkNotNull(T obj) {
+ if (obj == null) {
+ throw new NullPointerException();
+ }
+ return obj;
+ }
+
+ public static void checkArgument(boolean condition) {
+ if (!condition) {
+ throw new IllegalArgumentException();
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/$Gson$Types.java b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java
new file mode 100644
index 00000000..745d0719
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/$Gson$Types.java
@@ -0,0 +1,588 @@
+/**
+ * Copyright (C) 2008 Google 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 com.google.gson.internal;
+
+import static com.google.gson.internal.$Gson$Preconditions.checkArgument;
+import static com.google.gson.internal.$Gson$Preconditions.checkNotNull;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.GenericDeclaration;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+
+/**
+ * Static methods for working with types.
+ *
+ * @author Bob Lee
+ * @author Jesse Wilson
+ */
+public final class $Gson$Types {
+ static final Type[] EMPTY_TYPE_ARRAY = new Type[] {};
+
+ private $Gson$Types() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns a new parameterized type, applying {@code typeArguments} to
+ * {@code rawType} and enclosed by {@code ownerType}.
+ *
+ * @return a {@link java.io.Serializable serializable} parameterized type.
+ */
+ public static ParameterizedType newParameterizedTypeWithOwner(
+ Type ownerType, Type rawType, Type... typeArguments) {
+ return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
+ }
+
+ /**
+ * Returns an array type whose elements are all instances of
+ * {@code componentType}.
+ *
+ * @return a {@link java.io.Serializable serializable} generic array type.
+ */
+ public static GenericArrayType arrayOf(Type componentType) {
+ return new GenericArrayTypeImpl(componentType);
+ }
+
+ /**
+ * Returns a type that represents an unknown type that extends {@code bound}.
+ * For example, if {@code bound} is {@code CharSequence.class}, this returns
+ * {@code ? extends CharSequence}. If {@code bound} is {@code Object.class},
+ * this returns {@code ?}, which is shorthand for {@code ? extends Object}.
+ */
+ public static WildcardType subtypeOf(Type bound) {
+ return new WildcardTypeImpl(new Type[] { bound }, EMPTY_TYPE_ARRAY);
+ }
+
+ /**
+ * Returns a type that represents an unknown supertype of {@code bound}. For
+ * example, if {@code bound} is {@code String.class}, this returns {@code ?
+ * super String}.
+ */
+ public static WildcardType supertypeOf(Type bound) {
+ return new WildcardTypeImpl(new Type[] { Object.class }, new Type[] { bound });
+ }
+
+ /**
+ * Returns a type that is functionally equal but not necessarily equal
+ * according to {@link Object#equals(Object) Object.equals()}. The returned
+ * type is {@link java.io.Serializable}.
+ */
+ public static Type canonicalize(Type type) {
+ if (type instanceof Class) {
+ Class<?> c = (Class<?>) type;
+ return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;
+
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType p = (ParameterizedType) type;
+ return new ParameterizedTypeImpl(p.getOwnerType(),
+ p.getRawType(), p.getActualTypeArguments());
+
+ } else if (type instanceof GenericArrayType) {
+ GenericArrayType g = (GenericArrayType) type;
+ return new GenericArrayTypeImpl(g.getGenericComponentType());
+
+ } else if (type instanceof WildcardType) {
+ WildcardType w = (WildcardType) type;
+ return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
+
+ } else {
+ // type is either serializable as-is or unsupported
+ return type;
+ }
+ }
+
+ public static Class<?> getRawType(Type type) {
+ if (type instanceof Class<?>) {
+ // type is a normal class.
+ return (Class<?>) type;
+
+ } else if (type instanceof ParameterizedType) {
+ ParameterizedType parameterizedType = (ParameterizedType) type;
+
+ // I'm not exactly sure why getRawType() returns Type instead of Class.
+ // Neal isn't either but suspects some pathological case related
+ // to nested classes exists.
+ Type rawType = parameterizedType.getRawType();
+ checkArgument(rawType instanceof Class);
+ return (Class<?>) rawType;
+
+ } else if (type instanceof GenericArrayType) {
+ Type componentType = ((GenericArrayType)type).getGenericComponentType();
+ return Array.newInstance(getRawType(componentType), 0).getClass();
+
+ } else if (type instanceof TypeVariable) {
+ // we could use the variable's bounds, but that won't work if there are multiple.
+ // having a raw type that's more general than necessary is okay
+ return Object.class;
+
+ } else if (type instanceof WildcardType) {
+ return getRawType(((WildcardType) type).getUpperBounds()[0]);
+
+ } else {
+ String className = type == null ? "null" : type.getClass().getName();
+ throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
+ + "GenericArrayType, but <" + type + "> is of type " + className);
+ }
+ }
+
+ static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ /**
+ * Returns true if {@code a} and {@code b} are equal.
+ */
+ public static boolean equals(Type a, Type b) {
+ if (a == b) {
+ // also handles (a == null && b == null)
+ return true;
+
+ } else if (a instanceof Class) {
+ // Class already specifies equals().
+ return a.equals(b);
+
+ } else if (a instanceof ParameterizedType) {
+ if (!(b instanceof ParameterizedType)) {
+ return false;
+ }
+
+ // TODO: save a .clone() call
+ ParameterizedType pa = (ParameterizedType) a;
+ ParameterizedType pb = (ParameterizedType) b;
+ return equal(pa.getOwnerType(), pb.getOwnerType())
+ && pa.getRawType().equals(pb.getRawType())
+ && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());
+
+ } else if (a instanceof GenericArrayType) {
+ if (!(b instanceof GenericArrayType)) {
+ return false;
+ }
+
+ GenericArrayType ga = (GenericArrayType) a;
+ GenericArrayType gb = (GenericArrayType) b;
+ return equals(ga.getGenericComponentType(), gb.getGenericComponentType());
+
+ } else if (a instanceof WildcardType) {
+ if (!(b instanceof WildcardType)) {
+ return false;
+ }
+
+ WildcardType wa = (WildcardType) a;
+ WildcardType wb = (WildcardType) b;
+ return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
+ && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());
+
+ } else if (a instanceof TypeVariable) {
+ if (!(b instanceof TypeVariable)) {
+ return false;
+ }
+ TypeVariable<?> va = (TypeVariable<?>) a;
+ TypeVariable<?> vb = (TypeVariable<?>) b;
+ return va.getGenericDeclaration() == vb.getGenericDeclaration()
+ && va.getName().equals(vb.getName());
+
+ } else {
+ // This isn't a type we support. Could be a generic array type, wildcard type, etc.
+ return false;
+ }
+ }
+
+ private static int hashCodeOrZero(Object o) {
+ return o != null ? o.hashCode() : 0;
+ }
+
+ public static String typeToString(Type type) {
+ return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
+ }
+
+ /**
+ * Returns the generic supertype for {@code supertype}. For example, given a class {@code
+ * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the
+ * result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
+ */
+ static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
+ if (toResolve == rawType) {
+ return context;
+ }
+
+ // we skip searching through interfaces if unknown is an interface
+ if (toResolve.isInterface()) {
+ Class<?>[] interfaces = rawType.getInterfaces();
+ for (int i = 0, length = interfaces.length; i < length; i++) {
+ if (interfaces[i] == toResolve) {
+ return rawType.getGenericInterfaces()[i];
+ } else if (toResolve.isAssignableFrom(interfaces[i])) {
+ return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
+ }
+ }
+ }
+
+ // check our supertypes
+ if (!rawType.isInterface()) {
+ while (rawType != Object.class) {
+ Class<?> rawSupertype = rawType.getSuperclass();
+ if (rawSupertype == toResolve) {
+ return rawType.getGenericSuperclass();
+ } else if (toResolve.isAssignableFrom(rawSupertype)) {
+ return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
+ }
+ rawType = rawSupertype;
+ }
+ }
+
+ // we can't resolve this further
+ return toResolve;
+ }
+
+ /**
+ * Returns the generic form of {@code supertype}. For example, if this is {@code
+ * ArrayList<String>}, this returns {@code Iterable<String>} given the input {@code
+ * Iterable.class}.
+ *
+ * @param supertype a superclass of, or interface implemented by, this.
+ */
+ static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
+ checkArgument(supertype.isAssignableFrom(contextRawType));
+ return resolve(context, contextRawType,
+ $Gson$Types.getGenericSupertype(context, contextRawType, supertype));
+ }
+
+ /**
+ * Returns the component type of this array type.
+ * @throws ClassCastException if this type is not an array.
+ */
+ public static Type getArrayComponentType(Type array) {
+ return array instanceof GenericArrayType
+ ? ((GenericArrayType) array).getGenericComponentType()
+ : ((Class<?>) array).getComponentType();
+ }
+
+ /**
+ * Returns the element type of this collection type.
+ * @throws IllegalArgumentException if this type is not a collection.
+ */
+ public static Type getCollectionElementType(Type context, Class<?> contextRawType) {
+ Type collectionType = getSupertype(context, contextRawType, Collection.class);
+
+ if (collectionType instanceof WildcardType) {
+ collectionType = ((WildcardType)collectionType).getUpperBounds()[0];
+ }
+ if (collectionType instanceof ParameterizedType) {
+ return ((ParameterizedType) collectionType).getActualTypeArguments()[0];
+ }
+ return Object.class;
+ }
+
+ /**
+ * Returns a two element array containing this map's key and value types in
+ * positions 0 and 1 respectively.
+ */
+ public static Type[] getMapKeyAndValueTypes(Type context, Class<?> contextRawType) {
+ /*
+ * Work around a problem with the declaration of java.util.Properties. That
+ * class should extend Hashtable<String, String>, but it's declared to
+ * extend Hashtable<Object, Object>.
+ */
+ if (context == Properties.class) {
+ return new Type[] { String.class, String.class }; // TODO: test subclasses of Properties!
+ }
+
+ Type mapType = getSupertype(context, contextRawType, Map.class);
+ // TODO: strip wildcards?
+ if (mapType instanceof ParameterizedType) {
+ ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
+ return mapParameterizedType.getActualTypeArguments();
+ }
+ return new Type[] { Object.class, Object.class };
+ }
+
+ public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
+ // this implementation is made a little more complicated in an attempt to avoid object-creation
+ while (true) {
+ if (toResolve instanceof TypeVariable) {
+ TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
+ toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
+ if (toResolve == typeVariable) {
+ return toResolve;
+ }
+
+ } else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
+ Class<?> original = (Class<?>) toResolve;
+ Type componentType = original.getComponentType();
+ Type newComponentType = resolve(context, contextRawType, componentType);
+ return componentType == newComponentType
+ ? original
+ : arrayOf(newComponentType);
+
+ } else if (toResolve instanceof GenericArrayType) {
+ GenericArrayType original = (GenericArrayType) toResolve;
+ Type componentType = original.getGenericComponentType();
+ Type newComponentType = resolve(context, contextRawType, componentType);
+ return componentType == newComponentType
+ ? original
+ : arrayOf(newComponentType);
+
+ } else if (toResolve instanceof ParameterizedType) {
+ ParameterizedType original = (ParameterizedType) toResolve;
+ Type ownerType = original.getOwnerType();
+ Type newOwnerType = resolve(context, contextRawType, ownerType);
+ boolean changed = newOwnerType != ownerType;
+
+ Type[] args = original.getActualTypeArguments();
+ for (int t = 0, length = args.length; t < length; t++) {
+ Type resolvedTypeArgument = resolve(context, contextRawType, args[t]);
+ if (resolvedTypeArgument != args[t]) {
+ if (!changed) {
+ args = args.clone();
+ changed = true;
+ }
+ args[t] = resolvedTypeArgument;
+ }
+ }
+
+ return changed
+ ? newParameterizedTypeWithOwner(newOwnerType, original.getRawType(), args)
+ : original;
+
+ } else if (toResolve instanceof WildcardType) {
+ WildcardType original = (WildcardType) toResolve;
+ Type[] originalLowerBound = original.getLowerBounds();
+ Type[] originalUpperBound = original.getUpperBounds();
+
+ if (originalLowerBound.length == 1) {
+ Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]);
+ if (lowerBound != originalLowerBound[0]) {
+ return supertypeOf(lowerBound);
+ }
+ } else if (originalUpperBound.length == 1) {
+ Type upperBound = resolve(context, contextRawType, originalUpperBound[0]);
+ if (upperBound != originalUpperBound[0]) {
+ return subtypeOf(upperBound);
+ }
+ }
+ return original;
+
+ } else {
+ return toResolve;
+ }
+ }
+ }
+
+ static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
+ Class<?> declaredByRaw = declaringClassOf(unknown);
+
+ // we can't reduce this further
+ if (declaredByRaw == null) {
+ return unknown;
+ }
+
+ Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
+ if (declaredBy instanceof ParameterizedType) {
+ int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
+ return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
+ }
+
+ return unknown;
+ }
+
+ private static int indexOf(Object[] array, Object toFind) {
+ for (int i = 0; i < array.length; i++) {
+ if (toFind.equals(array[i])) {
+ return i;
+ }
+ }
+ throw new NoSuchElementException();
+ }
+
+ /**
+ * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
+ * a class.
+ */
+ private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
+ GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
+ return genericDeclaration instanceof Class
+ ? (Class<?>) genericDeclaration
+ : null;
+ }
+
+ private static void checkNotPrimitive(Type type) {
+ checkArgument(!(type instanceof Class<?>) || !((Class<?>) type).isPrimitive());
+ }
+
+ private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable {
+ private final Type ownerType;
+ private final Type rawType;
+ private final Type[] typeArguments;
+
+ public ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
+ // require an owner type if the raw type needs it
+ if (rawType instanceof Class<?>) {
+ Class<?> rawTypeAsClass = (Class<?>) rawType;
+ boolean isStaticOrTopLevelClass = Modifier.isStatic(rawTypeAsClass.getModifiers())
+ || rawTypeAsClass.getEnclosingClass() == null;
+ checkArgument(ownerType != null || isStaticOrTopLevelClass);
+ }
+
+ this.ownerType = ownerType == null ? null : canonicalize(ownerType);
+ this.rawType = canonicalize(rawType);
+ this.typeArguments = typeArguments.clone();
+ for (int t = 0; t < this.typeArguments.length; t++) {
+ checkNotNull(this.typeArguments[t]);
+ checkNotPrimitive(this.typeArguments[t]);
+ this.typeArguments[t] = canonicalize(this.typeArguments[t]);
+ }
+ }
+
+ public Type[] getActualTypeArguments() {
+ return typeArguments.clone();
+ }
+
+ public Type getRawType() {
+ return rawType;
+ }
+
+ public Type getOwnerType() {
+ return ownerType;
+ }
+
+ @Override public boolean equals(Object other) {
+ return other instanceof ParameterizedType
+ && $Gson$Types.equals(this, (ParameterizedType) other);
+ }
+
+ @Override public int hashCode() {
+ return Arrays.hashCode(typeArguments)
+ ^ rawType.hashCode()
+ ^ hashCodeOrZero(ownerType);
+ }
+
+ @Override public String toString() {
+ StringBuilder stringBuilder = new StringBuilder(30 * (typeArguments.length + 1));
+ stringBuilder.append(typeToString(rawType));
+
+ if (typeArguments.length == 0) {
+ return stringBuilder.toString();
+ }
+
+ stringBuilder.append("<").append(typeToString(typeArguments[0]));
+ for (int i = 1; i < typeArguments.length; i++) {
+ stringBuilder.append(", ").append(typeToString(typeArguments[i]));
+ }
+ return stringBuilder.append(">").toString();
+ }
+
+ private static final long serialVersionUID = 0;
+ }
+
+ private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable {
+ private final Type componentType;
+
+ public GenericArrayTypeImpl(Type componentType) {
+ this.componentType = canonicalize(componentType);
+ }
+
+ public Type getGenericComponentType() {
+ return componentType;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof GenericArrayType
+ && $Gson$Types.equals(this, (GenericArrayType) o);
+ }
+
+ @Override public int hashCode() {
+ return componentType.hashCode();
+ }
+
+ @Override public String toString() {
+ return typeToString(componentType) + "[]";
+ }
+
+ private static final long serialVersionUID = 0;
+ }
+
+ /**
+ * The WildcardType interface supports multiple upper bounds and multiple
+ * lower bounds. We only support what the Java 6 language needs - at most one
+ * bound. If a lower bound is set, the upper bound must be Object.class.
+ */
+ private static final class WildcardTypeImpl implements WildcardType, Serializable {
+ private final Type upperBound;
+ private final Type lowerBound;
+
+ public WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
+ checkArgument(lowerBounds.length <= 1);
+ checkArgument(upperBounds.length == 1);
+
+ if (lowerBounds.length == 1) {
+ checkNotNull(lowerBounds[0]);
+ checkNotPrimitive(lowerBounds[0]);
+ checkArgument(upperBounds[0] == Object.class);
+ this.lowerBound = canonicalize(lowerBounds[0]);
+ this.upperBound = Object.class;
+
+ } else {
+ checkNotNull(upperBounds[0]);
+ checkNotPrimitive(upperBounds[0]);
+ this.lowerBound = null;
+ this.upperBound = canonicalize(upperBounds[0]);
+ }
+ }
+
+ public Type[] getUpperBounds() {
+ return new Type[] { upperBound };
+ }
+
+ public Type[] getLowerBounds() {
+ return lowerBound != null ? new Type[] { lowerBound } : EMPTY_TYPE_ARRAY;
+ }
+
+ @Override public boolean equals(Object other) {
+ return other instanceof WildcardType
+ && $Gson$Types.equals(this, (WildcardType) other);
+ }
+
+ @Override public int hashCode() {
+ // this equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds());
+ return (lowerBound != null ? 31 + lowerBound.hashCode() : 1)
+ ^ (31 + upperBound.hashCode());
+ }
+
+ @Override public String toString() {
+ if (lowerBound != null) {
+ return "? super " + typeToString(lowerBound);
+ } else if (upperBound == Object.class) {
+ return "?";
+ } else {
+ return "? extends " + typeToString(upperBound);
+ }
+ }
+
+ private static final long serialVersionUID = 0;
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java
new file mode 100644
index 00000000..62bae91c
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal;
+
+import com.google.gson.InstanceCreator;
+import com.google.gson.JsonIOException;
+import com.google.gson.reflect.TypeToken;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Returns a function that can construct an instance of a requested type.
+ */
+public final class ConstructorConstructor {
+ private final Map<Type, InstanceCreator<?>> instanceCreators;
+
+ public ConstructorConstructor(Map<Type, InstanceCreator<?>> instanceCreators) {
+ this.instanceCreators = instanceCreators;
+ }
+
+ public <T> ObjectConstructor<T> get(TypeToken<T> typeToken) {
+ final Type type = typeToken.getType();
+ final Class<? super T> rawType = typeToken.getRawType();
+
+ // first try an instance creator
+
+ @SuppressWarnings("unchecked") // types must agree
+ final InstanceCreator<T> typeCreator = (InstanceCreator<T>) instanceCreators.get(type);
+ if (typeCreator != null) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return typeCreator.createInstance(type);
+ }
+ };
+ }
+
+ // Next try raw type match for instance creators
+ @SuppressWarnings("unchecked") // types must agree
+ final InstanceCreator<T> rawTypeCreator =
+ (InstanceCreator<T>) instanceCreators.get(rawType);
+ if (rawTypeCreator != null) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return rawTypeCreator.createInstance(type);
+ }
+ };
+ }
+
+ ObjectConstructor<T> defaultConstructor = newDefaultConstructor(rawType);
+ if (defaultConstructor != null) {
+ return defaultConstructor;
+ }
+
+ ObjectConstructor<T> defaultImplementation = newDefaultImplementationConstructor(type, rawType);
+ if (defaultImplementation != null) {
+ return defaultImplementation;
+ }
+
+ // finally try unsafe
+ return newUnsafeAllocator(type, rawType);
+ }
+
+ private <T> ObjectConstructor<T> newDefaultConstructor(Class<? super T> rawType) {
+ try {
+ final Constructor<? super T> constructor = rawType.getDeclaredConstructor();
+ if (!constructor.isAccessible()) {
+ constructor.setAccessible(true);
+ }
+ return new ObjectConstructor<T>() {
+ @SuppressWarnings("unchecked") // T is the same raw type as is requested
+ public T construct() {
+ try {
+ Object[] args = null;
+ return (T) constructor.newInstance(args);
+ } catch (InstantiationException e) {
+ // TODO: JsonParseException ?
+ throw new RuntimeException("Failed to invoke " + constructor + " with no args", e);
+ } catch (InvocationTargetException e) {
+ // TODO: don't wrap if cause is unchecked!
+ // TODO: JsonParseException ?
+ throw new RuntimeException("Failed to invoke " + constructor + " with no args",
+ e.getTargetException());
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ }
+ };
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Constructors for common interface types like Map and List and their
+ * subtypes.
+ */
+ @SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
+ private <T> ObjectConstructor<T> newDefaultImplementationConstructor(
+ final Type type, Class<? super T> rawType) {
+ if (Collection.class.isAssignableFrom(rawType)) {
+ if (SortedSet.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new TreeSet<Object>();
+ }
+ };
+ } else if (EnumSet.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor<T>() {
+ @SuppressWarnings("rawtypes")
+ public T construct() {
+ if (type instanceof ParameterizedType) {
+ Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
+ if (elementType instanceof Class) {
+ return (T) EnumSet.noneOf((Class)elementType);
+ } else {
+ throw new JsonIOException("Invalid EnumSet type: " + type.toString());
+ }
+ } else {
+ throw new JsonIOException("Invalid EnumSet type: " + type.toString());
+ }
+ }
+ };
+ } else if (Set.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new LinkedHashSet<Object>();
+ }
+ };
+ } else if (Queue.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new LinkedList<Object>();
+ }
+ };
+ } else {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new ArrayList<Object>();
+ }
+ };
+ }
+ }
+
+ if (Map.class.isAssignableFrom(rawType)) {
+ if (SortedMap.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new TreeMap<Object, Object>();
+ }
+ };
+ } else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
+ TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new LinkedHashMap<Object, Object>();
+ }
+ };
+ } else {
+ return new ObjectConstructor<T>() {
+ public T construct() {
+ return (T) new LinkedTreeMap<String, Object>();
+ }
+ };
+ }
+ }
+
+ return null;
+ }
+
+ private <T> ObjectConstructor<T> newUnsafeAllocator(
+ final Type type, final Class<? super T> rawType) {
+ return new ObjectConstructor<T>() {
+ private final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create();
+ @SuppressWarnings("unchecked")
+ public T construct() {
+ try {
+ Object newInstance = unsafeAllocator.newInstance(rawType);
+ return (T) newInstance;
+ } catch (Exception e) {
+ throw new RuntimeException(("Unable to invoke no-args constructor for " + type + ". "
+ + "Register an InstanceCreator with Gson for this type may fix this problem."), e);
+ }
+ }
+ };
+ }
+
+ @Override public String toString() {
+ return instanceCreators.toString();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/Excluder.java b/gson/src/main/java/com/google/gson/internal/Excluder.java
new file mode 100644
index 00000000..1c71e3e7
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/Excluder.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.internal;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.Since;
+import com.google.gson.annotations.Until;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class selects which fields and types to omit. It is configurable,
+ * supporting version attributes {@link Since} and {@link Until}, modifiers,
+ * synthetic fields, anonymous and local classes, inner classes, and fields with
+ * the {@link Expose} annotation.
+ *
+ * <p>This class is a type adapter factory; types that are excluded will be
+ * adapted to null. It may delegate to another type adapter if only one
+ * direction is excluded.
+ *
+ * @author Joel Leitch
+ * @author Jesse Wilson
+ */
+public final class Excluder implements TypeAdapterFactory, Cloneable {
+ private static final double IGNORE_VERSIONS = -1.0d;
+ public static final Excluder DEFAULT = new Excluder();
+
+ private double version = IGNORE_VERSIONS;
+ private int modifiers = Modifier.TRANSIENT | Modifier.STATIC;
+ private boolean serializeInnerClasses = true;
+ private boolean requireExpose;
+ private List<ExclusionStrategy> serializationStrategies = Collections.emptyList();
+ private List<ExclusionStrategy> deserializationStrategies = Collections.emptyList();
+
+ @Override protected Excluder clone() {
+ try {
+ return (Excluder) super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError();
+ }
+ }
+
+ public Excluder withVersion(double ignoreVersionsAfter) {
+ Excluder result = clone();
+ result.version = ignoreVersionsAfter;
+ return result;
+ }
+
+ public Excluder withModifiers(int... modifiers) {
+ Excluder result = clone();
+ result.modifiers = 0;
+ for (int modifier : modifiers) {
+ result.modifiers |= modifier;
+ }
+ return result;
+ }
+
+ public Excluder disableInnerClassSerialization() {
+ Excluder result = clone();
+ result.serializeInnerClasses = false;
+ return result;
+ }
+
+ public Excluder excludeFieldsWithoutExposeAnnotation() {
+ Excluder result = clone();
+ result.requireExpose = true;
+ return result;
+ }
+
+ public Excluder withExclusionStrategy(ExclusionStrategy exclusionStrategy,
+ boolean serialization, boolean deserialization) {
+ Excluder result = clone();
+ if (serialization) {
+ result.serializationStrategies = new ArrayList<ExclusionStrategy>(serializationStrategies);
+ result.serializationStrategies.add(exclusionStrategy);
+ }
+ if (deserialization) {
+ result.deserializationStrategies
+ = new ArrayList<ExclusionStrategy>(deserializationStrategies);
+ result.deserializationStrategies.add(exclusionStrategy);
+ }
+ return result;
+ }
+
+ public <T> TypeAdapter<T> create(final Gson gson, final TypeToken<T> type) {
+ Class<?> rawType = type.getRawType();
+ final boolean skipSerialize = excludeClass(rawType, true);
+ final boolean skipDeserialize = excludeClass(rawType, false);
+
+ if (!skipSerialize && !skipDeserialize) {
+ return null;
+ }
+
+ return new TypeAdapter<T>() {
+ /** The delegate is lazily created because it may not be needed, and creating it may fail. */
+ private TypeAdapter<T> delegate;
+
+ @Override public T read(JsonReader in) throws IOException {
+ if (skipDeserialize) {
+ in.skipValue();
+ return null;
+ }
+ return delegate().read(in);
+ }
+
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ if (skipSerialize) {
+ out.nullValue();
+ return;
+ }
+ delegate().write(out, value);
+ }
+
+ private TypeAdapter<T> delegate() {
+ TypeAdapter<T> d = delegate;
+ return d != null
+ ? d
+ : (delegate = gson.getDelegateAdapter(Excluder.this, type));
+ }
+ };
+ }
+
+ public boolean excludeField(Field field, boolean serialize) {
+ if ((modifiers & field.getModifiers()) != 0) {
+ return true;
+ }
+
+ if (version != Excluder.IGNORE_VERSIONS
+ && !isValidVersion(field.getAnnotation(Since.class), field.getAnnotation(Until.class))) {
+ return true;
+ }
+
+ if (field.isSynthetic()) {
+ return true;
+ }
+
+ if (requireExpose) {
+ Expose annotation = field.getAnnotation(Expose.class);
+ if (annotation == null || (serialize ? !annotation.serialize() : !annotation.deserialize())) {
+ return true;
+ }
+ }
+
+ if (!serializeInnerClasses && isInnerClass(field.getType())) {
+ return true;
+ }
+
+ if (isAnonymousOrLocal(field.getType())) {
+ return true;
+ }
+
+ List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
+ if (!list.isEmpty()) {
+ FieldAttributes fieldAttributes = new FieldAttributes(field);
+ for (ExclusionStrategy exclusionStrategy : list) {
+ if (exclusionStrategy.shouldSkipField(fieldAttributes)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ public boolean excludeClass(Class<?> clazz, boolean serialize) {
+ if (version != Excluder.IGNORE_VERSIONS
+ && !isValidVersion(clazz.getAnnotation(Since.class), clazz.getAnnotation(Until.class))) {
+ return true;
+ }
+
+ if (!serializeInnerClasses && isInnerClass(clazz)) {
+ return true;
+ }
+
+ if (isAnonymousOrLocal(clazz)) {
+ return true;
+ }
+
+ List<ExclusionStrategy> list = serialize ? serializationStrategies : deserializationStrategies;
+ for (ExclusionStrategy exclusionStrategy : list) {
+ if (exclusionStrategy.shouldSkipClass(clazz)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isAnonymousOrLocal(Class<?> clazz) {
+ return !Enum.class.isAssignableFrom(clazz)
+ && (clazz.isAnonymousClass() || clazz.isLocalClass());
+ }
+
+ private boolean isInnerClass(Class<?> clazz) {
+ return clazz.isMemberClass() && !isStatic(clazz);
+ }
+
+ private boolean isStatic(Class<?> clazz) {
+ return (clazz.getModifiers() & Modifier.STATIC) != 0;
+ }
+
+ private boolean isValidVersion(Since since, Until until) {
+ return isValidSince(since) && isValidUntil(until);
+ }
+
+ private boolean isValidSince(Since annotation) {
+ if (annotation != null) {
+ double annotationVersion = annotation.value();
+ if (annotationVersion > version) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean isValidUntil(Until annotation) {
+ if (annotation != null) {
+ double annotationVersion = annotation.value();
+ if (annotationVersion <= version) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/JsonReaderInternalAccess.java b/gson/src/main/java/com/google/gson/internal/JsonReaderInternalAccess.java
new file mode 100644
index 00000000..bbd47204
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/JsonReaderInternalAccess.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal;
+
+import com.google.gson.stream.JsonReader;
+import java.io.IOException;
+
+/**
+ * Internal-only APIs of JsonReader available only to other classes in Gson.
+ */
+public abstract class JsonReaderInternalAccess {
+ public static JsonReaderInternalAccess INSTANCE;
+
+ /**
+ * Changes the type of the current property name token to a string value.
+ */
+ public abstract void promoteNameToValue(JsonReader reader) throws IOException;
+}
diff --git a/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java b/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java
new file mode 100644
index 00000000..3669af7b
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/LazilyParsedNumber.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal;
+
+import java.io.ObjectStreamException;
+import java.math.BigDecimal;
+
+/**
+ * This class holds a number value that is lazily converted to a specific number type
+ *
+ * @author Inderjeet Singh
+ */
+public final class LazilyParsedNumber extends Number {
+ private final String value;
+
+ /** @param value must not be null */
+ public LazilyParsedNumber(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public int intValue() {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ try {
+ return (int) Long.parseLong(value);
+ } catch (NumberFormatException nfe) {
+ return new BigDecimal(value).intValue();
+ }
+ }
+ }
+
+ @Override
+ public long longValue() {
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ return new BigDecimal(value).longValue();
+ }
+ }
+
+ @Override
+ public float floatValue() {
+ return Float.parseFloat(value);
+ }
+
+ @Override
+ public double doubleValue() {
+ return Double.parseDouble(value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+
+ /**
+ * If somebody is unlucky enough to have to serialize one of these, serialize
+ * it as a BigDecimal so that they won't need Gson on the other side to
+ * deserialize it.
+ */
+ private Object writeReplace() throws ObjectStreamException {
+ return new BigDecimal(value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof LazilyParsedNumber) {
+ LazilyParsedNumber other = (LazilyParsedNumber) obj;
+ return value == other.value || value.equals(other.value);
+ }
+ return false;
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java
new file mode 100644
index 00000000..e251ec2f
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/LinkedHashTreeMap.java
@@ -0,0 +1,861 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 Google 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 com.google.gson.internal;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A map of comparable keys to values. Unlike {@code TreeMap}, this class uses
+ * insertion order for iteration order. Comparison order is only used as an
+ * optimization for efficient insertion and removal.
+ *
+ * <p>This implementation was derived from Android 4.1's TreeMap and
+ * LinkedHashMap classes.
+ */
+public final class LinkedHashTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
+ @SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>>
+ private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
+ public int compare(Comparable a, Comparable b) {
+ return a.compareTo(b);
+ }
+ };
+
+ Comparator<? super K> comparator;
+ Node<K, V>[] table;
+ final Node<K, V> header;
+ int size = 0;
+ int modCount = 0;
+ int threshold;
+
+ /**
+ * Create a natural order, empty tree map whose keys must be mutually
+ * comparable and non-null.
+ */
+ @SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
+ public LinkedHashTreeMap() {
+ this((Comparator<? super K>) NATURAL_ORDER);
+ }
+
+ /**
+ * Create a tree map ordered by {@code comparator}. This map's keys may only
+ * be null if {@code comparator} permits.
+ *
+ * @param comparator the comparator to order elements with, or {@code null} to
+ * use the natural ordering.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable
+ public LinkedHashTreeMap(Comparator<? super K> comparator) {
+ this.comparator = comparator != null
+ ? comparator
+ : (Comparator) NATURAL_ORDER;
+ this.header = new Node<K, V>();
+ this.table = new Node[16]; // TODO: sizing/resizing policies
+ this.threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity
+ }
+
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public V get(Object key) {
+ Node<K, V> node = findByObject(key);
+ return node != null ? node.value : null;
+ }
+
+ @Override public boolean containsKey(Object key) {
+ return findByObject(key) != null;
+ }
+
+ @Override public V put(K key, V value) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+ Node<K, V> created = find(key, true);
+ V result = created.value;
+ created.value = value;
+ return result;
+ }
+
+ @Override public void clear() {
+ Arrays.fill(table, null);
+ size = 0;
+ modCount++;
+
+ // Clear all links to help GC
+ Node<K, V> header = this.header;
+ for (Node<K, V> e = header.next; e != header; ) {
+ Node<K, V> next = e.next;
+ e.next = e.prev = null;
+ e = next;
+ }
+
+ header.next = header.prev = header;
+ }
+
+ @Override public V remove(Object key) {
+ Node<K, V> node = removeInternalByKey(key);
+ return node != null ? node.value : null;
+ }
+
+ /**
+ * Returns the node at or adjacent to the given key, creating it if requested.
+ *
+ * @throws ClassCastException if {@code key} and the tree's keys aren't
+ * mutually comparable.
+ */
+ Node<K, V> find(K key, boolean create) {
+ Comparator<? super K> comparator = this.comparator;
+ Node<K, V>[] table = this.table;
+ int hash = secondaryHash(key.hashCode());
+ int index = hash & (table.length - 1);
+ Node<K, V> nearest = table[index];
+ int comparison = 0;
+
+ if (nearest != null) {
+ // Micro-optimization: avoid polymorphic calls to Comparator.compare().
+ @SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
+ Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
+ ? (Comparable<Object>) key
+ : null;
+
+ while (true) {
+ comparison = (comparableKey != null)
+ ? comparableKey.compareTo(nearest.key)
+ : comparator.compare(key, nearest.key);
+
+ // We found the requested key.
+ if (comparison == 0) {
+ return nearest;
+ }
+
+ // If it exists, the key is in a subtree. Go deeper.
+ Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;
+ if (child == null) {
+ break;
+ }
+
+ nearest = child;
+ }
+ }
+
+ // The key doesn't exist in this tree.
+ if (!create) {
+ return null;
+ }
+
+ // Create the node and add it to the tree or the table.
+ Node<K, V> header = this.header;
+ Node<K, V> created;
+ if (nearest == null) {
+ // Check that the value is comparable if we didn't do any comparisons.
+ if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
+ throw new ClassCastException(key.getClass().getName() + " is not Comparable");
+ }
+ created = new Node<K, V>(nearest, key, hash, header, header.prev);
+ table[index] = created;
+ } else {
+ created = new Node<K, V>(nearest, key, hash, header, header.prev);
+ if (comparison < 0) { // nearest.key is higher
+ nearest.left = created;
+ } else { // comparison > 0, nearest.key is lower
+ nearest.right = created;
+ }
+ rebalance(nearest, true);
+ }
+
+ if (size++ > threshold) {
+ doubleCapacity();
+ }
+ modCount++;
+
+ return created;
+ }
+
+ @SuppressWarnings("unchecked")
+ Node<K, V> findByObject(Object key) {
+ try {
+ return key != null ? find((K) key, false) : null;
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns this map's entry that has the same key and value as {@code
+ * entry}, or null if this map has no such entry.
+ *
+ * <p>This method uses the comparator for key equality rather than {@code
+ * equals}. If this map's comparator isn't consistent with equals (such as
+ * {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code
+ * contains()} will violate the collections API.
+ */
+ Node<K, V> findByEntry(Entry<?, ?> entry) {
+ Node<K, V> mine = findByObject(entry.getKey());
+ boolean valuesEqual = mine != null && equal(mine.value, entry.getValue());
+ return valuesEqual ? mine : null;
+ }
+
+ private boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ /**
+ * Applies a supplemental hash function to a given hashCode, which defends
+ * against poor quality hash functions. This is critical because HashMap
+ * uses power-of-two length hash tables, that otherwise encounter collisions
+ * for hashCodes that do not differ in lower or upper bits.
+ */
+ private static int secondaryHash(int h) {
+ // Doug Lea's supplemental hash function
+ h ^= (h >>> 20) ^ (h >>> 12);
+ return h ^ (h >>> 7) ^ (h >>> 4);
+ }
+
+ /**
+ * Removes {@code node} from this tree, rearranging the tree's structure as
+ * necessary.
+ *
+ * @param unlink true to also unlink this node from the iteration linked list.
+ */
+ void removeInternal(Node<K, V> node, boolean unlink) {
+ if (unlink) {
+ node.prev.next = node.next;
+ node.next.prev = node.prev;
+ node.next = node.prev = null; // Help the GC (for performance)
+ }
+
+ Node<K, V> left = node.left;
+ Node<K, V> right = node.right;
+ Node<K, V> originalParent = node.parent;
+ if (left != null && right != null) {
+
+ /*
+ * To remove a node with both left and right subtrees, move an
+ * adjacent node from one of those subtrees into this node's place.
+ *
+ * Removing the adjacent node may change this node's subtrees. This
+ * node may no longer have two subtrees once the adjacent node is
+ * gone!
+ */
+
+ Node<K, V> adjacent = (left.height > right.height) ? left.last() : right.first();
+ removeInternal(adjacent, false); // takes care of rebalance and size--
+
+ int leftHeight = 0;
+ left = node.left;
+ if (left != null) {
+ leftHeight = left.height;
+ adjacent.left = left;
+ left.parent = adjacent;
+ node.left = null;
+ }
+ int rightHeight = 0;
+ right = node.right;
+ if (right != null) {
+ rightHeight = right.height;
+ adjacent.right = right;
+ right.parent = adjacent;
+ node.right = null;
+ }
+ adjacent.height = Math.max(leftHeight, rightHeight) + 1;
+ replaceInParent(node, adjacent);
+ return;
+ } else if (left != null) {
+ replaceInParent(node, left);
+ node.left = null;
+ } else if (right != null) {
+ replaceInParent(node, right);
+ node.right = null;
+ } else {
+ replaceInParent(node, null);
+ }
+
+ rebalance(originalParent, false);
+ size--;
+ modCount++;
+ }
+
+ Node<K, V> removeInternalByKey(Object key) {
+ Node<K, V> node = findByObject(key);
+ if (node != null) {
+ removeInternal(node, true);
+ }
+ return node;
+ }
+
+ private void replaceInParent(Node<K, V> node, Node<K, V> replacement) {
+ Node<K, V> parent = node.parent;
+ node.parent = null;
+ if (replacement != null) {
+ replacement.parent = parent;
+ }
+
+ if (parent != null) {
+ if (parent.left == node) {
+ parent.left = replacement;
+ } else {
+ assert (parent.right == node);
+ parent.right = replacement;
+ }
+ } else {
+ int index = node.hash & (table.length - 1);
+ table[index] = replacement;
+ }
+ }
+
+ /**
+ * Rebalances the tree by making any AVL rotations necessary between the
+ * newly-unbalanced node and the tree's root.
+ *
+ * @param insert true if the node was unbalanced by an insert; false if it
+ * was by a removal.
+ */
+ private void rebalance(Node<K, V> unbalanced, boolean insert) {
+ for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
+ Node<K, V> left = node.left;
+ Node<K, V> right = node.right;
+ int leftHeight = left != null ? left.height : 0;
+ int rightHeight = right != null ? right.height : 0;
+
+ int delta = leftHeight - rightHeight;
+ if (delta == -2) {
+ Node<K, V> rightLeft = right.left;
+ Node<K, V> rightRight = right.right;
+ int rightRightHeight = rightRight != null ? rightRight.height : 0;
+ int rightLeftHeight = rightLeft != null ? rightLeft.height : 0;
+
+ int rightDelta = rightLeftHeight - rightRightHeight;
+ if (rightDelta == -1 || (rightDelta == 0 && !insert)) {
+ rotateLeft(node); // AVL right right
+ } else {
+ assert (rightDelta == 1);
+ rotateRight(right); // AVL right left
+ rotateLeft(node);
+ }
+ if (insert) {
+ break; // no further rotations will be necessary
+ }
+
+ } else if (delta == 2) {
+ Node<K, V> leftLeft = left.left;
+ Node<K, V> leftRight = left.right;
+ int leftRightHeight = leftRight != null ? leftRight.height : 0;
+ int leftLeftHeight = leftLeft != null ? leftLeft.height : 0;
+
+ int leftDelta = leftLeftHeight - leftRightHeight;
+ if (leftDelta == 1 || (leftDelta == 0 && !insert)) {
+ rotateRight(node); // AVL left left
+ } else {
+ assert (leftDelta == -1);
+ rotateLeft(left); // AVL left right
+ rotateRight(node);
+ }
+ if (insert) {
+ break; // no further rotations will be necessary
+ }
+
+ } else if (delta == 0) {
+ node.height = leftHeight + 1; // leftHeight == rightHeight
+ if (insert) {
+ break; // the insert caused balance, so rebalancing is done!
+ }
+
+ } else {
+ assert (delta == -1 || delta == 1);
+ node.height = Math.max(leftHeight, rightHeight) + 1;
+ if (!insert) {
+ break; // the height hasn't changed, so rebalancing is done!
+ }
+ }
+ }
+ }
+
+ /**
+ * Rotates the subtree so that its root's right child is the new root.
+ */
+ private void rotateLeft(Node<K, V> root) {
+ Node<K, V> left = root.left;
+ Node<K, V> pivot = root.right;
+ Node<K, V> pivotLeft = pivot.left;
+ Node<K, V> pivotRight = pivot.right;
+
+ // move the pivot's left child to the root's right
+ root.right = pivotLeft;
+ if (pivotLeft != null) {
+ pivotLeft.parent = root;
+ }
+
+ replaceInParent(root, pivot);
+
+ // move the root to the pivot's left
+ pivot.left = root;
+ root.parent = pivot;
+
+ // fix heights
+ root.height = Math.max(left != null ? left.height : 0,
+ pivotLeft != null ? pivotLeft.height : 0) + 1;
+ pivot.height = Math.max(root.height,
+ pivotRight != null ? pivotRight.height : 0) + 1;
+ }
+
+ /**
+ * Rotates the subtree so that its root's left child is the new root.
+ */
+ private void rotateRight(Node<K, V> root) {
+ Node<K, V> pivot = root.left;
+ Node<K, V> right = root.right;
+ Node<K, V> pivotLeft = pivot.left;
+ Node<K, V> pivotRight = pivot.right;
+
+ // move the pivot's right child to the root's left
+ root.left = pivotRight;
+ if (pivotRight != null) {
+ pivotRight.parent = root;
+ }
+
+ replaceInParent(root, pivot);
+
+ // move the root to the pivot's right
+ pivot.right = root;
+ root.parent = pivot;
+
+ // fixup heights
+ root.height = Math.max(right != null ? right.height : 0,
+ pivotRight != null ? pivotRight.height : 0) + 1;
+ pivot.height = Math.max(root.height,
+ pivotLeft != null ? pivotLeft.height : 0) + 1;
+ }
+
+ private EntrySet entrySet;
+ private KeySet keySet;
+
+ @Override public Set<Entry<K, V>> entrySet() {
+ EntrySet result = entrySet;
+ return result != null ? result : (entrySet = new EntrySet());
+ }
+
+ @Override public Set<K> keySet() {
+ KeySet result = keySet;
+ return result != null ? result : (keySet = new KeySet());
+ }
+
+ static final class Node<K, V> implements Entry<K, V> {
+ Node<K, V> parent;
+ Node<K, V> left;
+ Node<K, V> right;
+ Node<K, V> next;
+ Node<K, V> prev;
+ final K key;
+ final int hash;
+ V value;
+ int height;
+
+ /** Create the header entry */
+ Node() {
+ key = null;
+ hash = -1;
+ next = prev = this;
+ }
+
+ /** Create a regular entry */
+ Node(Node<K, V> parent, K key, int hash, Node<K, V> next, Node<K, V> prev) {
+ this.parent = parent;
+ this.key = key;
+ this.hash = hash;
+ this.height = 1;
+ this.next = next;
+ this.prev = prev;
+ prev.next = this;
+ next.prev = this;
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ V oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override public boolean equals(Object o) {
+ if (o instanceof Entry) {
+ Entry other = (Entry) o;
+ return (key == null ? other.getKey() == null : key.equals(other.getKey()))
+ && (value == null ? other.getValue() == null : value.equals(other.getValue()));
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ return (key == null ? 0 : key.hashCode())
+ ^ (value == null ? 0 : value.hashCode());
+ }
+
+ @Override public String toString() {
+ return key + "=" + value;
+ }
+
+ /**
+ * Returns the first node in this subtree.
+ */
+ public Node<K, V> first() {
+ Node<K, V> node = this;
+ Node<K, V> child = node.left;
+ while (child != null) {
+ node = child;
+ child = node.left;
+ }
+ return node;
+ }
+
+ /**
+ * Returns the last node in this subtree.
+ */
+ public Node<K, V> last() {
+ Node<K, V> node = this;
+ Node<K, V> child = node.right;
+ while (child != null) {
+ node = child;
+ child = node.right;
+ }
+ return node;
+ }
+ }
+
+ private void doubleCapacity() {
+ table = doubleCapacity(table);
+ threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity
+ }
+
+ /**
+ * Returns a new array containing the same nodes as {@code oldTable}, but with
+ * twice as many trees, each of (approximately) half the previous size.
+ */
+ static <K, V> Node<K, V>[] doubleCapacity(Node<K, V>[] oldTable) {
+ // TODO: don't do anything if we're already at MAX_CAPACITY
+ int oldCapacity = oldTable.length;
+ @SuppressWarnings("unchecked") // Arrays and generics don't get along.
+ Node<K, V>[] newTable = new Node[oldCapacity * 2];
+ AvlIterator<K, V> iterator = new AvlIterator<K, V>();
+ AvlBuilder<K, V> leftBuilder = new AvlBuilder<K, V>();
+ AvlBuilder<K, V> rightBuilder = new AvlBuilder<K, V>();
+
+ // Split each tree into two trees.
+ for (int i = 0; i < oldCapacity; i++) {
+ Node<K, V> root = oldTable[i];
+ if (root == null) {
+ continue;
+ }
+
+ // Compute the sizes of the left and right trees.
+ iterator.reset(root);
+ int leftSize = 0;
+ int rightSize = 0;
+ for (Node<K, V> node; (node = iterator.next()) != null; ) {
+ if ((node.hash & oldCapacity) == 0) {
+ leftSize++;
+ } else {
+ rightSize++;
+ }
+ }
+
+ // Split the tree into two.
+ leftBuilder.reset(leftSize);
+ rightBuilder.reset(rightSize);
+ iterator.reset(root);
+ for (Node<K, V> node; (node = iterator.next()) != null; ) {
+ if ((node.hash & oldCapacity) == 0) {
+ leftBuilder.add(node);
+ } else {
+ rightBuilder.add(node);
+ }
+ }
+
+ // Populate the enlarged array with these new roots.
+ newTable[i] = leftSize > 0 ? leftBuilder.root() : null;
+ newTable[i + oldCapacity] = rightSize > 0 ? rightBuilder.root() : null;
+ }
+ return newTable;
+ }
+
+ /**
+ * Walks an AVL tree in iteration order. Once a node has been returned, its
+ * left, right and parent links are <strong>no longer used</strong>. For this
+ * reason it is safe to transform these links as you walk a tree.
+ *
+ * <p><strong>Warning:</strong> this iterator is destructive. It clears the
+ * parent node of all nodes in the tree. It is an error to make a partial
+ * iteration of a tree.
+ */
+ static class AvlIterator<K, V> {
+ /** This stack is a singly linked list, linked by the 'parent' field. */
+ private Node<K, V> stackTop;
+
+ void reset(Node<K, V> root) {
+ Node<K, V> stackTop = null;
+ for (Node<K, V> n = root; n != null; n = n.left) {
+ n.parent = stackTop;
+ stackTop = n; // Stack push.
+ }
+ this.stackTop = stackTop;
+ }
+
+ public Node<K, V> next() {
+ Node<K, V> stackTop = this.stackTop;
+ if (stackTop == null) {
+ return null;
+ }
+ Node<K, V> result = stackTop;
+ stackTop = result.parent;
+ result.parent = null;
+ for (Node<K, V> n = result.right; n != null; n = n.left) {
+ n.parent = stackTop;
+ stackTop = n; // Stack push.
+ }
+ this.stackTop = stackTop;
+ return result;
+ }
+ }
+
+ /**
+ * Builds AVL trees of a predetermined size by accepting nodes of increasing
+ * value. To use:
+ * <ol>
+ * <li>Call {@link #reset} to initialize the target size <i>size</i>.
+ * <li>Call {@link #add} <i>size</i> times with increasing values.
+ * <li>Call {@link #root} to get the root of the balanced tree.
+ * </ol>
+ *
+ * <p>The returned tree will satisfy the AVL constraint: for every node
+ * <i>N</i>, the height of <i>N.left</i> and <i>N.right</i> is different by at
+ * most 1. It accomplishes this by omitting deepest-level leaf nodes when
+ * building trees whose size isn't a power of 2 minus 1.
+ *
+ * <p>Unlike rebuilding a tree from scratch, this approach requires no value
+ * comparisons. Using this class to create a tree of size <i>S</i> is
+ * {@code O(S)}.
+ */
+ final static class AvlBuilder<K, V> {
+ /** This stack is a singly linked list, linked by the 'parent' field. */
+ private Node<K, V> stack;
+ private int leavesToSkip;
+ private int leavesSkipped;
+ private int size;
+
+ void reset(int targetSize) {
+ // compute the target tree size. This is a power of 2 minus one, like 15 or 31.
+ int treeCapacity = Integer.highestOneBit(targetSize) * 2 - 1;
+ leavesToSkip = treeCapacity - targetSize;
+ size = 0;
+ leavesSkipped = 0;
+ stack = null;
+ }
+
+ void add(Node<K, V> node) {
+ node.left = node.parent = node.right = null;
+ node.height = 1;
+
+ // Skip a leaf if necessary.
+ if (leavesToSkip > 0 && (size & 1) == 0) {
+ size++;
+ leavesToSkip--;
+ leavesSkipped++;
+ }
+
+ node.parent = stack;
+ stack = node; // Stack push.
+ size++;
+
+ // Skip a leaf if necessary.
+ if (leavesToSkip > 0 && (size & 1) == 0) {
+ size++;
+ leavesToSkip--;
+ leavesSkipped++;
+ }
+
+ /*
+ * Combine 3 nodes into subtrees whenever the size is one less than a
+ * multiple of 4. For example we combine the nodes A, B, C into a
+ * 3-element tree with B as the root.
+ *
+ * Combine two subtrees and a spare single value whenever the size is one
+ * less than a multiple of 8. For example at 8 we may combine subtrees
+ * (A B C) and (E F G) with D as the root to form ((A B C) D (E F G)).
+ *
+ * Just as we combine single nodes when size nears a multiple of 4, and
+ * 3-element trees when size nears a multiple of 8, we combine subtrees of
+ * size (N-1) whenever the total size is 2N-1 whenever N is a power of 2.
+ */
+ for (int scale = 4; (size & scale - 1) == scale - 1; scale *= 2) {
+ if (leavesSkipped == 0) {
+ // Pop right, center and left, then make center the top of the stack.
+ Node<K, V> right = stack;
+ Node<K, V> center = right.parent;
+ Node<K, V> left = center.parent;
+ center.parent = left.parent;
+ stack = center;
+ // Construct a tree.
+ center.left = left;
+ center.right = right;
+ center.height = right.height + 1;
+ left.parent = center;
+ right.parent = center;
+ } else if (leavesSkipped == 1) {
+ // Pop right and center, then make center the top of the stack.
+ Node<K, V> right = stack;
+ Node<K, V> center = right.parent;
+ stack = center;
+ // Construct a tree with no left child.
+ center.right = right;
+ center.height = right.height + 1;
+ right.parent = center;
+ leavesSkipped = 0;
+ } else if (leavesSkipped == 2) {
+ leavesSkipped = 0;
+ }
+ }
+ }
+
+ Node<K, V> root() {
+ Node<K, V> stackTop = this.stack;
+ if (stackTop.parent != null) {
+ throw new IllegalStateException();
+ }
+ return stackTop;
+ }
+ }
+
+ private abstract class LinkedTreeMapIterator<T> implements Iterator<T> {
+ Node<K, V> next = header.next;
+ Node<K, V> lastReturned = null;
+ int expectedModCount = modCount;
+
+ public final boolean hasNext() {
+ return next != header;
+ }
+
+ final Node<K, V> nextNode() {
+ Node<K, V> e = next;
+ if (e == header) {
+ throw new NoSuchElementException();
+ }
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ next = e.next;
+ return lastReturned = e;
+ }
+
+ public final void remove() {
+ if (lastReturned == null) {
+ throw new IllegalStateException();
+ }
+ removeInternal(lastReturned, true);
+ lastReturned = null;
+ expectedModCount = modCount;
+ }
+ }
+
+ final class EntrySet extends AbstractSet<Entry<K, V>> {
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public Iterator<Entry<K, V>> iterator() {
+ return new LinkedTreeMapIterator<Entry<K, V>>() {
+ public Entry<K, V> next() {
+ return nextNode();
+ }
+ };
+ }
+
+ @Override public boolean contains(Object o) {
+ return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
+ }
+
+ @Override public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+
+ Node<K, V> node = findByEntry((Entry<?, ?>) o);
+ if (node == null) {
+ return false;
+ }
+ removeInternal(node, true);
+ return true;
+ }
+
+ @Override public void clear() {
+ LinkedHashTreeMap.this.clear();
+ }
+ }
+
+ final class KeySet extends AbstractSet<K> {
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public Iterator<K> iterator() {
+ return new LinkedTreeMapIterator<K>() {
+ public K next() {
+ return nextNode().key;
+ }
+ };
+ }
+
+ @Override public boolean contains(Object o) {
+ return containsKey(o);
+ }
+
+ @Override public boolean remove(Object key) {
+ return removeInternalByKey(key) != null;
+ }
+
+ @Override public void clear() {
+ LinkedHashTreeMap.this.clear();
+ }
+ }
+
+ /**
+ * If somebody is unlucky enough to have to serialize one of these, serialize
+ * it as a LinkedHashMap so that they won't need Gson on the other side to
+ * deserialize it. Using serialization defeats our DoS defence, so most apps
+ * shouldn't use it.
+ */
+ private Object writeReplace() throws ObjectStreamException {
+ return new LinkedHashMap<K, V>(this);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java
new file mode 100644
index 00000000..c2c84802
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2012 Google 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 com.google.gson.internal;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Comparator;
+import java.util.ConcurrentModificationException;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A map of comparable keys to values. Unlike {@code TreeMap}, this class uses
+ * insertion order for iteration order. Comparison order is only used as an
+ * optimization for efficient insertion and removal.
+ *
+ * <p>This implementation was derived from Android 4.1's TreeMap class.
+ */
+public final class LinkedTreeMap<K, V> extends AbstractMap<K, V> implements Serializable {
+ @SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable<Comparable<Comparable<...>>>
+ private static final Comparator<Comparable> NATURAL_ORDER = new Comparator<Comparable>() {
+ public int compare(Comparable a, Comparable b) {
+ return a.compareTo(b);
+ }
+ };
+
+ Comparator<? super K> comparator;
+ Node<K, V> root;
+ int size = 0;
+ int modCount = 0;
+
+ // Used to preserve iteration order
+ final Node<K, V> header = new Node<K, V>();
+
+ /**
+ * Create a natural order, empty tree map whose keys must be mutually
+ * comparable and non-null.
+ */
+ @SuppressWarnings("unchecked") // unsafe! this assumes K is comparable
+ public LinkedTreeMap() {
+ this((Comparator<? super K>) NATURAL_ORDER);
+ }
+
+ /**
+ * Create a tree map ordered by {@code comparator}. This map's keys may only
+ * be null if {@code comparator} permits.
+ *
+ * @param comparator the comparator to order elements with, or {@code null} to
+ * use the natural ordering.
+ */
+ @SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable
+ public LinkedTreeMap(Comparator<? super K> comparator) {
+ this.comparator = comparator != null
+ ? comparator
+ : (Comparator) NATURAL_ORDER;
+ }
+
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public V get(Object key) {
+ Node<K, V> node = findByObject(key);
+ return node != null ? node.value : null;
+ }
+
+ @Override public boolean containsKey(Object key) {
+ return findByObject(key) != null;
+ }
+
+ @Override public V put(K key, V value) {
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+ Node<K, V> created = find(key, true);
+ V result = created.value;
+ created.value = value;
+ return result;
+ }
+
+ @Override public void clear() {
+ root = null;
+ size = 0;
+ modCount++;
+
+ // Clear iteration order
+ Node<K, V> header = this.header;
+ header.next = header.prev = header;
+ }
+
+ @Override public V remove(Object key) {
+ Node<K, V> node = removeInternalByKey(key);
+ return node != null ? node.value : null;
+ }
+
+ /**
+ * Returns the node at or adjacent to the given key, creating it if requested.
+ *
+ * @throws ClassCastException if {@code key} and the tree's keys aren't
+ * mutually comparable.
+ */
+ Node<K, V> find(K key, boolean create) {
+ Comparator<? super K> comparator = this.comparator;
+ Node<K, V> nearest = root;
+ int comparison = 0;
+
+ if (nearest != null) {
+ // Micro-optimization: avoid polymorphic calls to Comparator.compare().
+ @SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble.
+ Comparable<Object> comparableKey = (comparator == NATURAL_ORDER)
+ ? (Comparable<Object>) key
+ : null;
+
+ while (true) {
+ comparison = (comparableKey != null)
+ ? comparableKey.compareTo(nearest.key)
+ : comparator.compare(key, nearest.key);
+
+ // We found the requested key.
+ if (comparison == 0) {
+ return nearest;
+ }
+
+ // If it exists, the key is in a subtree. Go deeper.
+ Node<K, V> child = (comparison < 0) ? nearest.left : nearest.right;
+ if (child == null) {
+ break;
+ }
+
+ nearest = child;
+ }
+ }
+
+ // The key doesn't exist in this tree.
+ if (!create) {
+ return null;
+ }
+
+ // Create the node and add it to the tree or the table.
+ Node<K, V> header = this.header;
+ Node<K, V> created;
+ if (nearest == null) {
+ // Check that the value is comparable if we didn't do any comparisons.
+ if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) {
+ throw new ClassCastException(key.getClass().getName() + " is not Comparable");
+ }
+ created = new Node<K, V>(nearest, key, header, header.prev);
+ root = created;
+ } else {
+ created = new Node<K, V>(nearest, key, header, header.prev);
+ if (comparison < 0) { // nearest.key is higher
+ nearest.left = created;
+ } else { // comparison > 0, nearest.key is lower
+ nearest.right = created;
+ }
+ rebalance(nearest, true);
+ }
+ size++;
+ modCount++;
+
+ return created;
+ }
+
+ @SuppressWarnings("unchecked")
+ Node<K, V> findByObject(Object key) {
+ try {
+ return key != null ? find((K) key, false) : null;
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns this map's entry that has the same key and value as {@code
+ * entry}, or null if this map has no such entry.
+ *
+ * <p>This method uses the comparator for key equality rather than {@code
+ * equals}. If this map's comparator isn't consistent with equals (such as
+ * {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code
+ * contains()} will violate the collections API.
+ */
+ Node<K, V> findByEntry(Entry<?, ?> entry) {
+ Node<K, V> mine = findByObject(entry.getKey());
+ boolean valuesEqual = mine != null && equal(mine.value, entry.getValue());
+ return valuesEqual ? mine : null;
+ }
+
+ private boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ /**
+ * Removes {@code node} from this tree, rearranging the tree's structure as
+ * necessary.
+ *
+ * @param unlink true to also unlink this node from the iteration linked list.
+ */
+ void removeInternal(Node<K, V> node, boolean unlink) {
+ if (unlink) {
+ node.prev.next = node.next;
+ node.next.prev = node.prev;
+ }
+
+ Node<K, V> left = node.left;
+ Node<K, V> right = node.right;
+ Node<K, V> originalParent = node.parent;
+ if (left != null && right != null) {
+
+ /*
+ * To remove a node with both left and right subtrees, move an
+ * adjacent node from one of those subtrees into this node's place.
+ *
+ * Removing the adjacent node may change this node's subtrees. This
+ * node may no longer have two subtrees once the adjacent node is
+ * gone!
+ */
+
+ Node<K, V> adjacent = (left.height > right.height) ? left.last() : right.first();
+ removeInternal(adjacent, false); // takes care of rebalance and size--
+
+ int leftHeight = 0;
+ left = node.left;
+ if (left != null) {
+ leftHeight = left.height;
+ adjacent.left = left;
+ left.parent = adjacent;
+ node.left = null;
+ }
+
+ int rightHeight = 0;
+ right = node.right;
+ if (right != null) {
+ rightHeight = right.height;
+ adjacent.right = right;
+ right.parent = adjacent;
+ node.right = null;
+ }
+
+ adjacent.height = Math.max(leftHeight, rightHeight) + 1;
+ replaceInParent(node, adjacent);
+ return;
+ } else if (left != null) {
+ replaceInParent(node, left);
+ node.left = null;
+ } else if (right != null) {
+ replaceInParent(node, right);
+ node.right = null;
+ } else {
+ replaceInParent(node, null);
+ }
+
+ rebalance(originalParent, false);
+ size--;
+ modCount++;
+ }
+
+ Node<K, V> removeInternalByKey(Object key) {
+ Node<K, V> node = findByObject(key);
+ if (node != null) {
+ removeInternal(node, true);
+ }
+ return node;
+ }
+
+ private void replaceInParent(Node<K, V> node, Node<K, V> replacement) {
+ Node<K, V> parent = node.parent;
+ node.parent = null;
+ if (replacement != null) {
+ replacement.parent = parent;
+ }
+
+ if (parent != null) {
+ if (parent.left == node) {
+ parent.left = replacement;
+ } else {
+ assert (parent.right == node);
+ parent.right = replacement;
+ }
+ } else {
+ root = replacement;
+ }
+ }
+
+ /**
+ * Rebalances the tree by making any AVL rotations necessary between the
+ * newly-unbalanced node and the tree's root.
+ *
+ * @param insert true if the node was unbalanced by an insert; false if it
+ * was by a removal.
+ */
+ private void rebalance(Node<K, V> unbalanced, boolean insert) {
+ for (Node<K, V> node = unbalanced; node != null; node = node.parent) {
+ Node<K, V> left = node.left;
+ Node<K, V> right = node.right;
+ int leftHeight = left != null ? left.height : 0;
+ int rightHeight = right != null ? right.height : 0;
+
+ int delta = leftHeight - rightHeight;
+ if (delta == -2) {
+ Node<K, V> rightLeft = right.left;
+ Node<K, V> rightRight = right.right;
+ int rightRightHeight = rightRight != null ? rightRight.height : 0;
+ int rightLeftHeight = rightLeft != null ? rightLeft.height : 0;
+
+ int rightDelta = rightLeftHeight - rightRightHeight;
+ if (rightDelta == -1 || (rightDelta == 0 && !insert)) {
+ rotateLeft(node); // AVL right right
+ } else {
+ assert (rightDelta == 1);
+ rotateRight(right); // AVL right left
+ rotateLeft(node);
+ }
+ if (insert) {
+ break; // no further rotations will be necessary
+ }
+
+ } else if (delta == 2) {
+ Node<K, V> leftLeft = left.left;
+ Node<K, V> leftRight = left.right;
+ int leftRightHeight = leftRight != null ? leftRight.height : 0;
+ int leftLeftHeight = leftLeft != null ? leftLeft.height : 0;
+
+ int leftDelta = leftLeftHeight - leftRightHeight;
+ if (leftDelta == 1 || (leftDelta == 0 && !insert)) {
+ rotateRight(node); // AVL left left
+ } else {
+ assert (leftDelta == -1);
+ rotateLeft(left); // AVL left right
+ rotateRight(node);
+ }
+ if (insert) {
+ break; // no further rotations will be necessary
+ }
+
+ } else if (delta == 0) {
+ node.height = leftHeight + 1; // leftHeight == rightHeight
+ if (insert) {
+ break; // the insert caused balance, so rebalancing is done!
+ }
+
+ } else {
+ assert (delta == -1 || delta == 1);
+ node.height = Math.max(leftHeight, rightHeight) + 1;
+ if (!insert) {
+ break; // the height hasn't changed, so rebalancing is done!
+ }
+ }
+ }
+ }
+
+ /**
+ * Rotates the subtree so that its root's right child is the new root.
+ */
+ private void rotateLeft(Node<K, V> root) {
+ Node<K, V> left = root.left;
+ Node<K, V> pivot = root.right;
+ Node<K, V> pivotLeft = pivot.left;
+ Node<K, V> pivotRight = pivot.right;
+
+ // move the pivot's left child to the root's right
+ root.right = pivotLeft;
+ if (pivotLeft != null) {
+ pivotLeft.parent = root;
+ }
+
+ replaceInParent(root, pivot);
+
+ // move the root to the pivot's left
+ pivot.left = root;
+ root.parent = pivot;
+
+ // fix heights
+ root.height = Math.max(left != null ? left.height : 0,
+ pivotLeft != null ? pivotLeft.height : 0) + 1;
+ pivot.height = Math.max(root.height,
+ pivotRight != null ? pivotRight.height : 0) + 1;
+ }
+
+ /**
+ * Rotates the subtree so that its root's left child is the new root.
+ */
+ private void rotateRight(Node<K, V> root) {
+ Node<K, V> pivot = root.left;
+ Node<K, V> right = root.right;
+ Node<K, V> pivotLeft = pivot.left;
+ Node<K, V> pivotRight = pivot.right;
+
+ // move the pivot's right child to the root's left
+ root.left = pivotRight;
+ if (pivotRight != null) {
+ pivotRight.parent = root;
+ }
+
+ replaceInParent(root, pivot);
+
+ // move the root to the pivot's right
+ pivot.right = root;
+ root.parent = pivot;
+
+ // fixup heights
+ root.height = Math.max(right != null ? right.height : 0,
+ pivotRight != null ? pivotRight.height : 0) + 1;
+ pivot.height = Math.max(root.height,
+ pivotLeft != null ? pivotLeft.height : 0) + 1;
+ }
+
+ private EntrySet entrySet;
+ private KeySet keySet;
+
+ @Override public Set<Entry<K, V>> entrySet() {
+ EntrySet result = entrySet;
+ return result != null ? result : (entrySet = new EntrySet());
+ }
+
+ @Override public Set<K> keySet() {
+ KeySet result = keySet;
+ return result != null ? result : (keySet = new KeySet());
+ }
+
+ static final class Node<K, V> implements Entry<K, V> {
+ Node<K, V> parent;
+ Node<K, V> left;
+ Node<K, V> right;
+ Node<K, V> next;
+ Node<K, V> prev;
+ final K key;
+ V value;
+ int height;
+
+ /** Create the header entry */
+ Node() {
+ key = null;
+ next = prev = this;
+ }
+
+ /** Create a regular entry */
+ Node(Node<K, V> parent, K key, Node<K, V> next, Node<K, V> prev) {
+ this.parent = parent;
+ this.key = key;
+ this.height = 1;
+ this.next = next;
+ this.prev = prev;
+ prev.next = this;
+ next.prev = this;
+ }
+
+ public K getKey() {
+ return key;
+ }
+
+ public V getValue() {
+ return value;
+ }
+
+ public V setValue(V value) {
+ V oldValue = this.value;
+ this.value = value;
+ return oldValue;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override public boolean equals(Object o) {
+ if (o instanceof Entry) {
+ Entry other = (Entry) o;
+ return (key == null ? other.getKey() == null : key.equals(other.getKey()))
+ && (value == null ? other.getValue() == null : value.equals(other.getValue()));
+ }
+ return false;
+ }
+
+ @Override public int hashCode() {
+ return (key == null ? 0 : key.hashCode())
+ ^ (value == null ? 0 : value.hashCode());
+ }
+
+ @Override public String toString() {
+ return key + "=" + value;
+ }
+
+ /**
+ * Returns the first node in this subtree.
+ */
+ public Node<K, V> first() {
+ Node<K, V> node = this;
+ Node<K, V> child = node.left;
+ while (child != null) {
+ node = child;
+ child = node.left;
+ }
+ return node;
+ }
+
+ /**
+ * Returns the last node in this subtree.
+ */
+ public Node<K, V> last() {
+ Node<K, V> node = this;
+ Node<K, V> child = node.right;
+ while (child != null) {
+ node = child;
+ child = node.right;
+ }
+ return node;
+ }
+ }
+
+ private abstract class LinkedTreeMapIterator<T> implements Iterator<T> {
+ Node<K, V> next = header.next;
+ Node<K, V> lastReturned = null;
+ int expectedModCount = modCount;
+
+ public final boolean hasNext() {
+ return next != header;
+ }
+
+ final Node<K, V> nextNode() {
+ Node<K, V> e = next;
+ if (e == header) {
+ throw new NoSuchElementException();
+ }
+ if (modCount != expectedModCount) {
+ throw new ConcurrentModificationException();
+ }
+ next = e.next;
+ return lastReturned = e;
+ }
+
+ public final void remove() {
+ if (lastReturned == null) {
+ throw new IllegalStateException();
+ }
+ removeInternal(lastReturned, true);
+ lastReturned = null;
+ expectedModCount = modCount;
+ }
+ }
+
+ class EntrySet extends AbstractSet<Entry<K, V>> {
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public Iterator<Entry<K, V>> iterator() {
+ return new LinkedTreeMapIterator<Entry<K, V>>() {
+ public Entry<K, V> next() {
+ return nextNode();
+ }
+ };
+ }
+
+ @Override public boolean contains(Object o) {
+ return o instanceof Entry && findByEntry((Entry<?, ?>) o) != null;
+ }
+
+ @Override public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+
+ Node<K, V> node = findByEntry((Entry<?, ?>) o);
+ if (node == null) {
+ return false;
+ }
+ removeInternal(node, true);
+ return true;
+ }
+
+ @Override public void clear() {
+ LinkedTreeMap.this.clear();
+ }
+ }
+
+ final class KeySet extends AbstractSet<K> {
+ @Override public int size() {
+ return size;
+ }
+
+ @Override public Iterator<K> iterator() {
+ return new LinkedTreeMapIterator<K>() {
+ public K next() {
+ return nextNode().key;
+ }
+ };
+ }
+
+ @Override public boolean contains(Object o) {
+ return containsKey(o);
+ }
+
+ @Override public boolean remove(Object key) {
+ return removeInternalByKey(key) != null;
+ }
+
+ @Override public void clear() {
+ LinkedTreeMap.this.clear();
+ }
+ }
+
+ /**
+ * If somebody is unlucky enough to have to serialize one of these, serialize
+ * it as a LinkedHashMap so that they won't need Gson on the other side to
+ * deserialize it. Using serialization defeats our DoS defence, so most apps
+ * shouldn't use it.
+ */
+ private Object writeReplace() throws ObjectStreamException {
+ return new LinkedHashMap<K, V>(this);
+ }
+} \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/internal/ObjectConstructor.java b/gson/src/main/java/com/google/gson/internal/ObjectConstructor.java
new file mode 100644
index 00000000..6ef20607
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/ObjectConstructor.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.internal;
+
+/**
+ * Defines a generic object construction factory. The purpose of this class
+ * is to construct a default instance of a class that can be used for object
+ * navigation while deserialization from its JSON representation.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public interface ObjectConstructor<T> {
+
+ /**
+ * Returns a new instance.
+ */
+ public T construct();
+} \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/internal/Primitives.java b/gson/src/main/java/com/google/gson/internal/Primitives.java
new file mode 100644
index 00000000..a98f6242
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/Primitives.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.internal;
+
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Contains static utility methods pertaining to primitive types and their
+ * corresponding wrapper types.
+ *
+ * @author Kevin Bourrillion
+ */
+public final class Primitives {
+ private Primitives() {
+ throw new UnsupportedOperationException();
+ }
+
+ /** A map from primitive types to their corresponding wrapper types. */
+ private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_WRAPPER_TYPE;
+
+ /** A map from wrapper types to their corresponding primitive types. */
+ private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPE;
+
+ // Sad that we can't use a BiMap. :(
+
+ static {
+ Map<Class<?>, Class<?>> primToWrap = new HashMap<Class<?>, Class<?>>(16);
+ Map<Class<?>, Class<?>> wrapToPrim = new HashMap<Class<?>, Class<?>>(16);
+
+ add(primToWrap, wrapToPrim, boolean.class, Boolean.class);
+ add(primToWrap, wrapToPrim, byte.class, Byte.class);
+ add(primToWrap, wrapToPrim, char.class, Character.class);
+ add(primToWrap, wrapToPrim, double.class, Double.class);
+ add(primToWrap, wrapToPrim, float.class, Float.class);
+ add(primToWrap, wrapToPrim, int.class, Integer.class);
+ add(primToWrap, wrapToPrim, long.class, Long.class);
+ add(primToWrap, wrapToPrim, short.class, Short.class);
+ add(primToWrap, wrapToPrim, void.class, Void.class);
+
+ PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap);
+ WRAPPER_TO_PRIMITIVE_TYPE = Collections.unmodifiableMap(wrapToPrim);
+ }
+
+ private static void add(Map<Class<?>, Class<?>> forward,
+ Map<Class<?>, Class<?>> backward, Class<?> key, Class<?> value) {
+ forward.put(key, value);
+ backward.put(value, key);
+ }
+
+ /**
+ * Returns true if this type is a primitive.
+ */
+ public static boolean isPrimitive(Type type) {
+ return PRIMITIVE_TO_WRAPPER_TYPE.containsKey(type);
+ }
+
+ /**
+ * Returns {@code true} if {@code type} is one of the nine
+ * primitive-wrapper types, such as {@link Integer}.
+ *
+ * @see Class#isPrimitive
+ */
+ public static boolean isWrapperType(Type type) {
+ return WRAPPER_TO_PRIMITIVE_TYPE.containsKey(
+ $Gson$Preconditions.checkNotNull(type));
+ }
+
+ /**
+ * Returns the corresponding wrapper type of {@code type} if it is a primitive
+ * type; otherwise returns {@code type} itself. Idempotent.
+ * <pre>
+ * wrap(int.class) == Integer.class
+ * wrap(Integer.class) == Integer.class
+ * wrap(String.class) == String.class
+ * </pre>
+ */
+ public static <T> Class<T> wrap(Class<T> type) {
+ // cast is safe: long.class and Long.class are both of type Class<Long>
+ @SuppressWarnings("unchecked")
+ Class<T> wrapped = (Class<T>) PRIMITIVE_TO_WRAPPER_TYPE.get(
+ $Gson$Preconditions.checkNotNull(type));
+ return (wrapped == null) ? type : wrapped;
+ }
+
+ /**
+ * Returns the corresponding primitive type of {@code type} if it is a
+ * wrapper type; otherwise returns {@code type} itself. Idempotent.
+ * <pre>
+ * unwrap(Integer.class) == int.class
+ * unwrap(int.class) == int.class
+ * unwrap(String.class) == String.class
+ * </pre>
+ */
+ public static <T> Class<T> unwrap(Class<T> type) {
+ // cast is safe: long.class and Long.class are both of type Class<Long>
+ @SuppressWarnings("unchecked")
+ Class<T> unwrapped = (Class<T>) WRAPPER_TO_PRIMITIVE_TYPE.get(
+ $Gson$Preconditions.checkNotNull(type));
+ return (unwrapped == null) ? type : unwrapped;
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/Streams.java b/gson/src/main/java/com/google/gson/internal/Streams.java
new file mode 100644
index 00000000..7f00f11d
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/Streams.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.internal;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.internal.bind.TypeAdapters;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import com.google.gson.stream.MalformedJsonException;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Reads and writes GSON parse trees over streams.
+ */
+public final class Streams {
+ private Streams() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Takes a reader in any state and returns the next value as a JsonElement.
+ */
+ public static JsonElement parse(JsonReader reader) throws JsonParseException {
+ boolean isEmpty = true;
+ try {
+ reader.peek();
+ isEmpty = false;
+ return TypeAdapters.JSON_ELEMENT.read(reader);
+ } catch (EOFException e) {
+ /*
+ * For compatibility with JSON 1.5 and earlier, we return a JsonNull for
+ * empty documents instead of throwing.
+ */
+ if (isEmpty) {
+ return JsonNull.INSTANCE;
+ }
+ // The stream ended prematurely so it is likely a syntax error.
+ throw new JsonSyntaxException(e);
+ } catch (MalformedJsonException e) {
+ throw new JsonSyntaxException(e);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+
+ /**
+ * Writes the JSON element to the writer, recursively.
+ */
+ public static void write(JsonElement element, JsonWriter writer) throws IOException {
+ TypeAdapters.JSON_ELEMENT.write(writer, element);
+ }
+
+ @SuppressWarnings("resource")
+ public static Writer writerForAppendable(Appendable appendable) {
+ return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable);
+ }
+
+ /**
+ * Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer}
+ * is used.
+ */
+ private static final class AppendableWriter extends Writer {
+ private final Appendable appendable;
+ private final CurrentWrite currentWrite = new CurrentWrite();
+
+ private AppendableWriter(Appendable appendable) {
+ this.appendable = appendable;
+ }
+
+ @Override public void write(char[] chars, int offset, int length) throws IOException {
+ currentWrite.chars = chars;
+ appendable.append(currentWrite, offset, offset + length);
+ }
+
+ @Override public void write(int i) throws IOException {
+ appendable.append((char) i);
+ }
+
+ @Override public void flush() {}
+ @Override public void close() {}
+
+ /**
+ * A mutable char sequence pointing at a single char[].
+ */
+ static class CurrentWrite implements CharSequence {
+ char[] chars;
+ public int length() {
+ return chars.length;
+ }
+ public char charAt(int i) {
+ return chars[i];
+ }
+ public CharSequence subSequence(int start, int end) {
+ return new String(chars, start, end - start);
+ }
+ }
+ }
+
+}
diff --git a/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java b/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java
new file mode 100644
index 00000000..fce0be37
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/UnsafeAllocator.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal;
+
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamClass;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+/**
+ * Do sneaky things to allocate objects without invoking their constructors.
+ *
+ * @author Joel Leitch
+ * @author Jesse Wilson
+ */
+public abstract class UnsafeAllocator {
+ public abstract <T> T newInstance(Class<T> c) throws Exception;
+
+ public static UnsafeAllocator create() {
+ // try JVM
+ // public class Unsafe {
+ // public Object allocateInstance(Class<?> type);
+ // }
+ try {
+ Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field f = unsafeClass.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ final Object unsafe = f.get(null);
+ final Method allocateInstance = unsafeClass.getMethod("allocateInstance", Class.class);
+ return new UnsafeAllocator() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T newInstance(Class<T> c) throws Exception {
+ return (T) allocateInstance.invoke(unsafe, c);
+ }
+ };
+ } catch (Exception ignored) {
+ }
+
+ // try dalvikvm, post-gingerbread
+ // public class ObjectStreamClass {
+ // private static native int getConstructorId(Class<?> c);
+ // private static native Object newInstance(Class<?> instantiationClass, int methodId);
+ // }
+ try {
+ Method getConstructorId = ObjectStreamClass.class
+ .getDeclaredMethod("getConstructorId", Class.class);
+ getConstructorId.setAccessible(true);
+ final int constructorId = (Integer) getConstructorId.invoke(null, Object.class);
+ final Method newInstance = ObjectStreamClass.class
+ .getDeclaredMethod("newInstance", Class.class, int.class);
+ newInstance.setAccessible(true);
+ return new UnsafeAllocator() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T newInstance(Class<T> c) throws Exception {
+ return (T) newInstance.invoke(null, c, constructorId);
+ }
+ };
+ } catch (Exception ignored) {
+ }
+
+ // try dalvikvm, pre-gingerbread
+ // public class ObjectInputStream {
+ // private static native Object newInstance(
+ // Class<?> instantiationClass, Class<?> constructorClass);
+ // }
+ try {
+ final Method newInstance = ObjectInputStream.class
+ .getDeclaredMethod("newInstance", Class.class, Class.class);
+ newInstance.setAccessible(true);
+ return new UnsafeAllocator() {
+ @Override
+ @SuppressWarnings("unchecked")
+ public <T> T newInstance(Class<T> c) throws Exception {
+ return (T) newInstance.invoke(null, c, Object.class);
+ }
+ };
+ } catch (Exception ignored) {
+ }
+
+ // give up
+ return new UnsafeAllocator() {
+ @Override
+ public <T> T newInstance(Class<T> c) {
+ throw new UnsupportedOperationException("Cannot allocate " + c);
+ }
+ };
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java
new file mode 100644
index 00000000..55d7e309
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Adapt an array of objects.
+ */
+public final class ArrayTypeAdapter<E> extends TypeAdapter<Object> {
+ public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ Type type = typeToken.getType();
+ if (!(type instanceof GenericArrayType || type instanceof Class && ((Class<?>) type).isArray())) {
+ return null;
+ }
+
+ Type componentType = $Gson$Types.getArrayComponentType(type);
+ TypeAdapter<?> componentTypeAdapter = gson.getAdapter(TypeToken.get(componentType));
+ return new ArrayTypeAdapter(
+ gson, componentTypeAdapter, $Gson$Types.getRawType(componentType));
+ }
+ };
+
+ private final Class<E> componentType;
+ private final TypeAdapter<E> componentTypeAdapter;
+
+ public ArrayTypeAdapter(Gson context, TypeAdapter<E> componentTypeAdapter, Class<E> componentType) {
+ this.componentTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper<E>(context, componentTypeAdapter, componentType);
+ this.componentType = componentType;
+ }
+
+ public Object read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ List<E> list = new ArrayList<E>();
+ in.beginArray();
+ while (in.hasNext()) {
+ E instance = componentTypeAdapter.read(in);
+ list.add(instance);
+ }
+ in.endArray();
+ Object array = Array.newInstance(componentType, list.size());
+ for (int i = 0; i < list.size(); i++) {
+ Array.set(array, i, list.get(i));
+ }
+ return array;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public void write(JsonWriter out, Object array) throws IOException {
+ if (array == null) {
+ out.nullValue();
+ return;
+ }
+
+ out.beginArray();
+ for (int i = 0, length = Array.getLength(array); i < length; i++) {
+ E value = (E) Array.get(array, i);
+ componentTypeAdapter.write(out, value);
+ }
+ out.endArray();
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java
new file mode 100644
index 00000000..0b95445a
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.internal.ConstructorConstructor;
+import com.google.gson.internal.ObjectConstructor;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+/**
+ * Adapt a homogeneous collection of objects.
+ */
+public final class CollectionTypeAdapterFactory implements TypeAdapterFactory {
+ private final ConstructorConstructor constructorConstructor;
+
+ public CollectionTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
+ this.constructorConstructor = constructorConstructor;
+ }
+
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ Type type = typeToken.getType();
+
+ Class<? super T> rawType = typeToken.getRawType();
+ if (!Collection.class.isAssignableFrom(rawType)) {
+ return null;
+ }
+
+ Type elementType = $Gson$Types.getCollectionElementType(type, rawType);
+ TypeAdapter<?> elementTypeAdapter = gson.getAdapter(TypeToken.get(elementType));
+ ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
+
+ @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
+ TypeAdapter<T> result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
+ return result;
+ }
+
+ private static final class Adapter<E> extends TypeAdapter<Collection<E>> {
+ private final TypeAdapter<E> elementTypeAdapter;
+ private final ObjectConstructor<? extends Collection<E>> constructor;
+
+ public Adapter(Gson context, Type elementType,
+ TypeAdapter<E> elementTypeAdapter,
+ ObjectConstructor<? extends Collection<E>> constructor) {
+ this.elementTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper<E>(context, elementTypeAdapter, elementType);
+ this.constructor = constructor;
+ }
+
+ public Collection<E> read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ Collection<E> collection = constructor.construct();
+ in.beginArray();
+ while (in.hasNext()) {
+ E instance = elementTypeAdapter.read(in);
+ collection.add(instance);
+ }
+ in.endArray();
+ return collection;
+ }
+
+ public void write(JsonWriter out, Collection<E> collection) throws IOException {
+ if (collection == null) {
+ out.nullValue();
+ return;
+ }
+
+ out.beginArray();
+ for (E element : collection) {
+ elementTypeAdapter.write(out, element);
+ }
+ out.endArray();
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java
new file mode 100644
index 00000000..f2571724
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/DateTypeAdapter.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Adapter for Date. Although this class appears stateless, it is not.
+ * DateFormat captures its time zone and locale when it is created, which gives
+ * this class state. DateFormat isn't thread safe either, so this class has
+ * to synchronize its read and write methods.
+ */
+public final class DateTypeAdapter extends TypeAdapter<Date> {
+ public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ return typeToken.getRawType() == Date.class ? (TypeAdapter<T>) new DateTypeAdapter() : null;
+ }
+ };
+
+ private final DateFormat enUsFormat
+ = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US);
+ private final DateFormat localFormat
+ = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT);
+ private final DateFormat iso8601Format = buildIso8601Format();
+
+ private static DateFormat buildIso8601Format() {
+ DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return iso8601Format;
+ }
+
+ @Override public Date read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ return deserializeToDate(in.nextString());
+ }
+
+ private synchronized Date deserializeToDate(String json) {
+ try {
+ return localFormat.parse(json);
+ } catch (ParseException ignored) {
+ }
+ try {
+ return enUsFormat.parse(json);
+ } catch (ParseException ignored) {
+ }
+ try {
+ return iso8601Format.parse(json);
+ } catch (ParseException e) {
+ throw new JsonSyntaxException(json, e);
+ }
+ }
+
+ @Override public synchronized void write(JsonWriter out, Date value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+ String dateFormatAsString = enUsFormat.format(value);
+ out.value(dateFormatAsString);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java
new file mode 100644
index 00000000..7e0be28f
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/JsonAdapterAnnotationTypeAdapterFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.internal.ConstructorConstructor;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Given a type T, looks for the annotation {@link JsonAdapter} and uses an instance of the
+ * specified class as the default type adapter.
+ *
+ * @since 2.3
+ */
+public final class JsonAdapterAnnotationTypeAdapterFactory implements TypeAdapterFactory {
+
+ private final ConstructorConstructor constructorConstructor;
+
+ public JsonAdapterAnnotationTypeAdapterFactory(ConstructorConstructor constructorConstructor) {
+ this.constructorConstructor = constructorConstructor;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> targetType) {
+ JsonAdapter annotation = targetType.getRawType().getAnnotation(JsonAdapter.class);
+ if (annotation == null) {
+ return null;
+ }
+ return (TypeAdapter<T>) getTypeAdapter(constructorConstructor, gson, targetType, annotation);
+ }
+
+ @SuppressWarnings("unchecked") // Casts guarded by conditionals.
+ static TypeAdapter<?> getTypeAdapter(ConstructorConstructor constructorConstructor, Gson gson,
+ TypeToken<?> fieldType, JsonAdapter annotation) {
+ Class<?> value = annotation.value();
+ if (TypeAdapter.class.isAssignableFrom(value)) {
+ Class<TypeAdapter<?>> typeAdapter = (Class<TypeAdapter<?>>) value;
+ return constructorConstructor.get(TypeToken.get(typeAdapter)).construct();
+ }
+ if (TypeAdapterFactory.class.isAssignableFrom(value)) {
+ Class<TypeAdapterFactory> typeAdapterFactory = (Class<TypeAdapterFactory>) value;
+ return constructorConstructor.get(TypeToken.get(typeAdapterFactory))
+ .construct()
+ .create(gson, fieldType);
+ }
+
+ throw new IllegalArgumentException(
+ "@JsonAdapter value must be TypeAdapter or TypeAdapterFactory reference.");
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java
new file mode 100644
index 00000000..6a836280
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeReader.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This reader walks the elements of a JsonElement as if it was coming from a
+ * character stream.
+ *
+ * @author Jesse Wilson
+ */
+public final class JsonTreeReader extends JsonReader {
+ private static final Reader UNREADABLE_READER = new Reader() {
+ @Override public int read(char[] buffer, int offset, int count) throws IOException {
+ throw new AssertionError();
+ }
+ @Override public void close() throws IOException {
+ throw new AssertionError();
+ }
+ };
+ private static final Object SENTINEL_CLOSED = new Object();
+
+ private final List<Object> stack = new ArrayList<Object>();
+
+ public JsonTreeReader(JsonElement element) {
+ super(UNREADABLE_READER);
+ stack.add(element);
+ }
+
+ @Override public void beginArray() throws IOException {
+ expect(JsonToken.BEGIN_ARRAY);
+ JsonArray array = (JsonArray) peekStack();
+ stack.add(array.iterator());
+ }
+
+ @Override public void endArray() throws IOException {
+ expect(JsonToken.END_ARRAY);
+ popStack(); // empty iterator
+ popStack(); // array
+ }
+
+ @Override public void beginObject() throws IOException {
+ expect(JsonToken.BEGIN_OBJECT);
+ JsonObject object = (JsonObject) peekStack();
+ stack.add(object.entrySet().iterator());
+ }
+
+ @Override public void endObject() throws IOException {
+ expect(JsonToken.END_OBJECT);
+ popStack(); // empty iterator
+ popStack(); // object
+ }
+
+ @Override public boolean hasNext() throws IOException {
+ JsonToken token = peek();
+ return token != JsonToken.END_OBJECT && token != JsonToken.END_ARRAY;
+ }
+
+ @Override public JsonToken peek() throws IOException {
+ if (stack.isEmpty()) {
+ return JsonToken.END_DOCUMENT;
+ }
+
+ Object o = peekStack();
+ if (o instanceof Iterator) {
+ boolean isObject = stack.get(stack.size() - 2) instanceof JsonObject;
+ Iterator<?> iterator = (Iterator<?>) o;
+ if (iterator.hasNext()) {
+ if (isObject) {
+ return JsonToken.NAME;
+ } else {
+ stack.add(iterator.next());
+ return peek();
+ }
+ } else {
+ return isObject ? JsonToken.END_OBJECT : JsonToken.END_ARRAY;
+ }
+ } else if (o instanceof JsonObject) {
+ return JsonToken.BEGIN_OBJECT;
+ } else if (o instanceof JsonArray) {
+ return JsonToken.BEGIN_ARRAY;
+ } else if (o instanceof JsonPrimitive) {
+ JsonPrimitive primitive = (JsonPrimitive) o;
+ if (primitive.isString()) {
+ return JsonToken.STRING;
+ } else if (primitive.isBoolean()) {
+ return JsonToken.BOOLEAN;
+ } else if (primitive.isNumber()) {
+ return JsonToken.NUMBER;
+ } else {
+ throw new AssertionError();
+ }
+ } else if (o instanceof JsonNull) {
+ return JsonToken.NULL;
+ } else if (o == SENTINEL_CLOSED) {
+ throw new IllegalStateException("JsonReader is closed");
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ private Object peekStack() {
+ return stack.get(stack.size() - 1);
+ }
+
+ private Object popStack() {
+ return stack.remove(stack.size() - 1);
+ }
+
+ private void expect(JsonToken expected) throws IOException {
+ if (peek() != expected) {
+ throw new IllegalStateException("Expected " + expected + " but was " + peek());
+ }
+ }
+
+ @Override public String nextName() throws IOException {
+ expect(JsonToken.NAME);
+ Iterator<?> i = (Iterator<?>) peekStack();
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
+ stack.add(entry.getValue());
+ return (String) entry.getKey();
+ }
+
+ @Override public String nextString() throws IOException {
+ JsonToken token = peek();
+ if (token != JsonToken.STRING && token != JsonToken.NUMBER) {
+ throw new IllegalStateException("Expected " + JsonToken.STRING + " but was " + token);
+ }
+ return ((JsonPrimitive) popStack()).getAsString();
+ }
+
+ @Override public boolean nextBoolean() throws IOException {
+ expect(JsonToken.BOOLEAN);
+ return ((JsonPrimitive) popStack()).getAsBoolean();
+ }
+
+ @Override public void nextNull() throws IOException {
+ expect(JsonToken.NULL);
+ popStack();
+ }
+
+ @Override public double nextDouble() throws IOException {
+ JsonToken token = peek();
+ if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
+ throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token);
+ }
+ double result = ((JsonPrimitive) peekStack()).getAsDouble();
+ if (!isLenient() && (Double.isNaN(result) || Double.isInfinite(result))) {
+ throw new NumberFormatException("JSON forbids NaN and infinities: " + result);
+ }
+ popStack();
+ return result;
+ }
+
+ @Override public long nextLong() throws IOException {
+ JsonToken token = peek();
+ if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
+ throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token);
+ }
+ long result = ((JsonPrimitive) peekStack()).getAsLong();
+ popStack();
+ return result;
+ }
+
+ @Override public int nextInt() throws IOException {
+ JsonToken token = peek();
+ if (token != JsonToken.NUMBER && token != JsonToken.STRING) {
+ throw new IllegalStateException("Expected " + JsonToken.NUMBER + " but was " + token);
+ }
+ int result = ((JsonPrimitive) peekStack()).getAsInt();
+ popStack();
+ return result;
+ }
+
+ @Override public void close() throws IOException {
+ stack.clear();
+ stack.add(SENTINEL_CLOSED);
+ }
+
+ @Override public void skipValue() throws IOException {
+ if (peek() == JsonToken.NAME) {
+ nextName();
+ } else {
+ popStack();
+ }
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName();
+ }
+
+ public void promoteNameToValue() throws IOException {
+ expect(JsonToken.NAME);
+ Iterator<?> i = (Iterator<?>) peekStack();
+ Map.Entry<?, ?> entry = (Map.Entry<?, ?>) i.next();
+ stack.add(entry.getValue());
+ stack.add(new JsonPrimitive((String)entry.getKey()));
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java
new file mode 100644
index 00000000..5f9f0395
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/JsonTreeWriter.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This writer creates a JsonElement.
+ */
+public final class JsonTreeWriter extends JsonWriter {
+ private static final Writer UNWRITABLE_WRITER = new Writer() {
+ @Override public void write(char[] buffer, int offset, int counter) {
+ throw new AssertionError();
+ }
+ @Override public void flush() throws IOException {
+ throw new AssertionError();
+ }
+ @Override public void close() throws IOException {
+ throw new AssertionError();
+ }
+ };
+ /** Added to the top of the stack when this writer is closed to cause following ops to fail. */
+ private static final JsonPrimitive SENTINEL_CLOSED = new JsonPrimitive("closed");
+
+ /** The JsonElements and JsonArrays under modification, outermost to innermost. */
+ private final List<JsonElement> stack = new ArrayList<JsonElement>();
+
+ /** The name for the next JSON object value. If non-null, the top of the stack is a JsonObject. */
+ private String pendingName;
+
+ /** the JSON element constructed by this writer. */
+ private JsonElement product = JsonNull.INSTANCE; // TODO: is this really what we want?;
+
+ public JsonTreeWriter() {
+ super(UNWRITABLE_WRITER);
+ }
+
+ /**
+ * Returns the top level object produced by this writer.
+ */
+ public JsonElement get() {
+ if (!stack.isEmpty()) {
+ throw new IllegalStateException("Expected one JSON element but was " + stack);
+ }
+ return product;
+ }
+
+ private JsonElement peek() {
+ return stack.get(stack.size() - 1);
+ }
+
+ private void put(JsonElement value) {
+ if (pendingName != null) {
+ if (!value.isJsonNull() || getSerializeNulls()) {
+ JsonObject object = (JsonObject) peek();
+ object.add(pendingName, value);
+ }
+ pendingName = null;
+ } else if (stack.isEmpty()) {
+ product = value;
+ } else {
+ JsonElement element = peek();
+ if (element instanceof JsonArray) {
+ ((JsonArray) element).add(value);
+ } else {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ @Override public JsonWriter beginArray() throws IOException {
+ JsonArray array = new JsonArray();
+ put(array);
+ stack.add(array);
+ return this;
+ }
+
+ @Override public JsonWriter endArray() throws IOException {
+ if (stack.isEmpty() || pendingName != null) {
+ throw new IllegalStateException();
+ }
+ JsonElement element = peek();
+ if (element instanceof JsonArray) {
+ stack.remove(stack.size() - 1);
+ return this;
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override public JsonWriter beginObject() throws IOException {
+ JsonObject object = new JsonObject();
+ put(object);
+ stack.add(object);
+ return this;
+ }
+
+ @Override public JsonWriter endObject() throws IOException {
+ if (stack.isEmpty() || pendingName != null) {
+ throw new IllegalStateException();
+ }
+ JsonElement element = peek();
+ if (element instanceof JsonObject) {
+ stack.remove(stack.size() - 1);
+ return this;
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override public JsonWriter name(String name) throws IOException {
+ if (stack.isEmpty() || pendingName != null) {
+ throw new IllegalStateException();
+ }
+ JsonElement element = peek();
+ if (element instanceof JsonObject) {
+ pendingName = name;
+ return this;
+ }
+ throw new IllegalStateException();
+ }
+
+ @Override public JsonWriter value(String value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+ put(new JsonPrimitive(value));
+ return this;
+ }
+
+ @Override public JsonWriter nullValue() throws IOException {
+ put(JsonNull.INSTANCE);
+ return this;
+ }
+
+ @Override public JsonWriter value(boolean value) throws IOException {
+ put(new JsonPrimitive(value));
+ return this;
+ }
+
+ @Override public JsonWriter value(double value) throws IOException {
+ if (!isLenient() && (Double.isNaN(value) || Double.isInfinite(value))) {
+ throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
+ }
+ put(new JsonPrimitive(value));
+ return this;
+ }
+
+ @Override public JsonWriter value(long value) throws IOException {
+ put(new JsonPrimitive(value));
+ return this;
+ }
+
+ @Override public JsonWriter value(Number value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+
+ if (!isLenient()) {
+ double d = value.doubleValue();
+ if (Double.isNaN(d) || Double.isInfinite(d)) {
+ throw new IllegalArgumentException("JSON forbids NaN and infinities: " + value);
+ }
+ }
+
+ put(new JsonPrimitive(value));
+ return this;
+ }
+
+ @Override public void flush() throws IOException {
+ }
+
+ @Override public void close() throws IOException {
+ if (!stack.isEmpty()) {
+ throw new IOException("Incomplete document");
+ }
+ stack.add(SENTINEL_CLOSED);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java
new file mode 100644
index 00000000..c3c616c4
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/MapTypeAdapterFactory.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.internal.ConstructorConstructor;
+import com.google.gson.internal.JsonReaderInternalAccess;
+import com.google.gson.internal.ObjectConstructor;
+import com.google.gson.internal.Streams;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Adapts maps to either JSON objects or JSON arrays.
+ *
+ * <h3>Maps as JSON objects</h3>
+ * For primitive keys or when complex map key serialization is not enabled, this
+ * converts Java {@link Map Maps} to JSON Objects. This requires that map keys
+ * can be serialized as strings; this is insufficient for some key types. For
+ * example, consider a map whose keys are points on a grid. The default JSON
+ * form encodes reasonably: <pre> {@code
+ * Map<Point, String> original = new LinkedHashMap<Point, String>();
+ * original.put(new Point(5, 6), "a");
+ * original.put(new Point(8, 8), "b");
+ * System.out.println(gson.toJson(original, type));
+ * }</pre>
+ * The above code prints this JSON object:<pre> {@code
+ * {
+ * "(5,6)": "a",
+ * "(8,8)": "b"
+ * }
+ * }</pre>
+ * But GSON is unable to deserialize this value because the JSON string name is
+ * just the {@link Object#toString() toString()} of the map key. Attempting to
+ * convert the above JSON to an object fails with a parse exception:
+ * <pre>com.google.gson.JsonParseException: Expecting object found: "(5,6)"
+ * at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
+ * at com.google.gson.ObjectNavigator.navigateClassFields
+ * ...</pre>
+ *
+ * <h3>Maps as JSON arrays</h3>
+ * An alternative approach taken by this type adapter when it is required and
+ * complex map key serialization is enabled is to encode maps as arrays of map
+ * entries. Each map entry is a two element array containing a key and a value.
+ * This approach is more flexible because any type can be used as the map's key;
+ * not just strings. But it's also less portable because the receiver of such
+ * JSON must be aware of the map entry convention.
+ *
+ * <p>Register this adapter when you are creating your GSON instance.
+ * <pre> {@code
+ * Gson gson = new GsonBuilder()
+ * .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
+ * .create();
+ * }</pre>
+ * This will change the structure of the JSON emitted by the code above. Now we
+ * get an array. In this case the arrays elements are map entries:
+ * <pre> {@code
+ * [
+ * [
+ * {
+ * "x": 5,
+ * "y": 6
+ * },
+ * "a",
+ * ],
+ * [
+ * {
+ * "x": 8,
+ * "y": 8
+ * },
+ * "b"
+ * ]
+ * ]
+ * }</pre>
+ * This format will serialize and deserialize just fine as long as this adapter
+ * is registered.
+ */
+public final class MapTypeAdapterFactory implements TypeAdapterFactory {
+ private final ConstructorConstructor constructorConstructor;
+ private final boolean complexMapKeySerialization;
+
+ public MapTypeAdapterFactory(ConstructorConstructor constructorConstructor,
+ boolean complexMapKeySerialization) {
+ this.constructorConstructor = constructorConstructor;
+ this.complexMapKeySerialization = complexMapKeySerialization;
+ }
+
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ Type type = typeToken.getType();
+
+ Class<? super T> rawType = typeToken.getRawType();
+ if (!Map.class.isAssignableFrom(rawType)) {
+ return null;
+ }
+
+ Class<?> rawTypeOfSrc = $Gson$Types.getRawType(type);
+ Type[] keyAndValueTypes = $Gson$Types.getMapKeyAndValueTypes(type, rawTypeOfSrc);
+ TypeAdapter<?> keyAdapter = getKeyAdapter(gson, keyAndValueTypes[0]);
+ TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(keyAndValueTypes[1]));
+ ObjectConstructor<T> constructor = constructorConstructor.get(typeToken);
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ // we don't define a type parameter for the key or value types
+ TypeAdapter<T> result = new Adapter(gson, keyAndValueTypes[0], keyAdapter,
+ keyAndValueTypes[1], valueAdapter, constructor);
+ return result;
+ }
+
+ /**
+ * Returns a type adapter that writes the value as a string.
+ */
+ private TypeAdapter<?> getKeyAdapter(Gson context, Type keyType) {
+ return (keyType == boolean.class || keyType == Boolean.class)
+ ? TypeAdapters.BOOLEAN_AS_STRING
+ : context.getAdapter(TypeToken.get(keyType));
+ }
+
+ private final class Adapter<K, V> extends TypeAdapter<Map<K, V>> {
+ private final TypeAdapter<K> keyTypeAdapter;
+ private final TypeAdapter<V> valueTypeAdapter;
+ private final ObjectConstructor<? extends Map<K, V>> constructor;
+
+ public Adapter(Gson context, Type keyType, TypeAdapter<K> keyTypeAdapter,
+ Type valueType, TypeAdapter<V> valueTypeAdapter,
+ ObjectConstructor<? extends Map<K, V>> constructor) {
+ this.keyTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper<K>(context, keyTypeAdapter, keyType);
+ this.valueTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper<V>(context, valueTypeAdapter, valueType);
+ this.constructor = constructor;
+ }
+
+ public Map<K, V> read(JsonReader in) throws IOException {
+ JsonToken peek = in.peek();
+ if (peek == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ Map<K, V> map = constructor.construct();
+
+ if (peek == JsonToken.BEGIN_ARRAY) {
+ in.beginArray();
+ while (in.hasNext()) {
+ in.beginArray(); // entry array
+ K key = keyTypeAdapter.read(in);
+ V value = valueTypeAdapter.read(in);
+ V replaced = map.put(key, value);
+ if (replaced != null) {
+ throw new JsonSyntaxException("duplicate key: " + key);
+ }
+ in.endArray();
+ }
+ in.endArray();
+ } else {
+ in.beginObject();
+ while (in.hasNext()) {
+ JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
+ K key = keyTypeAdapter.read(in);
+ V value = valueTypeAdapter.read(in);
+ V replaced = map.put(key, value);
+ if (replaced != null) {
+ throw new JsonSyntaxException("duplicate key: " + key);
+ }
+ }
+ in.endObject();
+ }
+ return map;
+ }
+
+ public void write(JsonWriter out, Map<K, V> map) throws IOException {
+ if (map == null) {
+ out.nullValue();
+ return;
+ }
+
+ if (!complexMapKeySerialization) {
+ out.beginObject();
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ out.name(String.valueOf(entry.getKey()));
+ valueTypeAdapter.write(out, entry.getValue());
+ }
+ out.endObject();
+ return;
+ }
+
+ boolean hasComplexKeys = false;
+ List<JsonElement> keys = new ArrayList<JsonElement>(map.size());
+
+ List<V> values = new ArrayList<V>(map.size());
+ for (Map.Entry<K, V> entry : map.entrySet()) {
+ JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey());
+ keys.add(keyElement);
+ values.add(entry.getValue());
+ hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject();
+ }
+
+ if (hasComplexKeys) {
+ out.beginArray();
+ for (int i = 0; i < keys.size(); i++) {
+ out.beginArray(); // entry array
+ Streams.write(keys.get(i), out);
+ valueTypeAdapter.write(out, values.get(i));
+ out.endArray();
+ }
+ out.endArray();
+ } else {
+ out.beginObject();
+ for (int i = 0; i < keys.size(); i++) {
+ JsonElement keyElement = keys.get(i);
+ out.name(keyToString(keyElement));
+ valueTypeAdapter.write(out, values.get(i));
+ }
+ out.endObject();
+ }
+ }
+
+ private String keyToString(JsonElement keyElement) {
+ if (keyElement.isJsonPrimitive()) {
+ JsonPrimitive primitive = keyElement.getAsJsonPrimitive();
+ if (primitive.isNumber()) {
+ return String.valueOf(primitive.getAsNumber());
+ } else if (primitive.isBoolean()) {
+ return Boolean.toString(primitive.getAsBoolean());
+ } else if (primitive.isString()) {
+ return primitive.getAsString();
+ } else {
+ throw new AssertionError();
+ }
+ } else if (keyElement.isJsonNull()) {
+ return "null";
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java
new file mode 100644
index 00000000..235d2b38
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.internal.LinkedTreeMap;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Adapts types whose static type is only 'Object'. Uses getClass() on
+ * serialization and a primitive/Map/List on deserialization.
+ */
+public final class ObjectTypeAdapter extends TypeAdapter<Object> {
+ public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked")
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ if (type.getRawType() == Object.class) {
+ return (TypeAdapter<T>) new ObjectTypeAdapter(gson);
+ }
+ return null;
+ }
+ };
+
+ private final Gson gson;
+
+ private ObjectTypeAdapter(Gson gson) {
+ this.gson = gson;
+ }
+
+ @Override public Object read(JsonReader in) throws IOException {
+ JsonToken token = in.peek();
+ switch (token) {
+ case BEGIN_ARRAY:
+ List<Object> list = new ArrayList<Object>();
+ in.beginArray();
+ while (in.hasNext()) {
+ list.add(read(in));
+ }
+ in.endArray();
+ return list;
+
+ case BEGIN_OBJECT:
+ Map<String, Object> map = new LinkedTreeMap<String, Object>();
+ in.beginObject();
+ while (in.hasNext()) {
+ map.put(in.nextName(), read(in));
+ }
+ in.endObject();
+ return map;
+
+ case STRING:
+ return in.nextString();
+
+ case NUMBER:
+ return in.nextDouble();
+
+ case BOOLEAN:
+ return in.nextBoolean();
+
+ case NULL:
+ in.nextNull();
+ return null;
+
+ default:
+ throw new IllegalStateException();
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override public void write(JsonWriter out, Object value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+
+ TypeAdapter<Object> typeAdapter = (TypeAdapter<Object>) gson.getAdapter(value.getClass());
+ if (typeAdapter instanceof ObjectTypeAdapter) {
+ out.beginObject();
+ out.endObject();
+ return;
+ }
+
+ typeAdapter.write(out, value);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java
new file mode 100644
index 00000000..3957c369
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import static com.google.gson.internal.bind.JsonAdapterAnnotationTypeAdapterFactory.getTypeAdapter;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.FieldNamingStrategy;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.internal.ConstructorConstructor;
+import com.google.gson.internal.Excluder;
+import com.google.gson.internal.ObjectConstructor;
+import com.google.gson.internal.Primitives;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Type adapter that reflects over the fields and methods of a class.
+ */
+public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory {
+ private final ConstructorConstructor constructorConstructor;
+ private final FieldNamingStrategy fieldNamingPolicy;
+ private final Excluder excluder;
+
+ public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
+ FieldNamingStrategy fieldNamingPolicy, Excluder excluder) {
+ this.constructorConstructor = constructorConstructor;
+ this.fieldNamingPolicy = fieldNamingPolicy;
+ this.excluder = excluder;
+ }
+
+ public boolean excludeField(Field f, boolean serialize) {
+ return excludeField(f, serialize, excluder);
+ }
+
+ static boolean excludeField(Field f, boolean serialize, Excluder excluder) {
+ return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize);
+ }
+
+ /** first element holds the default name */
+ private List<String> getFieldNames(Field f) {
+ return getFieldName(fieldNamingPolicy, f);
+ }
+
+ /** first element holds the default name */
+ static List<String> getFieldName(FieldNamingStrategy fieldNamingPolicy, Field f) {
+ SerializedName serializedName = f.getAnnotation(SerializedName.class);
+ List<String> fieldNames = new LinkedList<String>();
+ if (serializedName == null) {
+ fieldNames.add(fieldNamingPolicy.translateName(f));
+ } else {
+ fieldNames.add(serializedName.value());
+ for (String alternate : serializedName.alternate()) {
+ fieldNames.add(alternate);
+ }
+ }
+ return fieldNames;
+ }
+
+ public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
+ Class<? super T> raw = type.getRawType();
+
+ if (!Object.class.isAssignableFrom(raw)) {
+ return null; // it's a primitive!
+ }
+
+ ObjectConstructor<T> constructor = constructorConstructor.get(type);
+ return new Adapter<T>(constructor, getBoundFields(gson, type, raw));
+ }
+
+ private ReflectiveTypeAdapterFactory.BoundField createBoundField(
+ final Gson context, final Field field, final String name,
+ final TypeToken<?> fieldType, boolean serialize, boolean deserialize) {
+ final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType());
+ // special casing primitives here saves ~5% on Android...
+ return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) {
+ final TypeAdapter<?> typeAdapter = getFieldAdapter(context, field, fieldType);
+ @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree
+ @Override void write(JsonWriter writer, Object value)
+ throws IOException, IllegalAccessException {
+ Object fieldValue = field.get(value);
+ TypeAdapter t =
+ new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType());
+ t.write(writer, fieldValue);
+ }
+ @Override void read(JsonReader reader, Object value)
+ throws IOException, IllegalAccessException {
+ Object fieldValue = typeAdapter.read(reader);
+ if (fieldValue != null || !isPrimitive) {
+ field.set(value, fieldValue);
+ }
+ }
+ public boolean writeField(Object value) throws IOException, IllegalAccessException {
+ if (!serialized) return false;
+ Object fieldValue = field.get(value);
+ return fieldValue != value; // avoid recursion for example for Throwable.cause
+ }
+ };
+ }
+
+ private TypeAdapter<?> getFieldAdapter(Gson gson, Field field, TypeToken<?> fieldType) {
+ JsonAdapter annotation = field.getAnnotation(JsonAdapter.class);
+ if (annotation != null) {
+ TypeAdapter<?> adapter = getTypeAdapter(constructorConstructor, gson, fieldType, annotation);
+ if (adapter != null) return adapter;
+ }
+ return gson.getAdapter(fieldType);
+ }
+
+ private Map<String, BoundField> getBoundFields(Gson context, TypeToken<?> type, Class<?> raw) {
+ Map<String, BoundField> result = new LinkedHashMap<String, BoundField>();
+ if (raw.isInterface()) {
+ return result;
+ }
+
+ Type declaredType = type.getType();
+ while (raw != Object.class) {
+ Field[] fields = raw.getDeclaredFields();
+ for (Field field : fields) {
+ boolean serialize = excludeField(field, true);
+ boolean deserialize = excludeField(field, false);
+ if (!serialize && !deserialize) {
+ continue;
+ }
+ field.setAccessible(true);
+ Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType());
+ List<String> fieldNames = getFieldNames(field);
+ BoundField previous = null;
+ for (int i = 0; i < fieldNames.size(); ++i) {
+ String name = fieldNames.get(i);
+ if (i != 0) serialize = false; // only serialize the default name
+ BoundField boundField = createBoundField(context, field, name,
+ TypeToken.get(fieldType), serialize, deserialize);
+ BoundField replaced = result.put(name, boundField);
+ if (previous == null) previous = replaced;
+ }
+ if (previous != null) {
+ throw new IllegalArgumentException(declaredType
+ + " declares multiple JSON fields named " + previous.name);
+ }
+ }
+ type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass()));
+ raw = type.getRawType();
+ }
+ return result;
+ }
+
+ static abstract class BoundField {
+ final String name;
+ final boolean serialized;
+ final boolean deserialized;
+
+ protected BoundField(String name, boolean serialized, boolean deserialized) {
+ this.name = name;
+ this.serialized = serialized;
+ this.deserialized = deserialized;
+ }
+ abstract boolean writeField(Object value) throws IOException, IllegalAccessException;
+ abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException;
+ abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException;
+ }
+
+ public static final class Adapter<T> extends TypeAdapter<T> {
+ private final ObjectConstructor<T> constructor;
+ private final Map<String, BoundField> boundFields;
+
+ private Adapter(ObjectConstructor<T> constructor, Map<String, BoundField> boundFields) {
+ this.constructor = constructor;
+ this.boundFields = boundFields;
+ }
+
+ @Override public T read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ T instance = constructor.construct();
+
+ try {
+ in.beginObject();
+ while (in.hasNext()) {
+ String name = in.nextName();
+ BoundField field = boundFields.get(name);
+ if (field == null || !field.deserialized) {
+ in.skipValue();
+ } else {
+ field.read(in, instance);
+ }
+ }
+ } catch (IllegalStateException e) {
+ throw new JsonSyntaxException(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ }
+ in.endObject();
+ return instance;
+ }
+
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+
+ out.beginObject();
+ try {
+ for (BoundField boundField : boundFields.values()) {
+ if (boundField.writeField(value)) {
+ out.name(boundField.name);
+ boundField.write(out, value);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new AssertionError();
+ }
+ out.endObject();
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java
new file mode 100644
index 00000000..5ecc2e96
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/SqlDateTypeAdapter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+/**
+ * Adapter for java.sql.Date. Although this class appears stateless, it is not.
+ * DateFormat captures its time zone and locale when it is created, which gives
+ * 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() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ return typeToken.getRawType() == java.sql.Date.class
+ ? (TypeAdapter<T>) new SqlDateTypeAdapter() : null;
+ }
+ };
+
+ private final DateFormat format = new SimpleDateFormat("MMM d, yyyy");
+
+ @Override
+ public synchronized java.sql.Date read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ final long utilDate = format.parse(in.nextString()).getTime();
+ return new java.sql.Date(utilDate);
+ } catch (ParseException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+
+ @Override
+ public synchronized void write(JsonWriter out, java.sql.Date value) throws IOException {
+ out.value(value == null ? null : format.format(value));
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java
new file mode 100644
index 00000000..bbbb4d97
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/TimeTypeAdapter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.sql.Time;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * Adapter for Time. Although this class appears stateless, it is not.
+ * DateFormat captures its time zone and locale when it is created, which gives
+ * this class state. DateFormat isn't thread safe either, so this class has
+ * to synchronize its read and write methods.
+ */
+public final class TimeTypeAdapter extends TypeAdapter<Time> {
+ public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ return typeToken.getRawType() == Time.class ? (TypeAdapter<T>) new TimeTypeAdapter() : null;
+ }
+ };
+
+ private final DateFormat format = new SimpleDateFormat("hh:mm:ss a");
+
+ @Override public synchronized Time read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ Date date = format.parse(in.nextString());
+ return new Time(date.getTime());
+ } catch (ParseException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+
+ @Override public synchronized void write(JsonWriter out, Time value) throws IOException {
+ out.value(value == null ? null : format.format(value));
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java
new file mode 100644
index 00000000..7e52c27d
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapterRuntimeTypeWrapper.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+
+final class TypeAdapterRuntimeTypeWrapper<T> extends TypeAdapter<T> {
+ private final Gson context;
+ private final TypeAdapter<T> delegate;
+ private final Type type;
+
+ TypeAdapterRuntimeTypeWrapper(Gson context, TypeAdapter<T> delegate, Type type) {
+ this.context = context;
+ this.delegate = delegate;
+ this.type = type;
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ return delegate.read(in);
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ // Order of preference for choosing type adapters
+ // First preference: a type adapter registered for the runtime type
+ // Second preference: a type adapter registered for the declared type
+ // Third preference: reflective type adapter for the runtime type (if it is a sub class of the declared type)
+ // Fourth preference: reflective type adapter for the declared type
+
+ TypeAdapter chosen = delegate;
+ Type runtimeType = getRuntimeTypeIfMoreSpecific(type, value);
+ if (runtimeType != type) {
+ TypeAdapter runtimeTypeAdapter = context.getAdapter(TypeToken.get(runtimeType));
+ if (!(runtimeTypeAdapter instanceof ReflectiveTypeAdapterFactory.Adapter)) {
+ // The user registered a type adapter for the runtime type, so we will use that
+ chosen = runtimeTypeAdapter;
+ } else if (!(delegate instanceof ReflectiveTypeAdapterFactory.Adapter)) {
+ // The user registered a type adapter for Base class, so we prefer it over the
+ // reflective type adapter for the runtime type
+ chosen = delegate;
+ } else {
+ // Use the type adapter for runtime type
+ chosen = runtimeTypeAdapter;
+ }
+ }
+ chosen.write(out, value);
+ }
+
+ /**
+ * Finds a compatible runtime type if it is more specific
+ */
+ private Type getRuntimeTypeIfMoreSpecific(Type type, Object value) {
+ if (value != null
+ && (type == Object.class || type instanceof TypeVariable<?> || type instanceof Class<?>)) {
+ type = value.getClass();
+ }
+ return type;
+ }
+}
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
new file mode 100644
index 00000000..ec7ceb4f
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java
@@ -0,0 +1,833 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.sql.Timestamp;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.UUID;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.internal.LazilyParsedNumber;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Type adapters for basic types.
+ */
+public final class TypeAdapters {
+ private TypeAdapters() {
+ throw new UnsupportedOperationException();
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static final TypeAdapter<Class> CLASS = new TypeAdapter<Class>() {
+ @Override
+ public void write(JsonWriter out, Class value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ } else {
+ throw new UnsupportedOperationException("Attempted to serialize java.lang.Class: "
+ + value.getName() + ". Forgot to register a type adapter?");
+ }
+ }
+ @Override
+ public Class read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ } else {
+ throw new UnsupportedOperationException(
+ "Attempted to deserialize a java.lang.Class. Forgot to register a type adapter?");
+ }
+ }
+ };
+ public static final TypeAdapterFactory CLASS_FACTORY = newFactory(Class.class, CLASS);
+
+ public static final TypeAdapter<BitSet> BIT_SET = new TypeAdapter<BitSet>() {
+ public BitSet read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ BitSet bitset = new BitSet();
+ in.beginArray();
+ int i = 0;
+ JsonToken tokenType = in.peek();
+ while (tokenType != JsonToken.END_ARRAY) {
+ boolean set;
+ switch (tokenType) {
+ case NUMBER:
+ set = in.nextInt() != 0;
+ break;
+ case BOOLEAN:
+ set = in.nextBoolean();
+ break;
+ case STRING:
+ String stringValue = in.nextString();
+ try {
+ set = Integer.parseInt(stringValue) != 0;
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(
+ "Error: Expecting: bitset number value (1, 0), Found: " + stringValue);
+ }
+ break;
+ default:
+ throw new JsonSyntaxException("Invalid bitset value type: " + tokenType);
+ }
+ if (set) {
+ bitset.set(i);
+ }
+ ++i;
+ tokenType = in.peek();
+ }
+ in.endArray();
+ return bitset;
+ }
+
+ public void write(JsonWriter out, BitSet src) throws IOException {
+ if (src == null) {
+ out.nullValue();
+ return;
+ }
+
+ out.beginArray();
+ for (int i = 0; i < src.length(); i++) {
+ int value = (src.get(i)) ? 1 : 0;
+ out.value(value);
+ }
+ out.endArray();
+ }
+ };
+
+ public static final TypeAdapterFactory BIT_SET_FACTORY = newFactory(BitSet.class, BIT_SET);
+
+ public static final TypeAdapter<Boolean> BOOLEAN = new TypeAdapter<Boolean>() {
+ @Override
+ public Boolean read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ } else if (in.peek() == JsonToken.STRING) {
+ // support strings for compatibility with GSON 1.7
+ return Boolean.parseBoolean(in.nextString());
+ }
+ return in.nextBoolean();
+ }
+ @Override
+ public void write(JsonWriter out, Boolean value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+ out.value(value);
+ }
+ };
+
+ /**
+ * Writes a boolean as a string. Useful for map keys, where booleans aren't
+ * otherwise permitted.
+ */
+ public static final TypeAdapter<Boolean> BOOLEAN_AS_STRING = new TypeAdapter<Boolean>() {
+ @Override public Boolean read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ return Boolean.valueOf(in.nextString());
+ }
+
+ @Override public void write(JsonWriter out, Boolean value) throws IOException {
+ out.value(value == null ? "null" : value.toString());
+ }
+ };
+
+ public static final TypeAdapterFactory BOOLEAN_FACTORY
+ = newFactory(boolean.class, Boolean.class, BOOLEAN);
+
+ public static final TypeAdapter<Number> BYTE = new TypeAdapter<Number>() {
+ @Override
+ public Number read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ int intValue = in.nextInt();
+ return (byte) intValue;
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, Number value) throws IOException {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapterFactory BYTE_FACTORY
+ = newFactory(byte.class, Byte.class, BYTE);
+
+ public static final TypeAdapter<Number> SHORT = new TypeAdapter<Number>() {
+ @Override
+ public Number read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ return (short) in.nextInt();
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, Number value) throws IOException {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapterFactory SHORT_FACTORY
+ = newFactory(short.class, Short.class, SHORT);
+
+ public static final TypeAdapter<Number> INTEGER = new TypeAdapter<Number>() {
+ @Override
+ public Number read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ return in.nextInt();
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, Number value) throws IOException {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapterFactory INTEGER_FACTORY
+ = newFactory(int.class, Integer.class, INTEGER);
+
+ public static final TypeAdapter<Number> LONG = new TypeAdapter<Number>() {
+ @Override
+ public Number read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ return in.nextLong();
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, Number value) throws IOException {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapter<Number> FLOAT = new TypeAdapter<Number>() {
+ @Override
+ public Number 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 {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapter<Number> DOUBLE = new TypeAdapter<Number>() {
+ @Override
+ public Number 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 {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapter<Number> NUMBER = new TypeAdapter<Number>() {
+ @Override
+ public Number read(JsonReader in) throws IOException {
+ JsonToken jsonToken = in.peek();
+ switch (jsonToken) {
+ case NULL:
+ in.nextNull();
+ return null;
+ case NUMBER:
+ return new LazilyParsedNumber(in.nextString());
+ default:
+ throw new JsonSyntaxException("Expecting number, got: " + jsonToken);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, Number value) throws IOException {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapterFactory NUMBER_FACTORY = newFactory(Number.class, NUMBER);
+
+ public static final TypeAdapter<Character> CHARACTER = new TypeAdapter<Character>() {
+ @Override
+ public Character read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ String str = in.nextString();
+ if (str.length() != 1) {
+ throw new JsonSyntaxException("Expecting character, got: " + str);
+ }
+ return str.charAt(0);
+ }
+ @Override
+ public void write(JsonWriter out, Character value) throws IOException {
+ out.value(value == null ? null : String.valueOf(value));
+ }
+ };
+
+ public static final TypeAdapterFactory CHARACTER_FACTORY
+ = newFactory(char.class, Character.class, CHARACTER);
+
+ public static final TypeAdapter<String> STRING = new TypeAdapter<String>() {
+ @Override
+ public String read(JsonReader in) throws IOException {
+ JsonToken peek = in.peek();
+ if (peek == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ /* coerce booleans to strings for backwards compatibility */
+ if (peek == JsonToken.BOOLEAN) {
+ return Boolean.toString(in.nextBoolean());
+ }
+ return in.nextString();
+ }
+ @Override
+ 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) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ return new BigDecimal(in.nextString());
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+
+ @Override public void write(JsonWriter out, BigDecimal 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) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ return new BigInteger(in.nextString());
+ } catch (NumberFormatException e) {
+ throw new JsonSyntaxException(e);
+ }
+ }
+
+ @Override public void write(JsonWriter out, BigInteger value) throws IOException {
+ out.value(value);
+ }
+ };
+
+ public static final TypeAdapterFactory STRING_FACTORY = newFactory(String.class, STRING);
+
+ public static final TypeAdapter<StringBuilder> STRING_BUILDER = new TypeAdapter<StringBuilder>() {
+ @Override
+ public StringBuilder read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ return new StringBuilder(in.nextString());
+ }
+ @Override
+ public void write(JsonWriter out, StringBuilder value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ };
+
+ public static final TypeAdapterFactory STRING_BUILDER_FACTORY =
+ newFactory(StringBuilder.class, STRING_BUILDER);
+
+ public static final TypeAdapter<StringBuffer> STRING_BUFFER = new TypeAdapter<StringBuffer>() {
+ @Override
+ public StringBuffer read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ return new StringBuffer(in.nextString());
+ }
+ @Override
+ public void write(JsonWriter out, StringBuffer value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ };
+
+ public static final TypeAdapterFactory STRING_BUFFER_FACTORY =
+ newFactory(StringBuffer.class, STRING_BUFFER);
+
+ public static final TypeAdapter<URL> URL = new TypeAdapter<URL>() {
+ @Override
+ public URL read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ String nextString = in.nextString();
+ return "null".equals(nextString) ? null : new URL(nextString);
+ }
+ @Override
+ public void write(JsonWriter out, URL value) throws IOException {
+ out.value(value == null ? null : value.toExternalForm());
+ }
+ };
+
+ public static final TypeAdapterFactory URL_FACTORY = newFactory(URL.class, URL);
+
+ public static final TypeAdapter<URI> URI = new TypeAdapter<URI>() {
+ @Override
+ public URI read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ try {
+ String nextString = in.nextString();
+ return "null".equals(nextString) ? null : new URI(nextString);
+ } catch (URISyntaxException e) {
+ throw new JsonIOException(e);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, URI value) throws IOException {
+ out.value(value == null ? null : value.toASCIIString());
+ }
+ };
+
+ public static final TypeAdapterFactory URI_FACTORY = newFactory(URI.class, URI);
+
+ public static final TypeAdapter<InetAddress> INET_ADDRESS = new TypeAdapter<InetAddress>() {
+ @Override
+ public InetAddress read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ // regrettably, this should have included both the host name and the host address
+ return InetAddress.getByName(in.nextString());
+ }
+ @Override
+ public void write(JsonWriter out, InetAddress value) throws IOException {
+ out.value(value == null ? null : value.getHostAddress());
+ }
+ };
+
+ public static final TypeAdapterFactory INET_ADDRESS_FACTORY =
+ newTypeHierarchyFactory(InetAddress.class, INET_ADDRESS);
+
+ public static final TypeAdapter<UUID> UUID = new TypeAdapter<UUID>() {
+ @Override
+ public UUID read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ return java.util.UUID.fromString(in.nextString());
+ }
+ @Override
+ public void write(JsonWriter out, UUID value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ };
+
+ public static final TypeAdapterFactory UUID_FACTORY = newFactory(UUID.class, UUID);
+
+ public static final TypeAdapterFactory TIMESTAMP_FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ 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";
+ private static final String DAY_OF_MONTH = "dayOfMonth";
+ private static final String HOUR_OF_DAY = "hourOfDay";
+ private static final String MINUTE = "minute";
+ private static final String SECOND = "second";
+
+ @Override
+ public Calendar read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ in.beginObject();
+ int year = 0;
+ int month = 0;
+ int dayOfMonth = 0;
+ int hourOfDay = 0;
+ int minute = 0;
+ int second = 0;
+ while (in.peek() != JsonToken.END_OBJECT) {
+ String name = in.nextName();
+ int value = in.nextInt();
+ if (YEAR.equals(name)) {
+ year = value;
+ } else if (MONTH.equals(name)) {
+ month = value;
+ } else if (DAY_OF_MONTH.equals(name)) {
+ dayOfMonth = value;
+ } else if (HOUR_OF_DAY.equals(name)) {
+ hourOfDay = value;
+ } else if (MINUTE.equals(name)) {
+ minute = value;
+ } else if (SECOND.equals(name)) {
+ second = value;
+ }
+ }
+ in.endObject();
+ return new GregorianCalendar(year, month, dayOfMonth, hourOfDay, minute, second);
+ }
+
+ @Override
+ public void write(JsonWriter out, Calendar value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ return;
+ }
+ out.beginObject();
+ out.name(YEAR);
+ out.value(value.get(Calendar.YEAR));
+ out.name(MONTH);
+ out.value(value.get(Calendar.MONTH));
+ out.name(DAY_OF_MONTH);
+ out.value(value.get(Calendar.DAY_OF_MONTH));
+ out.name(HOUR_OF_DAY);
+ out.value(value.get(Calendar.HOUR_OF_DAY));
+ out.name(MINUTE);
+ out.value(value.get(Calendar.MINUTE));
+ out.name(SECOND);
+ out.value(value.get(Calendar.SECOND));
+ out.endObject();
+ }
+ };
+
+ public static final TypeAdapterFactory CALENDAR_FACTORY =
+ newFactoryForMultipleTypes(Calendar.class, GregorianCalendar.class, CALENDAR);
+
+ public static final TypeAdapter<Locale> LOCALE = new TypeAdapter<Locale>() {
+ @Override
+ public Locale read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ String locale = in.nextString();
+ StringTokenizer tokenizer = new StringTokenizer(locale, "_");
+ String language = null;
+ String country = null;
+ String variant = null;
+ if (tokenizer.hasMoreElements()) {
+ language = tokenizer.nextToken();
+ }
+ if (tokenizer.hasMoreElements()) {
+ country = tokenizer.nextToken();
+ }
+ if (tokenizer.hasMoreElements()) {
+ variant = tokenizer.nextToken();
+ }
+ if (country == null && variant == null) {
+ return new Locale(language);
+ } else if (variant == null) {
+ return new Locale(language, country);
+ } else {
+ return new Locale(language, country, variant);
+ }
+ }
+ @Override
+ public void write(JsonWriter out, Locale value) throws IOException {
+ out.value(value == null ? null : value.toString());
+ }
+ };
+
+ public static final TypeAdapterFactory LOCALE_FACTORY = newFactory(Locale.class, LOCALE);
+
+ public static final TypeAdapter<JsonElement> JSON_ELEMENT = new TypeAdapter<JsonElement>() {
+ @Override public JsonElement read(JsonReader in) throws IOException {
+ switch (in.peek()) {
+ case STRING:
+ return new JsonPrimitive(in.nextString());
+ case NUMBER:
+ String number = in.nextString();
+ return new JsonPrimitive(new LazilyParsedNumber(number));
+ case BOOLEAN:
+ return new JsonPrimitive(in.nextBoolean());
+ case NULL:
+ in.nextNull();
+ return JsonNull.INSTANCE;
+ case BEGIN_ARRAY:
+ JsonArray array = new JsonArray();
+ in.beginArray();
+ while (in.hasNext()) {
+ array.add(read(in));
+ }
+ in.endArray();
+ return array;
+ case BEGIN_OBJECT:
+ JsonObject object = new JsonObject();
+ in.beginObject();
+ while (in.hasNext()) {
+ object.add(in.nextName(), read(in));
+ }
+ in.endObject();
+ return object;
+ case END_DOCUMENT:
+ case NAME:
+ case END_OBJECT:
+ case END_ARRAY:
+ default:
+ throw new IllegalArgumentException();
+ }
+ }
+
+ @Override public void write(JsonWriter out, JsonElement value) throws IOException {
+ if (value == null || value.isJsonNull()) {
+ out.nullValue();
+ } else if (value.isJsonPrimitive()) {
+ JsonPrimitive primitive = value.getAsJsonPrimitive();
+ if (primitive.isNumber()) {
+ out.value(primitive.getAsNumber());
+ } else if (primitive.isBoolean()) {
+ out.value(primitive.getAsBoolean());
+ } else {
+ out.value(primitive.getAsString());
+ }
+
+ } else if (value.isJsonArray()) {
+ out.beginArray();
+ for (JsonElement e : value.getAsJsonArray()) {
+ write(out, e);
+ }
+ out.endArray();
+
+ } else if (value.isJsonObject()) {
+ out.beginObject();
+ for (Map.Entry<String, JsonElement> e : value.getAsJsonObject().entrySet()) {
+ out.name(e.getKey());
+ write(out, e.getValue());
+ }
+ out.endObject();
+
+ } else {
+ throw new IllegalArgumentException("Couldn't write " + value.getClass());
+ }
+ }
+ };
+
+ public static final TypeAdapterFactory JSON_ELEMENT_FACTORY
+ = newTypeHierarchyFactory(JsonElement.class, JSON_ELEMENT);
+
+ private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
+ private final Map<String, T> nameToConstant = new HashMap<String, T>();
+ private final Map<T, String> constantToName = new HashMap<T, String>();
+
+ public EnumTypeAdapter(Class<T> classOfT) {
+ try {
+ for (T constant : classOfT.getEnumConstants()) {
+ String name = constant.name();
+ SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
+ if (annotation != null) {
+ name = annotation.value();
+ for (String alternate : annotation.alternate()) {
+ nameToConstant.put(alternate, constant);
+ }
+ }
+ nameToConstant.put(name, constant);
+ constantToName.put(constant, name);
+ }
+ } catch (NoSuchFieldException e) {
+ throw new AssertionError();
+ }
+ }
+ public T read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+ return nameToConstant.get(in.nextString());
+ }
+
+ public void write(JsonWriter out, T value) throws IOException {
+ out.value(value == null ? null : constantToName.get(value));
+ }
+ }
+
+ public static final TypeAdapterFactory ENUM_FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ Class<? super T> rawType = typeToken.getRawType();
+ if (!Enum.class.isAssignableFrom(rawType) || rawType == Enum.class) {
+ return null;
+ }
+ if (!rawType.isEnum()) {
+ rawType = rawType.getSuperclass(); // handle anonymous subclasses
+ }
+ return (TypeAdapter<T>) new EnumTypeAdapter(rawType);
+ }
+ };
+
+ public static <TT> TypeAdapterFactory newFactory(
+ final TypeToken<TT> type, final TypeAdapter<TT> typeAdapter) {
+ return new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ return typeToken.equals(type) ? (TypeAdapter<T>) typeAdapter : null;
+ }
+ };
+ }
+
+ public static <TT> TypeAdapterFactory newFactory(
+ final Class<TT> type, final TypeAdapter<TT> typeAdapter) {
+ return new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ return typeToken.getRawType() == type ? (TypeAdapter<T>) typeAdapter : null;
+ }
+ @Override public String toString() {
+ return "Factory[type=" + type.getName() + ",adapter=" + typeAdapter + "]";
+ }
+ };
+ }
+
+ public static <TT> TypeAdapterFactory newFactory(
+ final Class<TT> unboxed, final Class<TT> boxed, final TypeAdapter<? super TT> typeAdapter) {
+ return new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ Class<? super T> rawType = typeToken.getRawType();
+ return (rawType == unboxed || rawType == boxed) ? (TypeAdapter<T>) typeAdapter : null;
+ }
+ @Override public String toString() {
+ return "Factory[type=" + boxed.getName()
+ + "+" + unboxed.getName() + ",adapter=" + typeAdapter + "]";
+ }
+ };
+ }
+
+ public static <TT> TypeAdapterFactory newFactoryForMultipleTypes(final Class<TT> base,
+ final Class<? extends TT> sub, final TypeAdapter<? super TT> typeAdapter) {
+ return new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ Class<? super T> rawType = typeToken.getRawType();
+ return (rawType == base || rawType == sub) ? (TypeAdapter<T>) typeAdapter : null;
+ }
+ @Override public String toString() {
+ return "Factory[type=" + base.getName()
+ + "+" + sub.getName() + ",adapter=" + typeAdapter + "]";
+ }
+ };
+ }
+
+ public static <TT> TypeAdapterFactory newTypeHierarchyFactory(
+ final Class<TT> clazz, final TypeAdapter<TT> typeAdapter) {
+ return new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked")
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
+ return clazz.isAssignableFrom(typeToken.getRawType()) ? (TypeAdapter<T>) typeAdapter : null;
+ }
+ @Override public String toString() {
+ return "Factory[typeHierarchy=" + clazz.getName() + ",adapter=" + typeAdapter + "]";
+ }
+ };
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/internal/package-info.java b/gson/src/main/java/com/google/gson/internal/package-info.java
new file mode 100644
index 00000000..b5139b6d
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/internal/package-info.java
@@ -0,0 +1,7 @@
+/**
+ * Do NOT use any class in this package as they are meant for internal use in Gson.
+ * These classes will very likely change incompatibly in future versions. You have been warned.
+ *
+ * @author Inderjeet Singh, Joel Leitch, Jesse Wilson
+ */
+package com.google.gson.internal; \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/package-info.java b/gson/src/main/java/com/google/gson/package-info.java
new file mode 100644
index 00000000..428e280c
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/package-info.java
@@ -0,0 +1,11 @@
+/**
+ * This package provides the {@link com.google.gson.Gson} class to convert Json to Java and
+ * vice-versa.
+ *
+ * <p>The primary class to use is {@link com.google.gson.Gson} which can be constructed with
+ * {@code new Gson()} (using default settings) or by using {@link com.google.gson.GsonBuilder}
+ * (to configure various options such as using versioning and so on).</p>
+ *
+ * @author Inderjeet Singh, Joel Leitch
+ */
+package com.google.gson; \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/reflect/TypeToken.java b/gson/src/main/java/com/google/gson/reflect/TypeToken.java
new file mode 100644
index 00000000..e16e8e6d
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/reflect/TypeToken.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.reflect;
+
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.internal.$Gson$Preconditions;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents a generic type {@code T}. Java doesn't yet provide a way to
+ * represent generic types, so this class does. Forces clients to create a
+ * subclass of this class which enables retrieval the type information even at
+ * runtime.
+ *
+ * <p>For example, to create a type literal for {@code List<String>}, you can
+ * create an empty anonymous inner class:
+ *
+ * <p>
+ * {@code TypeToken<List<String>> list = new TypeToken<List<String>>() {};}
+ *
+ * <p>This syntax cannot be used to create type literals that have wildcard
+ * parameters, such as {@code Class<?>} or {@code List<? extends CharSequence>}.
+ *
+ * @author Bob Lee
+ * @author Sven Mawson
+ * @author Jesse Wilson
+ */
+public class TypeToken<T> {
+ final Class<? super T> rawType;
+ final Type type;
+ final int hashCode;
+
+ /**
+ * Constructs a new type literal. Derives represented class from type
+ * parameter.
+ *
+ * <p>Clients create an empty anonymous subclass. Doing so embeds the type
+ * parameter in the anonymous class's type hierarchy so we can reconstitute it
+ * at runtime despite erasure.
+ */
+ @SuppressWarnings("unchecked")
+ protected TypeToken() {
+ this.type = getSuperclassTypeParameter(getClass());
+ this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
+ this.hashCode = type.hashCode();
+ }
+
+ /**
+ * Unsafe. Constructs a type literal manually.
+ */
+ @SuppressWarnings("unchecked")
+ TypeToken(Type type) {
+ this.type = $Gson$Types.canonicalize($Gson$Preconditions.checkNotNull(type));
+ this.rawType = (Class<? super T>) $Gson$Types.getRawType(this.type);
+ this.hashCode = this.type.hashCode();
+ }
+
+ /**
+ * Returns the type from super class's type parameter in {@link $Gson$Types#canonicalize
+ * canonical form}.
+ */
+ static Type getSuperclassTypeParameter(Class<?> subclass) {
+ Type superclass = subclass.getGenericSuperclass();
+ if (superclass instanceof Class) {
+ throw new RuntimeException("Missing type parameter.");
+ }
+ ParameterizedType parameterized = (ParameterizedType) superclass;
+ return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
+ }
+
+ /**
+ * Returns the raw (non-generic) type for this type.
+ */
+ public final Class<? super T> getRawType() {
+ return rawType;
+ }
+
+ /**
+ * Gets underlying {@code Type} instance.
+ */
+ public final Type getType() {
+ return type;
+ }
+
+ /**
+ * Check if this type is assignable from the given class object.
+ *
+ * @deprecated this implementation may be inconsistent with javac for types
+ * with wildcards.
+ */
+ @Deprecated
+ public boolean isAssignableFrom(Class<?> cls) {
+ return isAssignableFrom((Type) cls);
+ }
+
+ /**
+ * Check if this type is assignable from the given Type.
+ *
+ * @deprecated this implementation may be inconsistent with javac for types
+ * with wildcards.
+ */
+ @Deprecated
+ public boolean isAssignableFrom(Type from) {
+ if (from == null) {
+ return false;
+ }
+
+ if (type.equals(from)) {
+ return true;
+ }
+
+ if (type instanceof Class<?>) {
+ return rawType.isAssignableFrom($Gson$Types.getRawType(from));
+ } else if (type instanceof ParameterizedType) {
+ return isAssignableFrom(from, (ParameterizedType) type,
+ new HashMap<String, Type>());
+ } else if (type instanceof GenericArrayType) {
+ return rawType.isAssignableFrom($Gson$Types.getRawType(from))
+ && isAssignableFrom(from, (GenericArrayType) type);
+ } else {
+ throw buildUnexpectedTypeError(
+ type, Class.class, ParameterizedType.class, GenericArrayType.class);
+ }
+ }
+
+ /**
+ * Check if this type is assignable from the given type token.
+ *
+ * @deprecated this implementation may be inconsistent with javac for types
+ * with wildcards.
+ */
+ @Deprecated
+ public boolean isAssignableFrom(TypeToken<?> token) {
+ return isAssignableFrom(token.getType());
+ }
+
+ /**
+ * Private helper function that performs some assignability checks for
+ * the provided GenericArrayType.
+ */
+ private static boolean isAssignableFrom(Type from, GenericArrayType to) {
+ Type toGenericComponentType = to.getGenericComponentType();
+ if (toGenericComponentType instanceof ParameterizedType) {
+ Type t = from;
+ if (from instanceof GenericArrayType) {
+ t = ((GenericArrayType) from).getGenericComponentType();
+ } else if (from instanceof Class<?>) {
+ Class<?> classType = (Class<?>) from;
+ while (classType.isArray()) {
+ classType = classType.getComponentType();
+ }
+ t = classType;
+ }
+ return isAssignableFrom(t, (ParameterizedType) toGenericComponentType,
+ new HashMap<String, Type>());
+ }
+ // No generic defined on "to"; therefore, return true and let other
+ // checks determine assignability
+ return true;
+ }
+
+ /**
+ * Private recursive helper function to actually do the type-safe checking
+ * of assignability.
+ */
+ private static boolean isAssignableFrom(Type from, ParameterizedType to,
+ Map<String, Type> typeVarMap) {
+
+ if (from == null) {
+ return false;
+ }
+
+ if (to.equals(from)) {
+ return true;
+ }
+
+ // First figure out the class and any type information.
+ Class<?> clazz = $Gson$Types.getRawType(from);
+ ParameterizedType ptype = null;
+ if (from instanceof ParameterizedType) {
+ ptype = (ParameterizedType) from;
+ }
+
+ // Load up parameterized variable info if it was parameterized.
+ if (ptype != null) {
+ Type[] tArgs = ptype.getActualTypeArguments();
+ TypeVariable<?>[] tParams = clazz.getTypeParameters();
+ for (int i = 0; i < tArgs.length; i++) {
+ Type arg = tArgs[i];
+ TypeVariable<?> var = tParams[i];
+ while (arg instanceof TypeVariable<?>) {
+ TypeVariable<?> v = (TypeVariable<?>) arg;
+ arg = typeVarMap.get(v.getName());
+ }
+ typeVarMap.put(var.getName(), arg);
+ }
+
+ // check if they are equivalent under our current mapping.
+ if (typeEquals(ptype, to, typeVarMap)) {
+ return true;
+ }
+ }
+
+ for (Type itype : clazz.getGenericInterfaces()) {
+ if (isAssignableFrom(itype, to, new HashMap<String, Type>(typeVarMap))) {
+ return true;
+ }
+ }
+
+ // Interfaces didn't work, try the superclass.
+ Type sType = clazz.getGenericSuperclass();
+ return isAssignableFrom(sType, to, new HashMap<String, Type>(typeVarMap));
+ }
+
+ /**
+ * Checks if two parameterized types are exactly equal, under the variable
+ * replacement described in the typeVarMap.
+ */
+ private static boolean typeEquals(ParameterizedType from,
+ ParameterizedType to, Map<String, Type> typeVarMap) {
+ if (from.getRawType().equals(to.getRawType())) {
+ Type[] fromArgs = from.getActualTypeArguments();
+ Type[] toArgs = to.getActualTypeArguments();
+ for (int i = 0; i < fromArgs.length; i++) {
+ if (!matches(fromArgs[i], toArgs[i], typeVarMap)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private static AssertionError buildUnexpectedTypeError(
+ Type token, Class<?>... expected) {
+
+ // Build exception message
+ StringBuilder exceptionMessage =
+ new StringBuilder("Unexpected type. Expected one of: ");
+ for (Class<?> clazz : expected) {
+ exceptionMessage.append(clazz.getName()).append(", ");
+ }
+ exceptionMessage.append("but got: ").append(token.getClass().getName())
+ .append(", for type token: ").append(token.toString()).append('.');
+
+ return new AssertionError(exceptionMessage.toString());
+ }
+
+ /**
+ * Checks if two types are the same or are equivalent under a variable mapping
+ * given in the type map that was provided.
+ */
+ private static boolean matches(Type from, Type to, Map<String, Type> typeMap) {
+ return to.equals(from)
+ || (from instanceof TypeVariable
+ && to.equals(typeMap.get(((TypeVariable<?>) from).getName())));
+
+ }
+
+ @Override public final int hashCode() {
+ return this.hashCode;
+ }
+
+ @Override public final boolean equals(Object o) {
+ return o instanceof TypeToken<?>
+ && $Gson$Types.equals(type, ((TypeToken<?>) o).type);
+ }
+
+ @Override public final String toString() {
+ return $Gson$Types.typeToString(type);
+ }
+
+ /**
+ * Gets type literal for the given {@code Type} instance.
+ */
+ public static TypeToken<?> get(Type type) {
+ return new TypeToken<Object>(type);
+ }
+
+ /**
+ * Gets type literal for the given {@code Class} instance.
+ */
+ public static <T> TypeToken<T> get(Class<T> type) {
+ return new TypeToken<T>(type);
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/reflect/package-info.java b/gson/src/main/java/com/google/gson/reflect/package-info.java
new file mode 100644
index 00000000..e666c431
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/reflect/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * This package provides utility classes for finding type information for generic types.
+ *
+ * @author Inderjeet Singh, Joel Leitch
+ */
+package com.google.gson.reflect; \ No newline at end of file
diff --git a/gson/src/main/java/com/google/gson/stream/JsonReader.java b/gson/src/main/java/com/google/gson/stream/JsonReader.java
new file mode 100644
index 00000000..388f30b0
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/stream/JsonReader.java
@@ -0,0 +1,1624 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+import com.google.gson.internal.JsonReaderInternalAccess;
+import com.google.gson.internal.bind.JsonTreeReader;
+import java.io.Closeable;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Reads a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value as a stream of tokens. This stream includes both literal
+ * values (strings, numbers, booleans, and nulls) as well as the begin and
+ * end delimiters of objects and arrays. The tokens are traversed in
+ * depth-first order, the same order that they appear in the JSON document.
+ * Within JSON objects, name/value pairs are represented by a single token.
+ *
+ * <h3>Parsing JSON</h3>
+ * To create a recursive descent parser for your own JSON streams, first create
+ * an entry point method that creates a {@code JsonReader}.
+ *
+ * <p>Next, create handler methods for each structure in your JSON text. You'll
+ * need a method for each object type and for each array type.
+ * <ul>
+ * <li>Within <strong>array handling</strong> methods, first call {@link
+ * #beginArray} to consume the array's opening bracket. Then create a
+ * while loop that accumulates values, terminating when {@link #hasNext}
+ * is false. Finally, read the array's closing bracket by calling {@link
+ * #endArray}.
+ * <li>Within <strong>object handling</strong> methods, first call {@link
+ * #beginObject} to consume the object's opening brace. Then create a
+ * while loop that assigns values to local variables based on their name.
+ * This loop should terminate when {@link #hasNext} is false. Finally,
+ * read the object's closing brace by calling {@link #endObject}.
+ * </ul>
+ * <p>When a nested object or array is encountered, delegate to the
+ * corresponding handler method.
+ *
+ * <p>When an unknown name is encountered, strict parsers should fail with an
+ * exception. Lenient parsers should call {@link #skipValue()} to recursively
+ * skip the value's nested tokens, which may otherwise conflict.
+ *
+ * <p>If a value may be null, you should first check using {@link #peek()}.
+ * Null literals can be consumed using either {@link #nextNull()} or {@link
+ * #skipValue()}.
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to parse a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I read a JSON stream in Java?",
+ * "geo": null,
+ * "user": {
+ * "name": "json_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@json_newb just use JsonReader!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code implements the parser for the above structure: <pre> {@code
+ *
+ * public List<Message> readJsonStream(InputStream in) throws IOException {
+ * JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ * try {
+ * return readMessagesArray(reader);
+ * } finally {
+ * reader.close();
+ * }
+ * }
+ *
+ * public List<Message> readMessagesArray(JsonReader reader) throws IOException {
+ * List<Message> messages = new ArrayList<Message>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * messages.add(readMessage(reader));
+ * }
+ * reader.endArray();
+ * return messages;
+ * }
+ *
+ * public Message readMessage(JsonReader reader) throws IOException {
+ * long id = -1;
+ * String text = null;
+ * User user = null;
+ * List<Double> geo = null;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("id")) {
+ * id = reader.nextLong();
+ * } else if (name.equals("text")) {
+ * text = reader.nextString();
+ * } else if (name.equals("geo") && reader.peek() != JsonToken.NULL) {
+ * geo = readDoublesArray(reader);
+ * } else if (name.equals("user")) {
+ * user = readUser(reader);
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new Message(id, text, user, geo);
+ * }
+ *
+ * public List<Double> readDoublesArray(JsonReader reader) throws IOException {
+ * List<Double> doubles = new ArrayList<Double>();
+ *
+ * reader.beginArray();
+ * while (reader.hasNext()) {
+ * doubles.add(reader.nextDouble());
+ * }
+ * reader.endArray();
+ * return doubles;
+ * }
+ *
+ * public User readUser(JsonReader reader) throws IOException {
+ * String username = null;
+ * int followersCount = -1;
+ *
+ * reader.beginObject();
+ * while (reader.hasNext()) {
+ * String name = reader.nextName();
+ * if (name.equals("name")) {
+ * username = reader.nextString();
+ * } else if (name.equals("followers_count")) {
+ * followersCount = reader.nextInt();
+ * } else {
+ * reader.skipValue();
+ * }
+ * }
+ * reader.endObject();
+ * return new User(username, followersCount);
+ * }}</pre>
+ *
+ * <h3>Number Handling</h3>
+ * This reader permits numeric values to be read as strings and string values to
+ * be read as numbers. For example, both elements of the JSON array {@code
+ * [1, "1"]} may be read using either {@link #nextInt} or {@link #nextString}.
+ * This behavior is intended to prevent lossy numeric conversions: double is
+ * JavaScript's only numeric type and very large values like {@code
+ * 9007199254740993} cannot be represented exactly on that platform. To minimize
+ * precision loss, extremely large values should be written and read as strings
+ * in JSON.
+ *
+ * <a name="nonexecuteprefix"/><h3>Non-Execute Prefix</h3>
+ * Web servers that serve private data using JSON may be vulnerable to <a
+ * href="http://en.wikipedia.org/wiki/JSON#Cross-site_request_forgery">Cross-site
+ * request forgery</a> attacks. In such an attack, a malicious site gains access
+ * to a private JSON file by executing it with an HTML {@code <script>} tag.
+ *
+ * <p>Prefixing JSON files with <code>")]}'\n"</code> makes them non-executable
+ * by {@code <script>} tags, disarming the attack. Since the prefix is malformed
+ * JSON, strict parsing fails when it is encountered. This class permits the
+ * non-execute prefix when {@link #setLenient(boolean) lenient parsing} is
+ * enabled.
+ *
+ * <p>Each {@code JsonReader} may be used to read a single JSON stream. Instances
+ * of this class are not thread safe.
+ *
+ * @author Jesse Wilson
+ * @since 1.6
+ */
+public class JsonReader implements Closeable {
+ /** The only non-execute prefix this parser permits */
+ private static final char[] NON_EXECUTE_PREFIX = ")]}'\n".toCharArray();
+ private static final long MIN_INCOMPLETE_INTEGER = Long.MIN_VALUE / 10;
+
+ private static final int PEEKED_NONE = 0;
+ private static final int PEEKED_BEGIN_OBJECT = 1;
+ private static final int PEEKED_END_OBJECT = 2;
+ private static final int PEEKED_BEGIN_ARRAY = 3;
+ private static final int PEEKED_END_ARRAY = 4;
+ private static final int PEEKED_TRUE = 5;
+ private static final int PEEKED_FALSE = 6;
+ private static final int PEEKED_NULL = 7;
+ private static final int PEEKED_SINGLE_QUOTED = 8;
+ private static final int PEEKED_DOUBLE_QUOTED = 9;
+ private static final int PEEKED_UNQUOTED = 10;
+ /** When this is returned, the string value is stored in peekedString. */
+ private static final int PEEKED_BUFFERED = 11;
+ private static final int PEEKED_SINGLE_QUOTED_NAME = 12;
+ private static final int PEEKED_DOUBLE_QUOTED_NAME = 13;
+ private static final int PEEKED_UNQUOTED_NAME = 14;
+ /** When this is returned, the integer value is stored in peekedLong. */
+ private static final int PEEKED_LONG = 15;
+ private static final int PEEKED_NUMBER = 16;
+ private static final int PEEKED_EOF = 17;
+
+ /* State machine when parsing numbers */
+ private static final int NUMBER_CHAR_NONE = 0;
+ private static final int NUMBER_CHAR_SIGN = 1;
+ private static final int NUMBER_CHAR_DIGIT = 2;
+ private static final int NUMBER_CHAR_DECIMAL = 3;
+ private static final int NUMBER_CHAR_FRACTION_DIGIT = 4;
+ private static final int NUMBER_CHAR_EXP_E = 5;
+ private static final int NUMBER_CHAR_EXP_SIGN = 6;
+ private static final int NUMBER_CHAR_EXP_DIGIT = 7;
+
+ /** The input JSON. */
+ private final Reader in;
+
+ /** True to accept non-spec compliant JSON */
+ private boolean lenient = false;
+
+ /**
+ * Use a manual buffer to easily read and unread upcoming characters, and
+ * also so we can create strings without an intermediate StringBuilder.
+ * We decode literals directly out of this buffer, so it must be at least as
+ * long as the longest token that can be reported as a number.
+ */
+ private final char[] buffer = new char[1024];
+ private int pos = 0;
+ private int limit = 0;
+
+ private int lineNumber = 0;
+ private int lineStart = 0;
+
+ private int peeked = PEEKED_NONE;
+
+ /**
+ * A peeked value that was composed entirely of digits with an optional
+ * leading dash. Positive values may not have a leading 0.
+ */
+ private long peekedLong;
+
+ /**
+ * The number of characters in a peeked number literal. Increment 'pos' by
+ * this after reading a number.
+ */
+ private int peekedNumberLength;
+
+ /**
+ * A peeked string that should be parsed on the next double, long or string.
+ * This is populated before a numeric value is parsed and used if that parsing
+ * fails.
+ */
+ private String peekedString;
+
+ /*
+ * The nesting stack. Using a manual array rather than an ArrayList saves 20%.
+ */
+ private int[] stack = new int[32];
+ private int stackSize = 0;
+ {
+ stack[stackSize++] = JsonScope.EMPTY_DOCUMENT;
+ }
+
+ /*
+ * The path members. It corresponds directly to stack: At indices where the
+ * stack contains an object (EMPTY_OBJECT, DANGLING_NAME or NONEMPTY_OBJECT),
+ * pathNames contains the name at this scope. Where it contains an array
+ * (EMPTY_ARRAY, NONEMPTY_ARRAY) pathIndices contains the current index in
+ * that array. Otherwise the value is undefined, and we take advantage of that
+ * by incrementing pathIndices when doing so isn't useful.
+ */
+ private String[] pathNames = new String[32];
+ private int[] pathIndices = new int[32];
+
+ /**
+ * Creates a new instance that reads a JSON-encoded stream from {@code in}.
+ */
+ public JsonReader(Reader in) {
+ if (in == null) {
+ throw new NullPointerException("in == null");
+ }
+ this.in = in;
+ }
+
+ /**
+ * Configure this parser to be be liberal in what it accepts. By default,
+ * this parser is strict and only accepts JSON as specified by <a
+ * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the
+ * parser to lenient causes it to ignore the following syntax errors:
+ *
+ * <ul>
+ * <li>Streams that start with the <a href="#nonexecuteprefix">non-execute
+ * prefix</a>, <code>")]}'\n"</code>.
+ * <li>Streams that include multiple top-level values. With strict parsing,
+ * each stream must contain exactly one top-level value.
+ * <li>Top-level values of any type. With strict parsing, the top-level
+ * value must be an object or an array.
+ * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
+ * Double#isInfinite() infinities}.
+ * <li>End of line comments starting with {@code //} or {@code #} and
+ * ending with a newline character.
+ * <li>C-style comments starting with {@code /*} and ending with
+ * {@code *}{@code /}. Such comments may not be nested.
+ * <li>Names that are unquoted or {@code 'single quoted'}.
+ * <li>Strings that are unquoted or {@code 'single quoted'}.
+ * <li>Array elements separated by {@code ;} instead of {@code ,}.
+ * <li>Unnecessary array separators. These are interpreted as if null
+ * was the omitted value.
+ * <li>Names and values separated by {@code =} or {@code =>} instead of
+ * {@code :}.
+ * <li>Name/value pairs separated by {@code ;} instead of {@code ,}.
+ * </ul>
+ */
+ public final void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ }
+
+ /**
+ * Returns true if this parser is liberal in what it accepts.
+ */
+ public final boolean isLenient() {
+ return lenient;
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new array.
+ */
+ public void beginArray() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ if (p == PEEKED_BEGIN_ARRAY) {
+ push(JsonScope.EMPTY_ARRAY);
+ pathIndices[stackSize - 1] = 0;
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected BEGIN_ARRAY but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current array.
+ */
+ public void endArray() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ if (p == PEEKED_END_ARRAY) {
+ stackSize--;
+ pathIndices[stackSize - 1]++;
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected END_ARRAY but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * beginning of a new object.
+ */
+ public void beginObject() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ if (p == PEEKED_BEGIN_OBJECT) {
+ push(JsonScope.EMPTY_OBJECT);
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected BEGIN_OBJECT but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is the
+ * end of the current object.
+ */
+ public void endObject() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ if (p == PEEKED_END_OBJECT) {
+ stackSize--;
+ pathNames[stackSize] = null; // Free the last path name so that it can be garbage collected!
+ pathIndices[stackSize - 1]++;
+ peeked = PEEKED_NONE;
+ } else {
+ throw new IllegalStateException("Expected END_OBJECT but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ }
+
+ /**
+ * Returns true if the current array or object has another element.
+ */
+ public boolean hasNext() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ return p != PEEKED_END_OBJECT && p != PEEKED_END_ARRAY;
+ }
+
+ /**
+ * Returns the type of the next token without consuming it.
+ */
+ public JsonToken peek() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+
+ switch (p) {
+ case PEEKED_BEGIN_OBJECT:
+ return JsonToken.BEGIN_OBJECT;
+ case PEEKED_END_OBJECT:
+ return JsonToken.END_OBJECT;
+ case PEEKED_BEGIN_ARRAY:
+ return JsonToken.BEGIN_ARRAY;
+ case PEEKED_END_ARRAY:
+ return JsonToken.END_ARRAY;
+ case PEEKED_SINGLE_QUOTED_NAME:
+ case PEEKED_DOUBLE_QUOTED_NAME:
+ case PEEKED_UNQUOTED_NAME:
+ return JsonToken.NAME;
+ case PEEKED_TRUE:
+ case PEEKED_FALSE:
+ return JsonToken.BOOLEAN;
+ case PEEKED_NULL:
+ return JsonToken.NULL;
+ case PEEKED_SINGLE_QUOTED:
+ case PEEKED_DOUBLE_QUOTED:
+ case PEEKED_UNQUOTED:
+ case PEEKED_BUFFERED:
+ return JsonToken.STRING;
+ case PEEKED_LONG:
+ case PEEKED_NUMBER:
+ return JsonToken.NUMBER;
+ case PEEKED_EOF:
+ return JsonToken.END_DOCUMENT;
+ default:
+ throw new AssertionError();
+ }
+ }
+
+ private int doPeek() throws IOException {
+ int peekStack = stack[stackSize - 1];
+ if (peekStack == JsonScope.EMPTY_ARRAY) {
+ stack[stackSize - 1] = JsonScope.NONEMPTY_ARRAY;
+ } else if (peekStack == JsonScope.NONEMPTY_ARRAY) {
+ // Look for a comma before the next element.
+ int c = nextNonWhitespace(true);
+ switch (c) {
+ case ']':
+ return peeked = PEEKED_END_ARRAY;
+ case ';':
+ checkLenient(); // fall-through
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated array");
+ }
+ } else if (peekStack == JsonScope.EMPTY_OBJECT || peekStack == JsonScope.NONEMPTY_OBJECT) {
+ stack[stackSize - 1] = JsonScope.DANGLING_NAME;
+ // Look for a comma before the next element.
+ if (peekStack == JsonScope.NONEMPTY_OBJECT) {
+ int c = nextNonWhitespace(true);
+ switch (c) {
+ case '}':
+ return peeked = PEEKED_END_OBJECT;
+ case ';':
+ checkLenient(); // fall-through
+ case ',':
+ break;
+ default:
+ throw syntaxError("Unterminated object");
+ }
+ }
+ int c = nextNonWhitespace(true);
+ switch (c) {
+ case '"':
+ return peeked = PEEKED_DOUBLE_QUOTED_NAME;
+ case '\'':
+ checkLenient();
+ return peeked = PEEKED_SINGLE_QUOTED_NAME;
+ case '}':
+ if (peekStack != JsonScope.NONEMPTY_OBJECT) {
+ return peeked = PEEKED_END_OBJECT;
+ } else {
+ throw syntaxError("Expected name");
+ }
+ default:
+ checkLenient();
+ pos--; // Don't consume the first character in an unquoted string.
+ if (isLiteral((char) c)) {
+ return peeked = PEEKED_UNQUOTED_NAME;
+ } else {
+ throw syntaxError("Expected name");
+ }
+ }
+ } else if (peekStack == JsonScope.DANGLING_NAME) {
+ stack[stackSize - 1] = JsonScope.NONEMPTY_OBJECT;
+ // Look for a colon before the value.
+ int c = nextNonWhitespace(true);
+ switch (c) {
+ case ':':
+ break;
+ case '=':
+ checkLenient();
+ if ((pos < limit || fillBuffer(1)) && buffer[pos] == '>') {
+ pos++;
+ }
+ break;
+ default:
+ throw syntaxError("Expected ':'");
+ }
+ } else if (peekStack == JsonScope.EMPTY_DOCUMENT) {
+ if (lenient) {
+ consumeNonExecutePrefix();
+ }
+ stack[stackSize - 1] = JsonScope.NONEMPTY_DOCUMENT;
+ } else if (peekStack == JsonScope.NONEMPTY_DOCUMENT) {
+ int c = nextNonWhitespace(false);
+ if (c == -1) {
+ return peeked = PEEKED_EOF;
+ } else {
+ checkLenient();
+ pos--;
+ }
+ } else if (peekStack == JsonScope.CLOSED) {
+ throw new IllegalStateException("JsonReader is closed");
+ }
+
+ int c = nextNonWhitespace(true);
+ switch (c) {
+ case ']':
+ if (peekStack == JsonScope.EMPTY_ARRAY) {
+ return peeked = PEEKED_END_ARRAY;
+ }
+ // fall-through to handle ",]"
+ case ';':
+ case ',':
+ // In lenient mode, a 0-length literal in an array means 'null'.
+ if (peekStack == JsonScope.EMPTY_ARRAY || peekStack == JsonScope.NONEMPTY_ARRAY) {
+ checkLenient();
+ pos--;
+ return peeked = PEEKED_NULL;
+ } else {
+ throw syntaxError("Unexpected value");
+ }
+ case '\'':
+ checkLenient();
+ return peeked = PEEKED_SINGLE_QUOTED;
+ case '"':
+ if (stackSize == 1) {
+ checkLenient();
+ }
+ return peeked = PEEKED_DOUBLE_QUOTED;
+ case '[':
+ return peeked = PEEKED_BEGIN_ARRAY;
+ case '{':
+ return peeked = PEEKED_BEGIN_OBJECT;
+ default:
+ pos--; // Don't consume the first character in a literal value.
+ }
+
+ if (stackSize == 1) {
+ checkLenient(); // Top-level value isn't an array or an object.
+ }
+
+ int result = peekKeyword();
+ if (result != PEEKED_NONE) {
+ return result;
+ }
+
+ result = peekNumber();
+ if (result != PEEKED_NONE) {
+ return result;
+ }
+
+ if (!isLiteral(buffer[pos])) {
+ throw syntaxError("Expected value");
+ }
+
+ checkLenient();
+ return peeked = PEEKED_UNQUOTED;
+ }
+
+ private int peekKeyword() throws IOException {
+ // Figure out which keyword we're matching against by its first character.
+ char c = buffer[pos];
+ String keyword;
+ String keywordUpper;
+ int peeking;
+ if (c == 't' || c == 'T') {
+ keyword = "true";
+ keywordUpper = "TRUE";
+ peeking = PEEKED_TRUE;
+ } else if (c == 'f' || c == 'F') {
+ keyword = "false";
+ keywordUpper = "FALSE";
+ peeking = PEEKED_FALSE;
+ } else if (c == 'n' || c == 'N') {
+ keyword = "null";
+ keywordUpper = "NULL";
+ peeking = PEEKED_NULL;
+ } else {
+ return PEEKED_NONE;
+ }
+
+ // Confirm that chars [1..length) match the keyword.
+ int length = keyword.length();
+ for (int i = 1; i < length; i++) {
+ if (pos + i >= limit && !fillBuffer(i + 1)) {
+ return PEEKED_NONE;
+ }
+ c = buffer[pos + i];
+ if (c != keyword.charAt(i) && c != keywordUpper.charAt(i)) {
+ return PEEKED_NONE;
+ }
+ }
+
+ if ((pos + length < limit || fillBuffer(length + 1))
+ && isLiteral(buffer[pos + length])) {
+ return PEEKED_NONE; // Don't match trues, falsey or nullsoft!
+ }
+
+ // We've found the keyword followed either by EOF or by a non-literal character.
+ pos += length;
+ return peeked = peeking;
+ }
+
+ private int peekNumber() throws IOException {
+ // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
+ char[] buffer = this.buffer;
+ int p = pos;
+ int l = limit;
+
+ long value = 0; // Negative to accommodate Long.MIN_VALUE more easily.
+ boolean negative = false;
+ boolean fitsInLong = true;
+ int last = NUMBER_CHAR_NONE;
+
+ int i = 0;
+
+ charactersOfNumber:
+ for (; true; i++) {
+ if (p + i == l) {
+ if (i == buffer.length) {
+ // Though this looks like a well-formed number, it's too long to continue reading. Give up
+ // and let the application handle this as an unquoted literal.
+ return PEEKED_NONE;
+ }
+ if (!fillBuffer(i + 1)) {
+ break;
+ }
+ p = pos;
+ l = limit;
+ }
+
+ char c = buffer[p + i];
+ switch (c) {
+ case '-':
+ if (last == NUMBER_CHAR_NONE) {
+ negative = true;
+ last = NUMBER_CHAR_SIGN;
+ continue;
+ } else if (last == NUMBER_CHAR_EXP_E) {
+ last = NUMBER_CHAR_EXP_SIGN;
+ continue;
+ }
+ return PEEKED_NONE;
+
+ case '+':
+ if (last == NUMBER_CHAR_EXP_E) {
+ last = NUMBER_CHAR_EXP_SIGN;
+ continue;
+ }
+ return PEEKED_NONE;
+
+ case 'e':
+ case 'E':
+ if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT) {
+ last = NUMBER_CHAR_EXP_E;
+ continue;
+ }
+ return PEEKED_NONE;
+
+ case '.':
+ if (last == NUMBER_CHAR_DIGIT) {
+ last = NUMBER_CHAR_DECIMAL;
+ continue;
+ }
+ return PEEKED_NONE;
+
+ default:
+ if (c < '0' || c > '9') {
+ if (!isLiteral(c)) {
+ break charactersOfNumber;
+ }
+ return PEEKED_NONE;
+ }
+ if (last == NUMBER_CHAR_SIGN || last == NUMBER_CHAR_NONE) {
+ value = -(c - '0');
+ last = NUMBER_CHAR_DIGIT;
+ } else if (last == NUMBER_CHAR_DIGIT) {
+ if (value == 0) {
+ return PEEKED_NONE; // Leading '0' prefix is not allowed (since it could be octal).
+ }
+ long newValue = value * 10 - (c - '0');
+ fitsInLong &= value > MIN_INCOMPLETE_INTEGER
+ || (value == MIN_INCOMPLETE_INTEGER && newValue < value);
+ value = newValue;
+ } else if (last == NUMBER_CHAR_DECIMAL) {
+ last = NUMBER_CHAR_FRACTION_DIGIT;
+ } else if (last == NUMBER_CHAR_EXP_E || last == NUMBER_CHAR_EXP_SIGN) {
+ last = NUMBER_CHAR_EXP_DIGIT;
+ }
+ }
+ }
+
+ // We've read a complete number. Decide if it's a PEEKED_LONG or a PEEKED_NUMBER.
+ if (last == NUMBER_CHAR_DIGIT && fitsInLong && (value != Long.MIN_VALUE || negative)) {
+ peekedLong = negative ? value : -value;
+ pos += i;
+ return peeked = PEEKED_LONG;
+ } else if (last == NUMBER_CHAR_DIGIT || last == NUMBER_CHAR_FRACTION_DIGIT
+ || last == NUMBER_CHAR_EXP_DIGIT) {
+ peekedNumberLength = i;
+ return peeked = PEEKED_NUMBER;
+ } else {
+ return PEEKED_NONE;
+ }
+ }
+
+ private boolean isLiteral(char c) throws IOException {
+ switch (c) {
+ case '/':
+ case '\\':
+ case ';':
+ case '#':
+ case '=':
+ checkLenient(); // fall-through
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case ':':
+ case ',':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ /**
+ * Returns the next token, a {@link com.google.gson.stream.JsonToken#NAME property name}, and
+ * consumes it.
+ *
+ * @throws java.io.IOException if the next token in the stream is not a property
+ * name.
+ */
+ public String nextName() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ String result;
+ if (p == PEEKED_UNQUOTED_NAME) {
+ result = nextUnquotedValue();
+ } else if (p == PEEKED_SINGLE_QUOTED_NAME) {
+ result = nextQuotedValue('\'');
+ } else if (p == PEEKED_DOUBLE_QUOTED_NAME) {
+ result = nextQuotedValue('"');
+ } else {
+ throw new IllegalStateException("Expected a name but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ peeked = PEEKED_NONE;
+ pathNames[stackSize - 1] = result;
+ return result;
+ }
+
+ /**
+ * Returns the {@link com.google.gson.stream.JsonToken#STRING string} value of the next token,
+ * consuming it. If the next token is a number, this method will return its
+ * string form.
+ *
+ * @throws IllegalStateException if the next token is not a string or if
+ * this reader is closed.
+ */
+ public String nextString() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ String result;
+ if (p == PEEKED_UNQUOTED) {
+ result = nextUnquotedValue();
+ } else if (p == PEEKED_SINGLE_QUOTED) {
+ result = nextQuotedValue('\'');
+ } else if (p == PEEKED_DOUBLE_QUOTED) {
+ result = nextQuotedValue('"');
+ } else if (p == PEEKED_BUFFERED) {
+ result = peekedString;
+ peekedString = null;
+ } else if (p == PEEKED_LONG) {
+ result = Long.toString(peekedLong);
+ } else if (p == PEEKED_NUMBER) {
+ result = new String(buffer, pos, peekedNumberLength);
+ pos += peekedNumberLength;
+ } else {
+ throw new IllegalStateException("Expected a string but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ }
+
+ /**
+ * Returns the {@link com.google.gson.stream.JsonToken#BOOLEAN boolean} value of the next token,
+ * consuming it.
+ *
+ * @throws IllegalStateException if the next token is not a boolean or if
+ * this reader is closed.
+ */
+ public boolean nextBoolean() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ if (p == PEEKED_TRUE) {
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return true;
+ } else if (p == PEEKED_FALSE) {
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return false;
+ }
+ throw new IllegalStateException("Expected a boolean but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+
+ /**
+ * Consumes the next token from the JSON stream and asserts that it is a
+ * literal null.
+ *
+ * @throws IllegalStateException if the next token is not null or if this
+ * reader is closed.
+ */
+ public void nextNull() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+ if (p == PEEKED_NULL) {
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ } else {
+ throw new IllegalStateException("Expected null but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ }
+
+ /**
+ * Returns the {@link com.google.gson.stream.JsonToken#NUMBER double} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a double using {@link Double#parseDouble(String)}.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a double, or is non-finite.
+ */
+ public double nextDouble() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+
+ if (p == PEEKED_LONG) {
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return (double) peekedLong;
+ }
+
+ if (p == PEEKED_NUMBER) {
+ peekedString = new String(buffer, pos, peekedNumberLength);
+ pos += peekedNumberLength;
+ } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
+ peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
+ } else if (p == PEEKED_UNQUOTED) {
+ peekedString = nextUnquotedValue();
+ } else if (p != PEEKED_BUFFERED) {
+ throw new IllegalStateException("Expected a double but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+
+ peeked = PEEKED_BUFFERED;
+ double result = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
+ if (!lenient && (Double.isNaN(result) || Double.isInfinite(result))) {
+ throw new MalformedJsonException("JSON forbids NaN and infinities: " + result
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ peekedString = null;
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ }
+
+ /**
+ * Returns the {@link com.google.gson.stream.JsonToken#NUMBER long} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as a long. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code long}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as a long.
+ */
+ public long nextLong() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+
+ if (p == PEEKED_LONG) {
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return peekedLong;
+ }
+
+ if (p == PEEKED_NUMBER) {
+ peekedString = new String(buffer, pos, peekedNumberLength);
+ pos += peekedNumberLength;
+ } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
+ peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
+ try {
+ long result = Long.parseLong(peekedString);
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ } catch (NumberFormatException ignored) {
+ // Fall back to parse as a double below.
+ }
+ } else {
+ throw new IllegalStateException("Expected a long but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+
+ peeked = PEEKED_BUFFERED;
+ double asDouble = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
+ long result = (long) asDouble;
+ if (result != asDouble) { // Make sure no precision was lost casting to 'long'.
+ throw new NumberFormatException("Expected a long but was " + peekedString
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ peekedString = null;
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ }
+
+ /**
+ * Returns the string up to but not including {@code quote}, unescaping any
+ * character escape sequences encountered along the way. The opening quote
+ * should have already been read. This consumes the closing quote, but does
+ * not include it in the returned string.
+ *
+ * @param quote either ' or ".
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private String nextQuotedValue(char quote) throws IOException {
+ // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
+ char[] buffer = this.buffer;
+ StringBuilder builder = new StringBuilder();
+ while (true) {
+ int p = pos;
+ int l = limit;
+ /* the index of the first character not yet appended to the builder. */
+ int start = p;
+ while (p < l) {
+ int c = buffer[p++];
+
+ if (c == quote) {
+ pos = p;
+ builder.append(buffer, start, p - start - 1);
+ return builder.toString();
+ } else if (c == '\\') {
+ pos = p;
+ builder.append(buffer, start, p - start - 1);
+ builder.append(readEscapeCharacter());
+ p = pos;
+ l = limit;
+ start = p;
+ } else if (c == '\n') {
+ lineNumber++;
+ lineStart = p;
+ }
+ }
+
+ builder.append(buffer, start, p - start);
+ pos = p;
+ if (!fillBuffer(1)) {
+ throw syntaxError("Unterminated string");
+ }
+ }
+ }
+
+ /**
+ * Returns an unquoted value as a string.
+ */
+ @SuppressWarnings("fallthrough")
+ private String nextUnquotedValue() throws IOException {
+ StringBuilder builder = null;
+ int i = 0;
+
+ findNonLiteralCharacter:
+ while (true) {
+ for (; pos + i < limit; i++) {
+ switch (buffer[pos + i]) {
+ case '/':
+ case '\\':
+ case ';':
+ case '#':
+ case '=':
+ checkLenient(); // fall-through
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case ':':
+ case ',':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ break findNonLiteralCharacter;
+ }
+ }
+
+ // Attempt to load the entire literal into the buffer at once.
+ if (i < buffer.length) {
+ if (fillBuffer(i + 1)) {
+ continue;
+ } else {
+ break;
+ }
+ }
+
+ // use a StringBuilder when the value is too long. This is too long to be a number!
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(buffer, pos, i);
+ pos += i;
+ i = 0;
+ if (!fillBuffer(1)) {
+ break;
+ }
+ }
+
+ String result;
+ if (builder == null) {
+ result = new String(buffer, pos, i);
+ } else {
+ builder.append(buffer, pos, i);
+ result = builder.toString();
+ }
+ pos += i;
+ return result;
+ }
+
+ private void skipQuotedValue(char quote) throws IOException {
+ // Like nextNonWhitespace, this uses locals 'p' and 'l' to save inner-loop field access.
+ char[] buffer = this.buffer;
+ do {
+ int p = pos;
+ int l = limit;
+ /* the index of the first character not yet appended to the builder. */
+ while (p < l) {
+ int c = buffer[p++];
+ if (c == quote) {
+ pos = p;
+ return;
+ } else if (c == '\\') {
+ pos = p;
+ readEscapeCharacter();
+ p = pos;
+ l = limit;
+ } else if (c == '\n') {
+ lineNumber++;
+ lineStart = p;
+ }
+ }
+ pos = p;
+ } while (fillBuffer(1));
+ throw syntaxError("Unterminated string");
+ }
+
+ private void skipUnquotedValue() throws IOException {
+ do {
+ int i = 0;
+ for (; pos + i < limit; i++) {
+ switch (buffer[pos + i]) {
+ case '/':
+ case '\\':
+ case ';':
+ case '#':
+ case '=':
+ checkLenient(); // fall-through
+ case '{':
+ case '}':
+ case '[':
+ case ']':
+ case ':':
+ case ',':
+ case ' ':
+ case '\t':
+ case '\f':
+ case '\r':
+ case '\n':
+ pos += i;
+ return;
+ }
+ }
+ pos += i;
+ } while (fillBuffer(1));
+ }
+
+ /**
+ * Returns the {@link com.google.gson.stream.JsonToken#NUMBER int} value of the next token,
+ * consuming it. If the next token is a string, this method will attempt to
+ * parse it as an int. If the next token's numeric value cannot be exactly
+ * represented by a Java {@code int}, this method throws.
+ *
+ * @throws IllegalStateException if the next token is not a literal value.
+ * @throws NumberFormatException if the next literal value cannot be parsed
+ * as a number, or exactly represented as an int.
+ */
+ public int nextInt() throws IOException {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+
+ int result;
+ if (p == PEEKED_LONG) {
+ result = (int) peekedLong;
+ if (peekedLong != result) { // Make sure no precision was lost casting to 'int'.
+ throw new NumberFormatException("Expected an int but was " + peekedLong
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ }
+
+ if (p == PEEKED_NUMBER) {
+ peekedString = new String(buffer, pos, peekedNumberLength);
+ pos += peekedNumberLength;
+ } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_DOUBLE_QUOTED) {
+ peekedString = nextQuotedValue(p == PEEKED_SINGLE_QUOTED ? '\'' : '"');
+ try {
+ result = Integer.parseInt(peekedString);
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ } catch (NumberFormatException ignored) {
+ // Fall back to parse as a double below.
+ }
+ } else {
+ throw new IllegalStateException("Expected an int but was " + peek()
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+
+ peeked = PEEKED_BUFFERED;
+ double asDouble = Double.parseDouble(peekedString); // don't catch this NumberFormatException.
+ result = (int) asDouble;
+ if (result != asDouble) { // Make sure no precision was lost casting to 'int'.
+ throw new NumberFormatException("Expected an int but was " + peekedString
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+ peekedString = null;
+ peeked = PEEKED_NONE;
+ pathIndices[stackSize - 1]++;
+ return result;
+ }
+
+ /**
+ * Closes this JSON reader and the underlying {@link java.io.Reader}.
+ */
+ public void close() throws IOException {
+ peeked = PEEKED_NONE;
+ stack[0] = JsonScope.CLOSED;
+ stackSize = 1;
+ in.close();
+ }
+
+ /**
+ * Skips the next value recursively. If it is an object or array, all nested
+ * elements are skipped. This method is intended for use when the JSON token
+ * stream contains unrecognized or unhandled values.
+ */
+ public void skipValue() throws IOException {
+ int count = 0;
+ do {
+ int p = peeked;
+ if (p == PEEKED_NONE) {
+ p = doPeek();
+ }
+
+ if (p == PEEKED_BEGIN_ARRAY) {
+ push(JsonScope.EMPTY_ARRAY);
+ count++;
+ } else if (p == PEEKED_BEGIN_OBJECT) {
+ push(JsonScope.EMPTY_OBJECT);
+ count++;
+ } else if (p == PEEKED_END_ARRAY) {
+ stackSize--;
+ count--;
+ } else if (p == PEEKED_END_OBJECT) {
+ stackSize--;
+ count--;
+ } else if (p == PEEKED_UNQUOTED_NAME || p == PEEKED_UNQUOTED) {
+ skipUnquotedValue();
+ } else if (p == PEEKED_SINGLE_QUOTED || p == PEEKED_SINGLE_QUOTED_NAME) {
+ skipQuotedValue('\'');
+ } else if (p == PEEKED_DOUBLE_QUOTED || p == PEEKED_DOUBLE_QUOTED_NAME) {
+ skipQuotedValue('"');
+ } else if (p == PEEKED_NUMBER) {
+ pos += peekedNumberLength;
+ }
+ peeked = PEEKED_NONE;
+ } while (count != 0);
+
+ pathIndices[stackSize - 1]++;
+ pathNames[stackSize - 1] = "null";
+ }
+
+ private void push(int newTop) {
+ if (stackSize == stack.length) {
+ int[] newStack = new int[stackSize * 2];
+ int[] newPathIndices = new int[stackSize * 2];
+ String[] newPathNames = new String[stackSize * 2];
+ System.arraycopy(stack, 0, newStack, 0, stackSize);
+ System.arraycopy(pathIndices, 0, newPathIndices, 0, stackSize);
+ System.arraycopy(pathNames, 0, newPathNames, 0, stackSize);
+ stack = newStack;
+ pathIndices = newPathIndices;
+ pathNames = newPathNames;
+ }
+ stack[stackSize++] = newTop;
+ }
+
+ /**
+ * Returns true once {@code limit - pos >= minimum}. If the data is
+ * exhausted before that many characters are available, this returns
+ * false.
+ */
+ private boolean fillBuffer(int minimum) throws IOException {
+ char[] buffer = this.buffer;
+ lineStart -= pos;
+ if (limit != pos) {
+ limit -= pos;
+ System.arraycopy(buffer, pos, buffer, 0, limit);
+ } else {
+ limit = 0;
+ }
+
+ pos = 0;
+ int total;
+ while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
+ limit += total;
+
+ // if this is the first read, consume an optional byte order mark (BOM) if it exists
+ if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\ufeff') {
+ pos++;
+ lineStart++;
+ minimum++;
+ }
+
+ if (limit >= minimum) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private int getLineNumber() {
+ return lineNumber + 1;
+ }
+
+ private int getColumnNumber() {
+ return pos - lineStart + 1;
+ }
+
+ /**
+ * Returns the next character in the stream that is neither whitespace nor a
+ * part of a comment. When this returns, the returned character is always at
+ * {@code buffer[pos-1]}; this means the caller can always push back the
+ * returned character by decrementing {@code pos}.
+ */
+ private int nextNonWhitespace(boolean throwOnEof) throws IOException {
+ /*
+ * This code uses ugly local variables 'p' and 'l' representing the 'pos'
+ * and 'limit' fields respectively. Using locals rather than fields saves
+ * a few field reads for each whitespace character in a pretty-printed
+ * document, resulting in a 5% speedup. We need to flush 'p' to its field
+ * before any (potentially indirect) call to fillBuffer() and reread both
+ * 'p' and 'l' after any (potentially indirect) call to the same method.
+ */
+ char[] buffer = this.buffer;
+ int p = pos;
+ int l = limit;
+ while (true) {
+ if (p == l) {
+ pos = p;
+ if (!fillBuffer(1)) {
+ break;
+ }
+ p = pos;
+ l = limit;
+ }
+
+ int c = buffer[p++];
+ if (c == '\n') {
+ lineNumber++;
+ lineStart = p;
+ continue;
+ } else if (c == ' ' || c == '\r' || c == '\t') {
+ continue;
+ }
+
+ if (c == '/') {
+ pos = p;
+ if (p == l) {
+ pos--; // push back '/' so it's still in the buffer when this method returns
+ boolean charsLoaded = fillBuffer(2);
+ pos++; // consume the '/' again
+ if (!charsLoaded) {
+ return c;
+ }
+ }
+
+ checkLenient();
+ char peek = buffer[pos];
+ switch (peek) {
+ case '*':
+ // skip a /* c-style comment */
+ pos++;
+ if (!skipTo("*/")) {
+ throw syntaxError("Unterminated comment");
+ }
+ p = pos + 2;
+ l = limit;
+ continue;
+
+ case '/':
+ // skip a // end-of-line comment
+ pos++;
+ skipToEndOfLine();
+ p = pos;
+ l = limit;
+ continue;
+
+ default:
+ return c;
+ }
+ } else if (c == '#') {
+ pos = p;
+ /*
+ * Skip a # hash end-of-line comment. The JSON RFC doesn't
+ * specify this behaviour, but it's required to parse
+ * existing documents. See http://b/2571423.
+ */
+ checkLenient();
+ skipToEndOfLine();
+ p = pos;
+ l = limit;
+ } else {
+ pos = p;
+ return c;
+ }
+ }
+ if (throwOnEof) {
+ throw new EOFException("End of input"
+ + " at line " + getLineNumber() + " column " + getColumnNumber());
+ } else {
+ return -1;
+ }
+ }
+
+ private void checkLenient() throws IOException {
+ if (!lenient) {
+ throw syntaxError("Use JsonReader.setLenient(true) to accept malformed JSON");
+ }
+ }
+
+ /**
+ * Advances the position until after the next newline character. If the line
+ * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+ * caller.
+ */
+ private void skipToEndOfLine() throws IOException {
+ while (pos < limit || fillBuffer(1)) {
+ char c = buffer[pos++];
+ if (c == '\n') {
+ lineNumber++;
+ lineStart = pos;
+ break;
+ } else if (c == '\r') {
+ break;
+ }
+ }
+ }
+
+ /**
+ * @param toFind a string to search for. Must not contain a newline.
+ */
+ private boolean skipTo(String toFind) throws IOException {
+ outer:
+ for (; pos + toFind.length() <= limit || fillBuffer(toFind.length()); pos++) {
+ if (buffer[pos] == '\n') {
+ lineNumber++;
+ lineStart = pos + 1;
+ continue;
+ }
+ for (int c = 0; c < toFind.length(); c++) {
+ if (buffer[pos + c] != toFind.charAt(c)) {
+ continue outer;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override public String toString() {
+ return getClass().getSimpleName()
+ + " at line " + getLineNumber() + " column " + getColumnNumber();
+ }
+
+ /**
+ * Returns a <a href="http://goessner.net/articles/JsonPath/">JsonPath</a> to
+ * the current location in the JSON value.
+ */
+ public String getPath() {
+ StringBuilder result = new StringBuilder().append('$');
+ for (int i = 0, size = stackSize; i < size; i++) {
+ switch (stack[i]) {
+ case JsonScope.EMPTY_ARRAY:
+ case JsonScope.NONEMPTY_ARRAY:
+ result.append('[').append(pathIndices[i]).append(']');
+ break;
+
+ case JsonScope.EMPTY_OBJECT:
+ case JsonScope.DANGLING_NAME:
+ case JsonScope.NONEMPTY_OBJECT:
+ result.append('.');
+ if (pathNames[i] != null) {
+ result.append(pathNames[i]);
+ }
+ break;
+
+ case JsonScope.NONEMPTY_DOCUMENT:
+ case JsonScope.EMPTY_DOCUMENT:
+ case JsonScope.CLOSED:
+ break;
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * Unescapes the character identified by the character or characters that
+ * immediately follow a backslash. The backslash '\' should have already
+ * been read. This supports both unicode escapes "u000A" and two-character
+ * escapes "\n".
+ *
+ * @throws NumberFormatException if any unicode escape sequences are
+ * malformed.
+ */
+ private char readEscapeCharacter() throws IOException {
+ if (pos == limit && !fillBuffer(1)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+
+ char escaped = buffer[pos++];
+ switch (escaped) {
+ case 'u':
+ if (pos + 4 > limit && !fillBuffer(4)) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ // Equivalent to Integer.parseInt(stringPool.get(buffer, pos, 4), 16);
+ char result = 0;
+ for (int i = pos, end = i + 4; i < end; i++) {
+ char c = buffer[i];
+ result <<= 4;
+ if (c >= '0' && c <= '9') {
+ result += (c - '0');
+ } else if (c >= 'a' && c <= 'f') {
+ result += (c - 'a' + 10);
+ } else if (c >= 'A' && c <= 'F') {
+ result += (c - 'A' + 10);
+ } else {
+ throw new NumberFormatException("\\u" + new String(buffer, pos, 4));
+ }
+ }
+ pos += 4;
+ return result;
+
+ case 't':
+ return '\t';
+
+ case 'b':
+ return '\b';
+
+ case 'n':
+ return '\n';
+
+ case 'r':
+ return '\r';
+
+ case 'f':
+ return '\f';
+
+ case '\n':
+ lineNumber++;
+ lineStart = pos;
+ // fall-through
+
+ case '\'':
+ case '"':
+ case '\\':
+ default:
+ return escaped;
+ }
+ }
+
+ /**
+ * Throws a new IO exception with the given message and a context snippet
+ * with this reader's content.
+ */
+ private IOException syntaxError(String message) throws IOException {
+ throw new MalformedJsonException(message
+ + " at line " + getLineNumber() + " column " + getColumnNumber() + " path " + getPath());
+ }
+
+ /**
+ * Consumes the non-execute prefix if it exists.
+ */
+ private void consumeNonExecutePrefix() throws IOException {
+ // fast forward through the leading whitespace
+ nextNonWhitespace(true);
+ pos--;
+
+ if (pos + NON_EXECUTE_PREFIX.length > limit && !fillBuffer(NON_EXECUTE_PREFIX.length)) {
+ return;
+ }
+
+ for (int i = 0; i < NON_EXECUTE_PREFIX.length; i++) {
+ if (buffer[pos + i] != NON_EXECUTE_PREFIX[i]) {
+ return; // not a security token!
+ }
+ }
+
+ // we consumed a security token!
+ pos += NON_EXECUTE_PREFIX.length;
+ }
+
+ static {
+ JsonReaderInternalAccess.INSTANCE = new JsonReaderInternalAccess() {
+ @Override public void promoteNameToValue(JsonReader reader) throws IOException {
+ if (reader instanceof JsonTreeReader) {
+ ((JsonTreeReader)reader).promoteNameToValue();
+ return;
+ }
+ int p = reader.peeked;
+ if (p == PEEKED_NONE) {
+ p = reader.doPeek();
+ }
+ if (p == PEEKED_DOUBLE_QUOTED_NAME) {
+ reader.peeked = PEEKED_DOUBLE_QUOTED;
+ } else if (p == PEEKED_SINGLE_QUOTED_NAME) {
+ reader.peeked = PEEKED_SINGLE_QUOTED;
+ } else if (p == PEEKED_UNQUOTED_NAME) {
+ reader.peeked = PEEKED_UNQUOTED;
+ } else {
+ throw new IllegalStateException("Expected a name but was " + reader.peek() + " "
+ + " at line " + reader.getLineNumber() + " column " + reader.getColumnNumber()
+ + " path " + reader.getPath());
+ }
+ }
+ };
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/stream/JsonScope.java b/gson/src/main/java/com/google/gson/stream/JsonScope.java
new file mode 100644
index 00000000..da691372
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/stream/JsonScope.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+/**
+ * Lexical scoping elements within a JSON reader or writer.
+ *
+ * @author Jesse Wilson
+ * @since 1.6
+ */
+final class JsonScope {
+
+ /**
+ * An array with no elements requires no separators or newlines before
+ * it is closed.
+ */
+ static final int EMPTY_ARRAY = 1;
+
+ /**
+ * A array with at least one value requires a comma and newline before
+ * the next element.
+ */
+ static final int NONEMPTY_ARRAY = 2;
+
+ /**
+ * An object with no name/value pairs requires no separators or newlines
+ * before it is closed.
+ */
+ static final int EMPTY_OBJECT = 3;
+
+ /**
+ * An object whose most recent element is a key. The next element must
+ * be a value.
+ */
+ static final int DANGLING_NAME = 4;
+
+ /**
+ * An object with at least one name/value pair requires a comma and
+ * newline before the next element.
+ */
+ static final int NONEMPTY_OBJECT = 5;
+
+ /**
+ * No object or array has been started.
+ */
+ static final int EMPTY_DOCUMENT = 6;
+
+ /**
+ * A document with at an array or object.
+ */
+ static final int NONEMPTY_DOCUMENT = 7;
+
+ /**
+ * A document that's been closed and cannot be accessed.
+ */
+ static final int CLOSED = 8;
+}
diff --git a/gson/src/main/java/com/google/gson/stream/JsonToken.java b/gson/src/main/java/com/google/gson/stream/JsonToken.java
new file mode 100644
index 00000000..f1025b3f
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/stream/JsonToken.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+/**
+ * A structure, name or value type in a JSON-encoded string.
+ *
+ * @author Jesse Wilson
+ * @since 1.6
+ */
+public enum JsonToken {
+
+ /**
+ * The opening of a JSON array. Written using {@link JsonWriter#beginArray}
+ * and read using {@link JsonReader#beginArray}.
+ */
+ BEGIN_ARRAY,
+
+ /**
+ * The closing of a JSON array. Written using {@link JsonWriter#endArray}
+ * and read using {@link JsonReader#endArray}.
+ */
+ END_ARRAY,
+
+ /**
+ * The opening of a JSON object. Written using {@link JsonWriter#beginObject}
+ * and read using {@link JsonReader#beginObject}.
+ */
+ BEGIN_OBJECT,
+
+ /**
+ * The closing of a JSON object. Written using {@link JsonWriter#endObject}
+ * and read using {@link JsonReader#endObject}.
+ */
+ END_OBJECT,
+
+ /**
+ * A JSON property name. Within objects, tokens alternate between names and
+ * their values. Written using {@link JsonWriter#name} and read using {@link
+ * JsonReader#nextName}
+ */
+ NAME,
+
+ /**
+ * A JSON string.
+ */
+ STRING,
+
+ /**
+ * A JSON number represented in this API by a Java {@code double}, {@code
+ * long}, or {@code int}.
+ */
+ NUMBER,
+
+ /**
+ * A JSON {@code true} or {@code false}.
+ */
+ BOOLEAN,
+
+ /**
+ * A JSON {@code null}.
+ */
+ NULL,
+
+ /**
+ * The end of the JSON stream. This sentinel value is returned by {@link
+ * JsonReader#peek()} to signal that the JSON-encoded value has no more
+ * tokens.
+ */
+ END_DOCUMENT
+}
diff --git a/gson/src/main/java/com/google/gson/stream/JsonWriter.java b/gson/src/main/java/com/google/gson/stream/JsonWriter.java
new file mode 100644
index 00000000..8d3bdb34
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/stream/JsonWriter.java
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+import java.io.Writer;
+
+import static com.google.gson.stream.JsonScope.DANGLING_NAME;
+import static com.google.gson.stream.JsonScope.EMPTY_ARRAY;
+import static com.google.gson.stream.JsonScope.EMPTY_DOCUMENT;
+import static com.google.gson.stream.JsonScope.EMPTY_OBJECT;
+import static com.google.gson.stream.JsonScope.NONEMPTY_ARRAY;
+import static com.google.gson.stream.JsonScope.NONEMPTY_DOCUMENT;
+import static com.google.gson.stream.JsonScope.NONEMPTY_OBJECT;
+
+/**
+ * Writes a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded value to a stream, one token at a time. The stream includes both
+ * literal values (strings, numbers, booleans and nulls) as well as the begin
+ * and end delimiters of objects and arrays.
+ *
+ * <h3>Encoding JSON</h3>
+ * To encode your data as JSON, create a new {@code JsonWriter}. Each JSON
+ * document must contain one top-level array or object. Call methods on the
+ * writer as you walk the structure's contents, nesting arrays and objects as
+ * necessary:
+ * <ul>
+ * <li>To write <strong>arrays</strong>, first call {@link #beginArray()}.
+ * Write each of the array's elements with the appropriate {@link #value}
+ * methods or by nesting other arrays and objects. Finally close the array
+ * using {@link #endArray()}.
+ * <li>To write <strong>objects</strong>, first call {@link #beginObject()}.
+ * Write each of the object's properties by alternating calls to
+ * {@link #name} with the property's value. Write property values with the
+ * appropriate {@link #value} method or by nesting other objects or arrays.
+ * Finally close the object using {@link #endObject()}.
+ * </ul>
+ *
+ * <h3>Example</h3>
+ * Suppose we'd like to encode a stream of messages such as the following: <pre> {@code
+ * [
+ * {
+ * "id": 912345678901,
+ * "text": "How do I stream JSON in Java?",
+ * "geo": null,
+ * "user": {
+ * "name": "json_newb",
+ * "followers_count": 41
+ * }
+ * },
+ * {
+ * "id": 912345678902,
+ * "text": "@json_newb just use JsonWriter!",
+ * "geo": [50.454722, -104.606667],
+ * "user": {
+ * "name": "jesse",
+ * "followers_count": 2
+ * }
+ * }
+ * ]}</pre>
+ * This code encodes the above structure: <pre> {@code
+ * public void writeJsonStream(OutputStream out, List<Message> messages) throws IOException {
+ * JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
+ * writer.setIndent(" ");
+ * writeMessagesArray(writer, messages);
+ * writer.close();
+ * }
+ *
+ * public void writeMessagesArray(JsonWriter writer, List<Message> messages) throws IOException {
+ * writer.beginArray();
+ * for (Message message : messages) {
+ * writeMessage(writer, message);
+ * }
+ * writer.endArray();
+ * }
+ *
+ * public void writeMessage(JsonWriter writer, Message message) throws IOException {
+ * writer.beginObject();
+ * writer.name("id").value(message.getId());
+ * writer.name("text").value(message.getText());
+ * if (message.getGeo() != null) {
+ * writer.name("geo");
+ * writeDoublesArray(writer, message.getGeo());
+ * } else {
+ * writer.name("geo").nullValue();
+ * }
+ * writer.name("user");
+ * writeUser(writer, message.getUser());
+ * writer.endObject();
+ * }
+ *
+ * public void writeUser(JsonWriter writer, User user) throws IOException {
+ * writer.beginObject();
+ * writer.name("name").value(user.getName());
+ * writer.name("followers_count").value(user.getFollowersCount());
+ * writer.endObject();
+ * }
+ *
+ * public void writeDoublesArray(JsonWriter writer, List<Double> doubles) throws IOException {
+ * writer.beginArray();
+ * for (Double value : doubles) {
+ * writer.value(value);
+ * }
+ * writer.endArray();
+ * }}</pre>
+ *
+ * <p>Each {@code JsonWriter} may be used to write a single JSON stream.
+ * Instances of this class are not thread safe. Calls that would result in a
+ * malformed JSON string will fail with an {@link IllegalStateException}.
+ *
+ * @author Jesse Wilson
+ * @since 1.6
+ */
+public class JsonWriter implements Closeable, Flushable {
+
+ /*
+ * From RFC 4627, "All Unicode characters may be placed within the
+ * quotation marks except for the characters that must be escaped:
+ * quotation mark, reverse solidus, and the control characters
+ * (U+0000 through U+001F)."
+ *
+ * We also escape '\u2028' and '\u2029', which JavaScript interprets as
+ * newline characters. This prevents eval() from failing with a syntax
+ * error. http://code.google.com/p/google-gson/issues/detail?id=341
+ */
+ private static final String[] REPLACEMENT_CHARS;
+ private static final String[] HTML_SAFE_REPLACEMENT_CHARS;
+ static {
+ REPLACEMENT_CHARS = new String[128];
+ for (int i = 0; i <= 0x1f; i++) {
+ REPLACEMENT_CHARS[i] = String.format("\\u%04x", (int) i);
+ }
+ REPLACEMENT_CHARS['"'] = "\\\"";
+ REPLACEMENT_CHARS['\\'] = "\\\\";
+ REPLACEMENT_CHARS['\t'] = "\\t";
+ REPLACEMENT_CHARS['\b'] = "\\b";
+ REPLACEMENT_CHARS['\n'] = "\\n";
+ REPLACEMENT_CHARS['\r'] = "\\r";
+ REPLACEMENT_CHARS['\f'] = "\\f";
+ HTML_SAFE_REPLACEMENT_CHARS = REPLACEMENT_CHARS.clone();
+ HTML_SAFE_REPLACEMENT_CHARS['<'] = "\\u003c";
+ HTML_SAFE_REPLACEMENT_CHARS['>'] = "\\u003e";
+ HTML_SAFE_REPLACEMENT_CHARS['&'] = "\\u0026";
+ HTML_SAFE_REPLACEMENT_CHARS['='] = "\\u003d";
+ HTML_SAFE_REPLACEMENT_CHARS['\''] = "\\u0027";
+ }
+
+ /** The output data, containing at most one top-level array or object. */
+ private final Writer out;
+
+ private int[] stack = new int[32];
+ private int stackSize = 0;
+ {
+ push(EMPTY_DOCUMENT);
+ }
+
+ /**
+ * A string containing a full set of spaces for a single level of
+ * indentation, or null for no pretty printing.
+ */
+ private String indent;
+
+ /**
+ * The name/value separator; either ":" or ": ".
+ */
+ private String separator = ":";
+
+ private boolean lenient;
+
+ private boolean htmlSafe;
+
+ private String deferredName;
+
+ private boolean serializeNulls = true;
+
+ /**
+ * Creates a new instance that writes a JSON-encoded stream to {@code out}.
+ * For best performance, ensure {@link Writer} is buffered; wrapping in
+ * {@link java.io.BufferedWriter BufferedWriter} if necessary.
+ */
+ public JsonWriter(Writer out) {
+ if (out == null) {
+ throw new NullPointerException("out == null");
+ }
+ this.out = out;
+ }
+
+ /**
+ * Sets the indentation string to be repeated for each level of indentation
+ * in the encoded document. If {@code indent.isEmpty()} the encoded document
+ * will be compact. Otherwise the encoded document will be more
+ * human-readable.
+ *
+ * @param indent a string containing only whitespace.
+ */
+ public final void setIndent(String indent) {
+ if (indent.length() == 0) {
+ this.indent = null;
+ this.separator = ":";
+ } else {
+ this.indent = indent;
+ this.separator = ": ";
+ }
+ }
+
+ /**
+ * Configure this writer to relax its syntax rules. By default, this writer
+ * only emits well-formed JSON as specified by <a
+ * href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>. Setting the writer
+ * to lenient permits the following:
+ * <ul>
+ * <li>Top-level values of any type. With strict writing, the top-level
+ * value must be an object or an array.
+ * <li>Numbers may be {@link Double#isNaN() NaNs} or {@link
+ * Double#isInfinite() infinities}.
+ * </ul>
+ */
+ public final void setLenient(boolean lenient) {
+ this.lenient = lenient;
+ }
+
+ /**
+ * Returns true if this writer has relaxed syntax rules.
+ */
+ public boolean isLenient() {
+ return lenient;
+ }
+
+ /**
+ * Configure this writer to emit JSON that's safe for direct inclusion in HTML
+ * and XML documents. This escapes the HTML characters {@code <}, {@code >},
+ * {@code &} and {@code =} before writing them to the stream. Without this
+ * setting, your XML/HTML encoder should replace these characters with the
+ * corresponding escape sequences.
+ */
+ public final void setHtmlSafe(boolean htmlSafe) {
+ this.htmlSafe = htmlSafe;
+ }
+
+ /**
+ * Returns true if this writer writes JSON that's safe for inclusion in HTML
+ * and XML documents.
+ */
+ public final boolean isHtmlSafe() {
+ return htmlSafe;
+ }
+
+ /**
+ * Sets whether object members are serialized when their value is null.
+ * This has no impact on array elements. The default is true.
+ */
+ public final void setSerializeNulls(boolean serializeNulls) {
+ this.serializeNulls = serializeNulls;
+ }
+
+ /**
+ * Returns true if object members are serialized when their value is null.
+ * This has no impact on array elements. The default is true.
+ */
+ public final boolean getSerializeNulls() {
+ return serializeNulls;
+ }
+
+ /**
+ * Begins encoding a new array. Each call to this method must be paired with
+ * a call to {@link #endArray}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginArray() throws IOException {
+ writeDeferredName();
+ return open(EMPTY_ARRAY, "[");
+ }
+
+ /**
+ * Ends encoding the current array.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endArray() throws IOException {
+ return close(EMPTY_ARRAY, NONEMPTY_ARRAY, "]");
+ }
+
+ /**
+ * Begins encoding a new object. Each call to this method must be paired
+ * with a call to {@link #endObject}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter beginObject() throws IOException {
+ writeDeferredName();
+ return open(EMPTY_OBJECT, "{");
+ }
+
+ /**
+ * Ends encoding the current object.
+ *
+ * @return this writer.
+ */
+ public JsonWriter endObject() throws IOException {
+ return close(EMPTY_OBJECT, NONEMPTY_OBJECT, "}");
+ }
+
+ /**
+ * Enters a new scope by appending any necessary whitespace and the given
+ * bracket.
+ */
+ private JsonWriter open(int empty, String openBracket) throws IOException {
+ beforeValue(true);
+ push(empty);
+ out.write(openBracket);
+ return this;
+ }
+
+ /**
+ * Closes the current scope by appending any necessary whitespace and the
+ * given bracket.
+ */
+ private JsonWriter close(int empty, int nonempty, String closeBracket)
+ throws IOException {
+ int context = peek();
+ if (context != nonempty && context != empty) {
+ throw new IllegalStateException("Nesting problem.");
+ }
+ if (deferredName != null) {
+ throw new IllegalStateException("Dangling name: " + deferredName);
+ }
+
+ stackSize--;
+ if (context == nonempty) {
+ newline();
+ }
+ out.write(closeBracket);
+ return this;
+ }
+
+ private void push(int newTop) {
+ if (stackSize == stack.length) {
+ int[] newStack = new int[stackSize * 2];
+ System.arraycopy(stack, 0, newStack, 0, stackSize);
+ stack = newStack;
+ }
+ stack[stackSize++] = newTop;
+ }
+
+ /**
+ * Returns the value on the top of the stack.
+ */
+ private int peek() {
+ if (stackSize == 0) {
+ throw new IllegalStateException("JsonWriter is closed.");
+ }
+ return stack[stackSize - 1];
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(int topOfStack) {
+ stack[stackSize - 1] = topOfStack;
+ }
+
+ /**
+ * Encodes the property name.
+ *
+ * @param name the name of the forthcoming value. May not be null.
+ * @return this writer.
+ */
+ public JsonWriter name(String name) throws IOException {
+ if (name == null) {
+ throw new NullPointerException("name == null");
+ }
+ if (deferredName != null) {
+ throw new IllegalStateException();
+ }
+ if (stackSize == 0) {
+ throw new IllegalStateException("JsonWriter is closed.");
+ }
+ deferredName = name;
+ return this;
+ }
+
+ private void writeDeferredName() throws IOException {
+ if (deferredName != null) {
+ beforeName();
+ string(deferredName);
+ deferredName = null;
+ }
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value the literal string value, or null to encode a null literal.
+ * @return this writer.
+ */
+ public JsonWriter value(String value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+ writeDeferredName();
+ beforeValue(false);
+ string(value);
+ return this;
+ }
+
+ /**
+ * Writes {@code value} directly to the writer without quoting or
+ * escaping.
+ *
+ * @param value the literal string value, or null to encode a null literal.
+ * @return this writer.
+ */
+ public JsonWriter jsonValue(String value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+ writeDeferredName();
+ beforeValue(false);
+ out.append(value);
+ return this;
+ }
+
+ /**
+ * Encodes {@code null}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter nullValue() throws IOException {
+ if (deferredName != null) {
+ if (serializeNulls) {
+ writeDeferredName();
+ } else {
+ deferredName = null;
+ return this; // skip the name and the value
+ }
+ }
+ beforeValue(false);
+ out.write("null");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(boolean value) throws IOException {
+ writeDeferredName();
+ beforeValue(false);
+ out.write(value ? "true" : "false");
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this writer.
+ */
+ public JsonWriter value(double value) throws IOException {
+ if (Double.isNaN(value) || Double.isInfinite(value)) {
+ throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+ }
+ writeDeferredName();
+ beforeValue(false);
+ out.append(Double.toString(value));
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @return this writer.
+ */
+ public JsonWriter value(long value) throws IOException {
+ writeDeferredName();
+ beforeValue(false);
+ out.write(Long.toString(value));
+ return this;
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this writer.
+ */
+ public JsonWriter value(Number value) throws IOException {
+ if (value == null) {
+ return nullValue();
+ }
+
+ writeDeferredName();
+ String string = value.toString();
+ if (!lenient
+ && (string.equals("-Infinity") || string.equals("Infinity") || string.equals("NaN"))) {
+ throw new IllegalArgumentException("Numeric values must be finite, but was " + value);
+ }
+ beforeValue(false);
+ out.append(string);
+ return this;
+ }
+
+ /**
+ * Ensures all buffered data is written to the underlying {@link Writer}
+ * and flushes that writer.
+ */
+ public void flush() throws IOException {
+ if (stackSize == 0) {
+ throw new IllegalStateException("JsonWriter is closed.");
+ }
+ out.flush();
+ }
+
+ /**
+ * Flushes and closes this writer and the underlying {@link Writer}.
+ *
+ * @throws IOException if the JSON document is incomplete.
+ */
+ public void close() throws IOException {
+ out.close();
+
+ int size = stackSize;
+ if (size > 1 || size == 1 && stack[size - 1] != NONEMPTY_DOCUMENT) {
+ throw new IOException("Incomplete document");
+ }
+ stackSize = 0;
+ }
+
+ private void string(String value) throws IOException {
+ String[] replacements = htmlSafe ? HTML_SAFE_REPLACEMENT_CHARS : REPLACEMENT_CHARS;
+ out.write("\"");
+ int last = 0;
+ int length = value.length();
+ for (int i = 0; i < length; i++) {
+ char c = value.charAt(i);
+ String replacement;
+ if (c < 128) {
+ replacement = replacements[c];
+ if (replacement == null) {
+ continue;
+ }
+ } else if (c == '\u2028') {
+ replacement = "\\u2028";
+ } else if (c == '\u2029') {
+ replacement = "\\u2029";
+ } else {
+ continue;
+ }
+ if (last < i) {
+ out.write(value, last, i - last);
+ }
+ out.write(replacement);
+ last = i + 1;
+ }
+ if (last < length) {
+ out.write(value, last, length - last);
+ }
+ out.write("\"");
+ }
+
+ private void newline() throws IOException {
+ if (indent == null) {
+ return;
+ }
+
+ out.write("\n");
+ for (int i = 1, size = stackSize; i < size; i++) {
+ out.write(indent);
+ }
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a name. Also
+ * adjusts the stack to expect the name's value.
+ */
+ private void beforeName() throws IOException {
+ int context = peek();
+ if (context == NONEMPTY_OBJECT) { // first in object
+ out.write(',');
+ } else if (context != EMPTY_OBJECT) { // not in an object!
+ throw new IllegalStateException("Nesting problem.");
+ }
+ newline();
+ replaceTop(DANGLING_NAME);
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a literal value,
+ * inline array, or inline object. Also adjusts the stack to expect either a
+ * closing bracket or another element.
+ *
+ * @param root true if the value is a new array or object, the two values
+ * permitted as top-level elements.
+ */
+ @SuppressWarnings("fallthrough")
+ private void beforeValue(boolean root) throws IOException {
+ switch (peek()) {
+ case NONEMPTY_DOCUMENT:
+ if (!lenient) {
+ throw new IllegalStateException(
+ "JSON must have only one top-level value.");
+ }
+ // fall-through
+ case EMPTY_DOCUMENT: // first in document
+ if (!lenient && !root) {
+ throw new IllegalStateException(
+ "JSON must start with an array or an object.");
+ }
+ replaceTop(NONEMPTY_DOCUMENT);
+ break;
+
+ case EMPTY_ARRAY: // first in array
+ replaceTop(NONEMPTY_ARRAY);
+ newline();
+ break;
+
+ case NONEMPTY_ARRAY: // another in array
+ out.append(',');
+ newline();
+ break;
+
+ case DANGLING_NAME: // value for name
+ out.append(separator);
+ replaceTop(NONEMPTY_OBJECT);
+ break;
+
+ default:
+ throw new IllegalStateException("Nesting problem.");
+ }
+ }
+}
diff --git a/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java b/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java
new file mode 100644
index 00000000..9da70ebc
--- /dev/null
+++ b/gson/src/main/java/com/google/gson/stream/MalformedJsonException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+import java.io.IOException;
+
+/**
+ * Thrown when a reader encounters malformed JSON. Some syntax errors can be
+ * ignored by calling {@link JsonReader#setLenient(boolean)}.
+ */
+public final class MalformedJsonException extends IOException {
+ private static final long serialVersionUID = 1L;
+
+ public MalformedJsonException(String msg) {
+ super(msg);
+ }
+
+ public MalformedJsonException(String msg, Throwable throwable) {
+ super(msg);
+ // Using initCause() instead of calling super() because Java 1.5 didn't retrofit IOException
+ // with a constructor with Throwable. This was done in Java 1.6
+ initCause(throwable);
+ }
+
+ public MalformedJsonException(Throwable throwable) {
+ // Using initCause() instead of calling super() because Java 1.5 didn't retrofit IOException
+ // with a constructor with Throwable. This was done in Java 1.6
+ initCause(throwable);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/CommentsTest.java b/gson/src/test/java/com/google/gson/CommentsTest.java
new file mode 100644
index 00000000..306e5aff
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/CommentsTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson;
+
+import com.google.gson.reflect.TypeToken;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * @author Jesse Wilson
+ */
+public final class CommentsTest extends TestCase {
+
+ /**
+ * Test for issue 212.
+ */
+ public void testParseComments() {
+ String json = "[\n"
+ + " // this is a comment\n"
+ + " \"a\",\n"
+ + " /* this is another comment */\n"
+ + " \"b\",\n"
+ + " # this is yet another comment\n"
+ + " \"c\"\n"
+ + "]";
+
+ List<String> abc = new Gson().fromJson(json, new TypeToken<List<String>>() {}.getType());
+ assertEquals(Arrays.asList("a", "b", "c"), abc);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java
new file mode 100644
index 00000000..966a8c18
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/DefaultDateTypeAdapterTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import junit.framework.TestCase;
+
+/**
+ * A simple unit test for the {@link DefaultDateTypeAdapter} class.
+ *
+ * @author Joel Leitch
+ */
+public class DefaultDateTypeAdapterTest extends TestCase {
+
+ public void testFormattingInEnUs() {
+ assertFormattingAlwaysEmitsUsLocale(Locale.US);
+ }
+
+ public void testFormattingInFr() {
+ assertFormattingAlwaysEmitsUsLocale(Locale.FRANCE);
+ }
+
+ private void assertFormattingAlwaysEmitsUsLocale(Locale locale) {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(locale);
+ try {
+ assertFormatted("Jan 1, 1970 12:00:00 AM", new DefaultDateTypeAdapter());
+ assertFormatted("1/1/70", new DefaultDateTypeAdapter(DateFormat.SHORT));
+ assertFormatted("Jan 1, 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM));
+ assertFormatted("January 1, 1970", new DefaultDateTypeAdapter(DateFormat.LONG));
+ assertFormatted("1/1/70 12:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
+ assertFormatted("Jan 1, 1970 12:00:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertFormatted("January 1, 1970 12:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG));
+ assertFormatted("Thursday, January 1, 1970 12:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testParsingDatesFormattedWithSystemLocale() {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.FRANCE);
+ try {
+ assertParsed("1 janv. 1970 00:00:00", new DefaultDateTypeAdapter());
+ assertParsed("01/01/70", new DefaultDateTypeAdapter(DateFormat.SHORT));
+ assertParsed("1 janv. 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM));
+ assertParsed("1 janvier 1970", new DefaultDateTypeAdapter(DateFormat.LONG));
+ assertParsed("01/01/70 00:00",
+ new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
+ assertParsed("1 janv. 1970 00:00:00",
+ new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertParsed("1 janvier 1970 00:00:00 UTC",
+ new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG));
+ assertParsed("jeudi 1 janvier 1970 00 h 00 UTC",
+ new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testParsingDatesFormattedWithUsLocale() {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ try {
+ assertParsed("Jan 1, 1970 0:00:00 AM", new DefaultDateTypeAdapter());
+ assertParsed("1/1/70", new DefaultDateTypeAdapter(DateFormat.SHORT));
+ assertParsed("Jan 1, 1970", new DefaultDateTypeAdapter(DateFormat.MEDIUM));
+ assertParsed("January 1, 1970", new DefaultDateTypeAdapter(DateFormat.LONG));
+ assertParsed("1/1/70 0:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.SHORT, DateFormat.SHORT));
+ assertParsed("Jan 1, 1970 0:00:00 AM",
+ new DefaultDateTypeAdapter(DateFormat.MEDIUM, DateFormat.MEDIUM));
+ assertParsed("January 1, 1970 0:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.LONG, DateFormat.LONG));
+ assertParsed("Thursday, January 1, 1970 0:00:00 AM UTC",
+ new DefaultDateTypeAdapter(DateFormat.FULL, DateFormat.FULL));
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testFormatUsesDefaultTimezone() {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ try {
+ assertFormatted("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter());
+ assertParsed("Dec 31, 1969 4:00:00 PM", new DefaultDateTypeAdapter());
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testDateSerialization() throws Exception {
+ int dateStyle = DateFormat.LONG;
+ DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle);
+ DateFormat formatter = DateFormat.getDateInstance(dateStyle, Locale.US);
+ Date currentDate = new Date();
+
+ String dateString = dateTypeAdapter.serialize(currentDate, Date.class, null).getAsString();
+ assertEquals(formatter.format(currentDate), dateString);
+ }
+
+ public void testDatePattern() throws Exception {
+ String pattern = "yyyy-MM-dd";
+ DefaultDateTypeAdapter dateTypeAdapter = new DefaultDateTypeAdapter(pattern);
+ DateFormat formatter = new SimpleDateFormat(pattern);
+ Date currentDate = new Date();
+
+ String dateString = dateTypeAdapter.serialize(currentDate, Date.class, null).getAsString();
+ assertEquals(formatter.format(currentDate), dateString);
+ }
+
+ public void testInvalidDatePattern() throws Exception {
+ try {
+ new DefaultDateTypeAdapter("I am a bad Date pattern....");
+ fail("Invalid date pattern should fail.");
+ } catch (IllegalArgumentException expected) { }
+ }
+
+ private void assertFormatted(String formatted, DefaultDateTypeAdapter adapter) {
+ assertEquals(formatted, adapter.serialize(new Date(0), Date.class, null).getAsString());
+ }
+
+ private void assertParsed(String date, DefaultDateTypeAdapter adapter) {
+ assertEquals(date, new Date(0), adapter.deserialize(new JsonPrimitive(date), Date.class, null));
+ assertEquals("ISO 8601", new Date(0), adapter.deserialize(
+ new JsonPrimitive("1970-01-01T00:00:00Z"), Date.class, null));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java
new file mode 100644
index 00000000..6b853f5d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/DefaultInetAddressTypeAdapterTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import java.net.InetAddress;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the default serializer/deserializer for the {@code InetAddress} type.
+ *
+ * @author Joel Leitch
+ */
+public class DefaultInetAddressTypeAdapterTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testInetAddressSerializationAndDeserialization() throws Exception {
+ InetAddress address = InetAddress.getByName("8.8.8.8");
+ String jsonAddress = gson.toJson(address);
+ assertEquals("\"8.8.8.8\"", jsonAddress);
+
+ InetAddress value = gson.fromJson(jsonAddress, InetAddress.class);
+ assertEquals(value, address);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/DefaultMapJsonSerializerTest.java b/gson/src/test/java/com/google/gson/DefaultMapJsonSerializerTest.java
new file mode 100644
index 00000000..5c061953
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/DefaultMapJsonSerializerTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import junit.framework.TestCase;
+
+/**
+ * Unit test for the default JSON map serialization object located in the
+ * {@link DefaultTypeAdapters} class.
+ *
+ * @author Joel Leitch
+ */
+public class DefaultMapJsonSerializerTest extends TestCase {
+ private Gson gson = new Gson();
+
+ public void testEmptyMapNoTypeSerialization() {
+ Map<String, String> emptyMap = new HashMap<String, String>();
+ JsonElement element = gson.toJsonTree(emptyMap, emptyMap.getClass());
+ assertTrue(element instanceof JsonObject);
+ JsonObject emptyMapJsonObject = (JsonObject) element;
+ assertTrue(emptyMapJsonObject.entrySet().isEmpty());
+ }
+
+ public void testEmptyMapSerialization() {
+ Type mapType = new TypeToken<Map<String, String>>() { }.getType();
+ Map<String, String> emptyMap = new HashMap<String, String>();
+ JsonElement element = gson.toJsonTree(emptyMap, mapType);
+
+ assertTrue(element instanceof JsonObject);
+ JsonObject emptyMapJsonObject = (JsonObject) element;
+ assertTrue(emptyMapJsonObject.entrySet().isEmpty());
+ }
+
+ public void testNonEmptyMapSerialization() {
+ Type mapType = new TypeToken<Map<String, String>>() { }.getType();
+ Map<String, String> myMap = new HashMap<String, String>();
+ String key = "key1";
+ myMap.put(key, "value1");
+ Gson gson = new Gson();
+ JsonElement element = gson.toJsonTree(myMap, mapType);
+
+ assertTrue(element.isJsonObject());
+ JsonObject mapJsonObject = element.getAsJsonObject();
+ assertTrue(mapJsonObject.has(key));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java
new file mode 100644
index 00000000..dd8a7a92
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/ExposeAnnotationExclusionStrategyTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import com.google.gson.annotations.Expose;
+
+import com.google.gson.internal.Excluder;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Field;
+
+/**
+ * Unit tests for GsonBuilder.REQUIRE_EXPOSE_DESERIALIZE.
+ *
+ * @author Joel Leitch
+ */
+public class ExposeAnnotationExclusionStrategyTest extends TestCase {
+ private Excluder excluder = Excluder.DEFAULT.excludeFieldsWithoutExposeAnnotation();
+
+ public void testNeverSkipClasses() throws Exception {
+ assertFalse(excluder.excludeClass(MockObject.class, true));
+ assertFalse(excluder.excludeClass(MockObject.class, false));
+ }
+
+ public void testSkipNonAnnotatedFields() throws Exception {
+ Field f = createFieldAttributes("hiddenField");
+ assertTrue(excluder.excludeField(f, true));
+ assertTrue(excluder.excludeField(f, false));
+ }
+
+ public void testSkipExplicitlySkippedFields() throws Exception {
+ Field f = createFieldAttributes("explicitlyHiddenField");
+ assertTrue(excluder.excludeField(f, true));
+ assertTrue(excluder.excludeField(f, false));
+ }
+
+ public void testNeverSkipExposedAnnotatedFields() throws Exception {
+ Field f = createFieldAttributes("exposedField");
+ assertFalse(excluder.excludeField(f, true));
+ assertFalse(excluder.excludeField(f, false));
+ }
+
+ public void testNeverSkipExplicitlyExposedAnnotatedFields() throws Exception {
+ Field f = createFieldAttributes("explicitlyExposedField");
+ assertFalse(excluder.excludeField(f, true));
+ assertFalse(excluder.excludeField(f, false));
+ }
+
+ public void testDifferentSerializeAndDeserializeField() throws Exception {
+ Field f = createFieldAttributes("explicitlyDifferentModeField");
+ assertFalse(excluder.excludeField(f, true));
+ assertTrue(excluder.excludeField(f, false));
+ }
+
+ private static Field createFieldAttributes(String fieldName) throws Exception {
+ return MockObject.class.getField(fieldName);
+ }
+
+ @SuppressWarnings("unused")
+ private static class MockObject {
+ @Expose
+ public final int exposedField = 0;
+
+ @Expose(serialize=true, deserialize=true)
+ public final int explicitlyExposedField = 0;
+
+ @Expose(serialize=false, deserialize=false)
+ public final int explicitlyHiddenField = 0;
+
+ @Expose(serialize=true, deserialize=false)
+ public final int explicitlyDifferentModeField = 0;
+
+ public final int hiddenField = 0;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/FieldAttributesTest.java b/gson/src/test/java/com/google/gson/FieldAttributesTest.java
new file mode 100644
index 00000000..8a9d9533
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/FieldAttributesTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Unit tests for the {@link FieldAttributes} class.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class FieldAttributesTest extends TestCase {
+ private FieldAttributes fieldAttributes;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ fieldAttributes = new FieldAttributes(Foo.class.getField("bar"));
+ }
+
+ public void testNullField() throws Exception {
+ try {
+ new FieldAttributes(null);
+ fail("Field parameter can not be null");
+ } catch (NullPointerException expected) { }
+ }
+
+ public void testDeclaringClass() throws Exception {
+ assertEquals(Foo.class, fieldAttributes.getDeclaringClass());
+ }
+
+ public void testModifiers() throws Exception {
+ assertFalse(fieldAttributes.hasModifier(Modifier.STATIC));
+ assertFalse(fieldAttributes.hasModifier(Modifier.FINAL));
+ assertFalse(fieldAttributes.hasModifier(Modifier.ABSTRACT));
+ assertFalse(fieldAttributes.hasModifier(Modifier.VOLATILE));
+ assertFalse(fieldAttributes.hasModifier(Modifier.PROTECTED));
+
+ assertTrue(fieldAttributes.hasModifier(Modifier.PUBLIC));
+ assertTrue(fieldAttributes.hasModifier(Modifier.TRANSIENT));
+ }
+
+ public void testIsSynthetic() throws Exception {
+ assertFalse(fieldAttributes.isSynthetic());
+ }
+
+ public void testName() throws Exception {
+ assertEquals("bar", fieldAttributes.getName());
+ }
+
+ public void testDeclaredTypeAndClass() throws Exception {
+ Type expectedType = new TypeToken<List<String>>() {}.getType();
+ assertEquals(expectedType, fieldAttributes.getDeclaredType());
+ assertEquals(List.class, fieldAttributes.getDeclaredClass());
+ }
+
+ private static class Foo {
+ @SuppressWarnings("unused")
+ public transient List<String> bar;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/GenericArrayTypeTest.java b/gson/src/test/java/com/google/gson/GenericArrayTypeTest.java
new file mode 100644
index 00000000..42acb8a2
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/GenericArrayTypeTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Unit tests for the {@code GenericArrayType}s created by the {@link $Gson$Types} class.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class GenericArrayTypeTest extends TestCase {
+ private GenericArrayType ourType;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ourType = $Gson$Types.arrayOf($Gson$Types.newParameterizedTypeWithOwner(null, List.class, String.class));
+ }
+
+ public void testOurTypeFunctionality() throws Exception {
+ Type parameterizedType = new TypeToken<List<String>>() {}.getType();
+ Type genericArrayType = new TypeToken<List<String>[]>() {}.getType();
+
+ assertEquals(parameterizedType, ourType.getGenericComponentType());
+ assertEquals(genericArrayType, ourType);
+ assertEquals(genericArrayType.hashCode(), ourType.hashCode());
+ }
+
+ public void testNotEquals() throws Exception {
+ Type differentGenericArrayType = new TypeToken<List<String>[][]>() {}.getType();
+ assertFalse(differentGenericArrayType.equals(ourType));
+ assertFalse(ourType.equals(differentGenericArrayType));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/GsonBuilderTest.java b/gson/src/test/java/com/google/gson/GsonBuilderTest.java
new file mode 100755
index 00000000..73601c0e
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/GsonBuilderTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+
+import junit.framework.TestCase;
+
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Unit tests for {@link GsonBuilder}.
+ *
+ * @author Inderjeet Singh
+ */
+public class GsonBuilderTest extends TestCase {
+ private static final TypeAdapter<Object> NULL_TYPE_ADAPTER = new TypeAdapter<Object>() {
+ @Override public void write(JsonWriter out, Object value) {
+ throw new AssertionError();
+ }
+ @Override public Object read(JsonReader in) {
+ throw new AssertionError();
+ }
+ };
+
+ public void testCreatingMoreThanOnce() {
+ GsonBuilder builder = new GsonBuilder();
+ builder.create();
+ builder.create();
+ }
+
+ public void testExcludeFieldsWithModifiers() {
+ Gson gson = new GsonBuilder()
+ .excludeFieldsWithModifiers(Modifier.VOLATILE, Modifier.PRIVATE)
+ .create();
+ assertEquals("{\"d\":\"d\"}", gson.toJson(new HasModifiers()));
+ }
+
+ public void testRegisterTypeAdapterForCoreType() {
+ Type[] types = {
+ byte.class,
+ int.class,
+ double.class,
+ Short.class,
+ Long.class,
+ String.class,
+ };
+ for (Type type : types) {
+ new GsonBuilder().registerTypeAdapter(type, NULL_TYPE_ADAPTER);
+ }
+ }
+
+ @SuppressWarnings("unused")
+ static class HasModifiers {
+ private String a = "a";
+ volatile String b = "b";
+ private volatile String c = "c";
+ String d = "d";
+ }
+
+ public void testTransientFieldExclusion() {
+ Gson gson = new GsonBuilder()
+ .excludeFieldsWithModifiers()
+ .create();
+ assertEquals("{\"a\":\"a\"}", gson.toJson(new HasTransients()));
+ }
+
+ static class HasTransients {
+ transient String a = "a";
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/GsonTypeAdapterTest.java b/gson/src/test/java/com/google/gson/GsonTypeAdapterTest.java
new file mode 100644
index 00000000..922cecc4
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/GsonTypeAdapterTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import java.lang.reflect.Type;
+import java.math.BigInteger;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import junit.framework.TestCase;
+
+/**
+ * Contains numerous tests involving registered type converters with a Gson instance.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class GsonTypeAdapterTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new GsonBuilder()
+ .registerTypeAdapter(AtomicLong.class, new ExceptionTypeAdapter())
+ .registerTypeAdapter(AtomicInteger.class, new AtomicIntegerTypeAdapter())
+ .create();
+ }
+
+ public void testDefaultTypeAdapterThrowsParseException() throws Exception {
+ try {
+ gson.fromJson("{\"abc\":123}", BigInteger.class);
+ fail("Should have thrown a JsonParseException");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testTypeAdapterThrowsException() throws Exception {
+ try {
+ gson.toJson(new AtomicLong(0));
+ fail("Type Adapter should have thrown an exception");
+ } catch (IllegalStateException expected) { }
+
+ try {
+ gson.fromJson("123", AtomicLong.class);
+ fail("Type Adapter should have thrown an exception");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testTypeAdapterProperlyConvertsTypes() throws Exception {
+ int intialValue = 1;
+ AtomicInteger atomicInt = new AtomicInteger(intialValue);
+ String json = gson.toJson(atomicInt);
+ assertEquals(intialValue + 1, Integer.parseInt(json));
+
+ atomicInt = gson.fromJson(json, AtomicInteger.class);
+ assertEquals(intialValue, atomicInt.get());
+ }
+
+ public void testTypeAdapterDoesNotAffectNonAdaptedTypes() throws Exception {
+ String expected = "blah";
+ String actual = gson.toJson(expected);
+ assertEquals("\"" + expected + "\"", actual);
+
+ actual = gson.fromJson(actual, String.class);
+ assertEquals(expected, actual);
+ }
+
+ private static class ExceptionTypeAdapter
+ implements JsonSerializer<AtomicLong>, JsonDeserializer<AtomicLong> {
+ public JsonElement serialize(
+ AtomicLong src, Type typeOfSrc, JsonSerializationContext context) {
+ throw new IllegalStateException();
+ }
+
+ public AtomicLong deserialize(
+ JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ throw new IllegalStateException();
+ }
+ }
+
+ private static class AtomicIntegerTypeAdapter
+ implements JsonSerializer<AtomicInteger>, JsonDeserializer<AtomicInteger> {
+ public JsonElement serialize(AtomicInteger src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.incrementAndGet());
+ }
+
+ public AtomicInteger deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ int intValue = json.getAsInt();
+ return new AtomicInteger(--intValue);
+ }
+ }
+
+ static abstract class Abstract {
+ String a;
+ }
+
+ static class Concrete extends Abstract {
+ String b;
+ }
+
+ // https://groups.google.com/d/topic/google-gson/EBmOCa8kJPE/discussion
+ public void testDeserializerForAbstractClass() {
+ Concrete instance = new Concrete();
+ instance.a = "android";
+ instance.b = "beep";
+ assertSerialized("{\"a\":\"android\"}", Abstract.class, true, true, instance);
+ assertSerialized("{\"a\":\"android\"}", Abstract.class, true, false, instance);
+ assertSerialized("{\"a\":\"android\"}", Abstract.class, false, true, instance);
+ assertSerialized("{\"a\":\"android\"}", Abstract.class, false, false, instance);
+ assertSerialized("{\"b\":\"beep\",\"a\":\"android\"}", Concrete.class, true, true, instance);
+ assertSerialized("{\"b\":\"beep\",\"a\":\"android\"}", Concrete.class, true, false, instance);
+ assertSerialized("{\"b\":\"beep\",\"a\":\"android\"}", Concrete.class, false, true, instance);
+ assertSerialized("{\"b\":\"beep\",\"a\":\"android\"}", Concrete.class, false, false, instance);
+ }
+
+ private void assertSerialized(String expected, Class<?> instanceType, boolean registerAbstractDeserializer,
+ boolean registerAbstractHierarchyDeserializer, Object instance) {
+ JsonDeserializer<Abstract> deserializer = new JsonDeserializer<Abstract>() {
+ public Abstract deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ throw new AssertionError();
+ }
+ };
+ GsonBuilder builder = new GsonBuilder();
+ if (registerAbstractDeserializer) {
+ builder.registerTypeAdapter(Abstract.class, deserializer);
+ }
+ if (registerAbstractHierarchyDeserializer) {
+ builder.registerTypeHierarchyAdapter(Abstract.class, deserializer);
+ }
+ Gson gson = builder.create();
+ assertEquals(expected, gson.toJson(instance, instanceType));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java
new file mode 100644
index 00000000..86f7a622
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/InnerClassExclusionStrategyTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.Excluder;
+import java.lang.reflect.Field;
+import junit.framework.TestCase;
+
+/**
+ * Unit test for GsonBuilder.EXCLUDE_INNER_CLASSES.
+ *
+ * @author Joel Leitch
+ */
+public class InnerClassExclusionStrategyTest extends TestCase {
+ public InnerClass innerClass = new InnerClass();
+ public StaticNestedClass staticNestedClass = new StaticNestedClass();
+ private Excluder excluder = Excluder.DEFAULT.disableInnerClassSerialization();
+
+ public void testExcludeInnerClassObject() throws Exception {
+ Class<?> clazz = innerClass.getClass();
+ assertTrue(excluder.excludeClass(clazz, true));
+ }
+
+ public void testExcludeInnerClassField() throws Exception {
+ Field f = getClass().getField("innerClass");
+ assertTrue(excluder.excludeField(f, true));
+ }
+
+ public void testIncludeStaticNestedClassObject() throws Exception {
+ Class<?> clazz = staticNestedClass.getClass();
+ assertFalse(excluder.excludeClass(clazz, true));
+ }
+
+ public void testIncludeStaticNestedClassField() throws Exception {
+ Field f = getClass().getField("staticNestedClass");
+ assertFalse(excluder.excludeField(f, true));
+ }
+
+ class InnerClass {
+ }
+
+ static class StaticNestedClass {
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JavaSerializationTest.java b/gson/src/test/java/com/google/gson/JavaSerializationTest.java
new file mode 100644
index 00000000..fbaea19d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JavaSerializationTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2012 Google 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 com.google.gson;
+
+import com.google.gson.reflect.TypeToken;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import junit.framework.TestCase;
+
+/**
+ * Check that Gson doesn't return non-serializable data types.
+ *
+ * @author Jesse Wilson
+ */
+public final class JavaSerializationTest extends TestCase {
+ private final Gson gson = new Gson();
+
+ public void testMapIsSerializable() throws Exception {
+ Type type = new TypeToken<Map<String, Integer>>() {}.getType();
+ Map<String, Integer> map = gson.fromJson("{\"b\":1,\"c\":2,\"a\":3}", type);
+ Map<String, Integer> serialized = serializedCopy(map);
+ assertEquals(map, serialized);
+ // Also check that the iteration order is retained.
+ assertEquals(Arrays.asList("b", "c", "a"), new ArrayList<String>(serialized.keySet()));
+ }
+
+ public void testListIsSerializable() throws Exception {
+ Type type = new TypeToken<List<String>>() {}.getType();
+ List<String> list = gson.fromJson("[\"a\",\"b\",\"c\"]", type);
+ List<String> serialized = serializedCopy(list);
+ assertEquals(list, serialized);
+ }
+
+ public void testNumberIsSerializable() throws Exception {
+ Type type = new TypeToken<List<Number>>() {}.getType();
+ List<Number> list = gson.fromJson("[1,3.14,6.673e-11]", type);
+ List<Number> serialized = serializedCopy(list);
+ assertEquals(1.0, serialized.get(0).doubleValue());
+ assertEquals(3.14, serialized.get(1).doubleValue());
+ assertEquals(6.673e-11, serialized.get(2).doubleValue());
+ }
+
+ @SuppressWarnings("unchecked") // Serialization promises to return the same type.
+ private <T> T serializedCopy(T object) throws IOException, ClassNotFoundException {
+ ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(bytesOut);
+ out.writeObject(object);
+ out.close();
+ ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
+ ObjectInputStream in = new ObjectInputStream(bytesIn);
+ return (T) in.readObject();
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonArrayTest.java b/gson/src/test/java/com/google/gson/JsonArrayTest.java
new file mode 100644
index 00000000..b77d6f1b
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonArrayTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import junit.framework.TestCase;
+
+import com.google.gson.common.MoreAsserts;
+
+/**
+ * @author Jesse Wilson
+ */
+public final class JsonArrayTest extends TestCase {
+
+ public void testEqualsOnEmptyArray() {
+ MoreAsserts.assertEqualsAndHashCode(new JsonArray(), new JsonArray());
+ }
+
+ public void testEqualsNonEmptyArray() {
+ JsonArray a = new JsonArray();
+ JsonArray b = new JsonArray();
+
+ assertEquals(a, a);
+
+ a.add(new JsonObject());
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+
+ b.add(new JsonObject());
+ MoreAsserts.assertEqualsAndHashCode(a, b);
+
+ a.add(new JsonObject());
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+
+ b.add(JsonNull.INSTANCE);
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+ }
+
+ public void testRemove() {
+ JsonArray array = new JsonArray();
+ try {
+ array.remove(0);
+ fail();
+ } catch (IndexOutOfBoundsException expected) {}
+ JsonPrimitive a = new JsonPrimitive("a");
+ array.add(a);
+ assertTrue(array.remove(a));
+ assertFalse(array.contains(a));
+ array.add(a);
+ array.add(new JsonPrimitive("b"));
+ assertEquals("b", array.remove(1).getAsString());
+ assertEquals(1, array.size());
+ assertTrue(array.contains(a));
+ }
+
+ public void testSet() {
+ JsonArray array = new JsonArray();
+ try {
+ array.set(0, new JsonPrimitive(1));
+ fail();
+ } catch (IndexOutOfBoundsException expected) {}
+ JsonPrimitive a = new JsonPrimitive("a");
+ array.add(a);
+ array.set(0, new JsonPrimitive("b"));
+ assertEquals("b", array.get(0).getAsString());
+ array.set(0, null);
+ assertNull(array.get(0));
+ array.set(0, new JsonPrimitive("c"));
+ assertEquals("c", array.get(0).getAsString());
+ assertEquals(1, array.size());
+ }
+
+ public void testDeepCopy() {
+ JsonArray original = new JsonArray();
+ JsonArray firstEntry = new JsonArray();
+ original.add(firstEntry);
+
+ JsonArray copy = original.deepCopy();
+ original.add(new JsonPrimitive("y"));
+
+ assertEquals(1, copy.size());
+ firstEntry.add(new JsonPrimitive("z"));
+
+ assertEquals(1, original.get(0).getAsJsonArray().size());
+ assertEquals(0, copy.get(0).getAsJsonArray().size());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonNullTest.java b/gson/src/test/java/com/google/gson/JsonNullTest.java
new file mode 100644
index 00000000..6157e387
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonNullTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import com.google.gson.common.MoreAsserts;
+import junit.framework.TestCase;
+
+/**
+ * @author Jesse Wilson
+ */
+public final class JsonNullTest extends TestCase {
+
+ @SuppressWarnings("deprecation")
+ public void testEqualsAndHashcode() {
+ MoreAsserts.assertEqualsAndHashCode(new JsonNull(), new JsonNull());
+ MoreAsserts.assertEqualsAndHashCode(new JsonNull(), JsonNull.INSTANCE);
+ MoreAsserts.assertEqualsAndHashCode(JsonNull.INSTANCE, JsonNull.INSTANCE);
+ }
+
+ public void testDeepCopy() {
+ @SuppressWarnings("deprecation")
+ JsonNull a = new JsonNull();
+ assertSame(JsonNull.INSTANCE, a.deepCopy());
+ assertSame(JsonNull.INSTANCE, JsonNull.INSTANCE.deepCopy());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonObjectTest.java b/gson/src/test/java/com/google/gson/JsonObjectTest.java
new file mode 100644
index 00000000..9423a24d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonObjectTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.common.MoreAsserts;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for the {@link JsonObject} class.
+ *
+ * @author Joel Leitch
+ */
+public class JsonObjectTest extends TestCase {
+
+ public void testAddingAndRemovingObjectProperties() throws Exception {
+ JsonObject jsonObj = new JsonObject();
+ String propertyName = "property";
+ assertFalse(jsonObj.has(propertyName));
+ assertNull(jsonObj.get(propertyName));
+
+ JsonPrimitive value = new JsonPrimitive("blah");
+ jsonObj.add(propertyName, value);
+ assertEquals(value, jsonObj.get(propertyName));
+
+ JsonElement removedElement = jsonObj.remove(propertyName);
+ assertEquals(value, removedElement);
+ assertFalse(jsonObj.has(propertyName));
+ assertNull(jsonObj.get(propertyName));
+ }
+
+ public void testAddingNullPropertyValue() throws Exception {
+ String propertyName = "property";
+ JsonObject jsonObj = new JsonObject();
+ jsonObj.add(propertyName, null);
+
+ assertTrue(jsonObj.has(propertyName));
+
+ JsonElement jsonElement = jsonObj.get(propertyName);
+ assertNotNull(jsonElement);
+ assertTrue(jsonElement.isJsonNull());
+ }
+
+ public void testAddingNullOrEmptyPropertyName() throws Exception {
+ JsonObject jsonObj = new JsonObject();
+ try {
+ jsonObj.add(null, JsonNull.INSTANCE);
+ fail("Should not allow null property names.");
+ } catch (NullPointerException expected) { }
+
+ jsonObj.add("", JsonNull.INSTANCE);
+ jsonObj.add(" \t", JsonNull.INSTANCE);
+ }
+
+ public void testAddingBooleanProperties() throws Exception {
+ String propertyName = "property";
+ JsonObject jsonObj = new JsonObject();
+ jsonObj.addProperty(propertyName, true);
+
+ assertTrue(jsonObj.has(propertyName));
+
+ JsonElement jsonElement = jsonObj.get(propertyName);
+ assertNotNull(jsonElement);
+ assertTrue(jsonElement.getAsBoolean());
+ }
+
+ public void testAddingStringProperties() throws Exception {
+ String propertyName = "property";
+ String value = "blah";
+
+ JsonObject jsonObj = new JsonObject();
+ jsonObj.addProperty(propertyName, value);
+
+ assertTrue(jsonObj.has(propertyName));
+
+ JsonElement jsonElement = jsonObj.get(propertyName);
+ assertNotNull(jsonElement);
+ assertEquals(value, jsonElement.getAsString());
+ }
+
+ public void testAddingCharacterProperties() throws Exception {
+ String propertyName = "property";
+ char value = 'a';
+
+ JsonObject jsonObj = new JsonObject();
+ jsonObj.addProperty(propertyName, value);
+
+ assertTrue(jsonObj.has(propertyName));
+
+ JsonElement jsonElement = jsonObj.get(propertyName);
+ assertNotNull(jsonElement);
+ assertEquals(String.valueOf(value), jsonElement.getAsString());
+ assertEquals(value, jsonElement.getAsCharacter());
+ }
+
+ /**
+ * From bug report http://code.google.com/p/google-gson/issues/detail?id=182
+ */
+ public void testPropertyWithQuotes() {
+ JsonObject jsonObj = new JsonObject();
+ jsonObj.add("a\"b", new JsonPrimitive("c\"d"));
+ String json = new Gson().toJson(jsonObj);
+ assertEquals("{\"a\\\"b\":\"c\\\"d\"}", json);
+ }
+
+ /**
+ * From issue 227.
+ */
+ public void testWritePropertyWithEmptyStringName() {
+ JsonObject jsonObj = new JsonObject();
+ jsonObj.add("", new JsonPrimitive(true));
+ assertEquals("{\"\":true}", new Gson().toJson(jsonObj));
+
+ }
+
+ public void testReadPropertyWithEmptyStringName() {
+ JsonObject jsonObj = new JsonParser().parse("{\"\":true}").getAsJsonObject();
+ assertEquals(true, jsonObj.get("").getAsBoolean());
+ }
+
+ public void testEqualsOnEmptyObject() {
+ MoreAsserts.assertEqualsAndHashCode(new JsonObject(), new JsonObject());
+ }
+
+ public void testEqualsNonEmptyObject() {
+ JsonObject a = new JsonObject();
+ JsonObject b = new JsonObject();
+
+ assertEquals(a, a);
+
+ a.add("foo", new JsonObject());
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+
+ b.add("foo", new JsonObject());
+ MoreAsserts.assertEqualsAndHashCode(a, b);
+
+ a.add("bar", new JsonObject());
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+
+ b.add("bar", JsonNull.INSTANCE);
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+ }
+
+ public void testDeepCopy() {
+ JsonObject original = new JsonObject();
+ JsonArray firstEntry = new JsonArray();
+ original.add("key", firstEntry);
+
+ JsonObject copy = original.deepCopy();
+ firstEntry.add(new JsonPrimitive("z"));
+
+ assertEquals(1, original.get("key").getAsJsonArray().size());
+ assertEquals(0, copy.get("key").getAsJsonArray().size());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonParserTest.java b/gson/src/test/java/com/google/gson/JsonParserTest.java
new file mode 100644
index 00000000..7efa7fd2
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonParserTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.StringReader;
+
+import junit.framework.TestCase;
+
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.internal.Streams;
+import com.google.gson.stream.JsonReader;
+
+/**
+ * Unit test for {@link JsonParser}
+ *
+ * @author Inderjeet Singh
+ */
+public class JsonParserTest extends TestCase {
+ private JsonParser parser;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ parser = new JsonParser();
+ }
+
+ public void testParseInvalidJson() {
+ try {
+ parser.parse("[[]");
+ fail();
+ } catch (JsonSyntaxException expected) { }
+ }
+
+ public void testParseUnquotedStringArrayFails() {
+ JsonElement element = parser.parse("[a,b,c]");
+ assertEquals("a", element.getAsJsonArray().get(0).getAsString());
+ assertEquals("b", element.getAsJsonArray().get(1).getAsString());
+ assertEquals("c", element.getAsJsonArray().get(2).getAsString());
+ assertEquals(3, element.getAsJsonArray().size());
+ }
+
+ public void testParseString() {
+ String json = "{a:10,b:'c'}";
+ JsonElement e = parser.parse(json);
+ assertTrue(e.isJsonObject());
+ assertEquals(10, e.getAsJsonObject().get("a").getAsInt());
+ assertEquals("c", e.getAsJsonObject().get("b").getAsString());
+ }
+
+ public void testParseEmptyString() {
+ JsonElement e = parser.parse("\" \"");
+ assertTrue(e.isJsonPrimitive());
+ assertEquals(" ", e.getAsString());
+ }
+
+ public void testParseEmptyWhitespaceInput() {
+ JsonElement e = parser.parse(" ");
+ assertTrue(e.isJsonNull());
+ }
+
+ public void testParseUnquotedSingleWordStringFails() {
+ assertEquals("Test", parser.parse("Test").getAsString());
+ }
+
+ public void testParseUnquotedMultiWordStringFails() {
+ String unquotedSentence = "Test is a test..blah blah";
+ try {
+ parser.parse(unquotedSentence);
+ fail();
+ } catch (JsonSyntaxException expected) { }
+ }
+
+ public void testParseMixedArray() {
+ String json = "[{},13,\"stringValue\"]";
+ JsonElement e = parser.parse(json);
+ assertTrue(e.isJsonArray());
+
+ JsonArray array = e.getAsJsonArray();
+ assertEquals("{}", array.get(0).toString());
+ assertEquals(13, array.get(1).getAsInt());
+ assertEquals("stringValue", array.get(2).getAsString());
+ }
+
+ public void testParseReader() {
+ StringReader reader = new StringReader("{a:10,b:'c'}");
+ JsonElement e = parser.parse(reader);
+ assertTrue(e.isJsonObject());
+ assertEquals(10, e.getAsJsonObject().get("a").getAsInt());
+ assertEquals("c", e.getAsJsonObject().get("b").getAsString());
+ }
+
+ public void testReadWriteTwoObjects() throws Exception {
+ Gson gson = new Gson();
+ CharArrayWriter writer = new CharArrayWriter();
+ BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one");
+ writer.write(gson.toJson(expectedOne).toCharArray());
+ BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two");
+ writer.write(gson.toJson(expectedTwo).toCharArray());
+ CharArrayReader reader = new CharArrayReader(writer.toCharArray());
+
+ JsonReader parser = new JsonReader(reader);
+ parser.setLenient(true);
+ JsonElement element1 = Streams.parse(parser);
+ JsonElement element2 = Streams.parse(parser);
+ BagOfPrimitives actualOne = gson.fromJson(element1, BagOfPrimitives.class);
+ assertEquals("one", actualOne.stringValue);
+ BagOfPrimitives actualTwo = gson.fromJson(element2, BagOfPrimitives.class);
+ assertEquals("two", actualTwo.stringValue);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java b/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java
new file mode 100644
index 00000000..fa3611c9
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonPrimitiveTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.common.MoreAsserts;
+
+import junit.framework.TestCase;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * Unit test for the {@link JsonPrimitive} class.
+ *
+ * @author Joel Leitch
+ */
+public class JsonPrimitiveTest extends TestCase {
+
+ public void testBoolean() throws Exception {
+ JsonPrimitive json = new JsonPrimitive(Boolean.TRUE);
+
+ assertTrue(json.isBoolean());
+ assertTrue(json.getAsBoolean());
+
+ // Extra support for booleans
+ json = new JsonPrimitive(1);
+ assertFalse(json.getAsBoolean());
+
+ json = new JsonPrimitive("1");
+ assertFalse(json.getAsBoolean());
+
+ json = new JsonPrimitive("true");
+ assertTrue(json.getAsBoolean());
+
+ json = new JsonPrimitive("TrUe");
+ assertTrue(json.getAsBoolean());
+
+ json = new JsonPrimitive("1.3");
+ assertFalse(json.getAsBoolean());
+ }
+
+ public void testParsingStringAsBoolean() throws Exception {
+ JsonPrimitive json = new JsonPrimitive("true");
+
+ assertFalse(json.isBoolean());
+ assertTrue(json.getAsBoolean());
+ }
+
+ public void testParsingStringAsNumber() throws Exception {
+ JsonPrimitive json = new JsonPrimitive("1");
+
+ assertFalse(json.isNumber());
+ assertEquals(1D, json.getAsDouble(), 0.00001);
+ assertEquals(1F, json.getAsFloat(), 0.00001);
+ assertEquals(1, json.getAsInt());
+ assertEquals(1L, json.getAsLong());
+ assertEquals((short) 1, json.getAsShort());
+ assertEquals((byte) 1, json.getAsByte());
+ assertEquals(new BigInteger("1"), json.getAsBigInteger());
+ assertEquals(new BigDecimal("1"), json.getAsBigDecimal());
+ }
+
+ public void testStringsAndChar() throws Exception {
+ JsonPrimitive json = new JsonPrimitive("abc");
+ assertTrue(json.isString());
+ assertEquals('a', json.getAsCharacter());
+ assertEquals("abc", json.getAsString());
+
+ json = new JsonPrimitive('z');
+ assertTrue(json.isString());
+ assertEquals('z', json.getAsCharacter());
+ assertEquals("z", json.getAsString());
+ }
+
+ public void testExponential() throws Exception {
+ JsonPrimitive json = new JsonPrimitive("1E+7");
+
+ assertEquals(new BigDecimal("1E+7"), json.getAsBigDecimal());
+ assertEquals(new Double("1E+7"), json.getAsDouble(), 0.00001);
+ assertEquals(new Float("1E+7"), json.getAsDouble(), 0.00001);
+
+ try {
+ json.getAsInt();
+ fail("Integers can not handle exponents like this.");
+ } catch (NumberFormatException expected) { }
+ }
+
+ public void testByteEqualsShort() {
+ JsonPrimitive p1 = new JsonPrimitive(new Byte((byte)10));
+ JsonPrimitive p2 = new JsonPrimitive(new Short((short)10));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testByteEqualsInteger() {
+ JsonPrimitive p1 = new JsonPrimitive(new Byte((byte)10));
+ JsonPrimitive p2 = new JsonPrimitive(new Integer(10));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testByteEqualsLong() {
+ JsonPrimitive p1 = new JsonPrimitive(new Byte((byte)10));
+ JsonPrimitive p2 = new JsonPrimitive(new Long(10L));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testByteEqualsBigInteger() {
+ JsonPrimitive p1 = new JsonPrimitive(new Byte((byte)10));
+ JsonPrimitive p2 = new JsonPrimitive(new BigInteger("10"));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testShortEqualsInteger() {
+ JsonPrimitive p1 = new JsonPrimitive(new Short((short)10));
+ JsonPrimitive p2 = new JsonPrimitive(new Integer(10));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testShortEqualsLong() {
+ JsonPrimitive p1 = new JsonPrimitive(new Short((short)10));
+ JsonPrimitive p2 = new JsonPrimitive(new Long(10));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testShortEqualsBigInteger() {
+ JsonPrimitive p1 = new JsonPrimitive(new Short((short)10));
+ JsonPrimitive p2 = new JsonPrimitive(new BigInteger("10"));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testIntegerEqualsLong() {
+ JsonPrimitive p1 = new JsonPrimitive(new Integer(10));
+ JsonPrimitive p2 = new JsonPrimitive(new Long(10L));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testIntegerEqualsBigInteger() {
+ JsonPrimitive p1 = new JsonPrimitive(new Integer(10));
+ JsonPrimitive p2 = new JsonPrimitive(new BigInteger("10"));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testLongEqualsBigInteger() {
+ JsonPrimitive p1 = new JsonPrimitive(new Long(10L));
+ JsonPrimitive p2 = new JsonPrimitive(new BigInteger("10"));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testFloatEqualsDouble() {
+ JsonPrimitive p1 = new JsonPrimitive(new Float(10.25F));
+ JsonPrimitive p2 = new JsonPrimitive(new Double(10.25D));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testFloatEqualsBigDecimal() {
+ JsonPrimitive p1 = new JsonPrimitive(new Float(10.25F));
+ JsonPrimitive p2 = new JsonPrimitive(new BigDecimal("10.25"));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testDoubleEqualsBigDecimal() {
+ JsonPrimitive p1 = new JsonPrimitive(new Double(10.25D));
+ JsonPrimitive p2 = new JsonPrimitive(new BigDecimal("10.25"));
+ assertEquals(p1, p2);
+ assertEquals(p1.hashCode(), p2.hashCode());
+ }
+
+ public void testValidJsonOnToString() throws Exception {
+ JsonPrimitive json = new JsonPrimitive("Some\nEscaped\nValue");
+ assertEquals("\"Some\\nEscaped\\nValue\"", json.toString());
+
+ json = new JsonPrimitive(new BigDecimal("1.333"));
+ assertEquals("1.333", json.toString());
+ }
+
+ public void testEquals() {
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive("A"), new JsonPrimitive("A"));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(true), new JsonPrimitive(true));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(5L), new JsonPrimitive(5L));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive('a'), new JsonPrimitive('a'));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Float.NaN), new JsonPrimitive(Float.NaN));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Float.NEGATIVE_INFINITY),
+ new JsonPrimitive(Float.NEGATIVE_INFINITY));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Float.POSITIVE_INFINITY),
+ new JsonPrimitive(Float.POSITIVE_INFINITY));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Double.NaN), new JsonPrimitive(Double.NaN));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Double.NEGATIVE_INFINITY),
+ new JsonPrimitive(Double.NEGATIVE_INFINITY));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Double.POSITIVE_INFINITY),
+ new JsonPrimitive(Double.POSITIVE_INFINITY));
+ assertFalse(new JsonPrimitive("a").equals(new JsonPrimitive("b")));
+ assertFalse(new JsonPrimitive(true).equals(new JsonPrimitive(false)));
+ assertFalse(new JsonPrimitive(0).equals(new JsonPrimitive(1)));
+ }
+
+ public void testEqualsAcrossTypes() {
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive("a"), new JsonPrimitive('a'));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(new BigInteger("0")), new JsonPrimitive(0));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(0), new JsonPrimitive(0L));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(new BigInteger("0")), new JsonPrimitive(0));
+ MoreAsserts.assertEqualsAndHashCode(new JsonPrimitive(Float.NaN), new JsonPrimitive(Double.NaN));
+ }
+
+ public void testEqualsIntegerAndBigInteger() {
+ JsonPrimitive a = new JsonPrimitive(5L);
+ JsonPrimitive b = new JsonPrimitive(new BigInteger("18446744073709551621")); // 2^64 + 5
+ // Ideally, the following assertion should have failed but the price is too much to pay
+ // assertFalse(a + " equals " + b, a.equals(b));
+ assertTrue(a + " equals " + b, a.equals(b));
+ }
+
+ public void testEqualsDoesNotEquateStringAndNonStringTypes() {
+ assertFalse(new JsonPrimitive("true").equals(new JsonPrimitive(true)));
+ assertFalse(new JsonPrimitive("0").equals(new JsonPrimitive(0)));
+ assertFalse(new JsonPrimitive("NaN").equals(new JsonPrimitive(Float.NaN)));
+ }
+
+ public void testDeepCopy() {
+ JsonPrimitive a = new JsonPrimitive("a");
+ assertSame(a, a.deepCopy()); // Primitives are immutable!
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/JsonStreamParserTest.java b/gson/src/test/java/com/google/gson/JsonStreamParserTest.java
new file mode 100644
index 00000000..1b40b58b
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/JsonStreamParserTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import junit.framework.TestCase;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Unit tests for {@link JsonStreamParser}
+ *
+ * @author Inderjeet Singh
+ */
+public class JsonStreamParserTest extends TestCase {
+ private JsonStreamParser parser;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ parser = new JsonStreamParser("'one' 'two'");
+ }
+
+ public void testParseTwoStrings() {
+ String actualOne = parser.next().getAsString();
+ assertEquals("one", actualOne);
+ String actualTwo = parser.next().getAsString();
+ assertEquals("two", actualTwo);
+ }
+
+ public void testIterator() {
+ assertTrue(parser.hasNext());
+ assertEquals("one", parser.next().getAsString());
+ assertTrue(parser.hasNext());
+ assertEquals("two", parser.next().getAsString());
+ assertFalse(parser.hasNext());
+ }
+
+ public void testNoSideEffectForHasNext() throws Exception {
+ assertTrue(parser.hasNext());
+ assertTrue(parser.hasNext());
+ assertTrue(parser.hasNext());
+ assertEquals("one", parser.next().getAsString());
+
+ assertTrue(parser.hasNext());
+ assertTrue(parser.hasNext());
+ assertEquals("two", parser.next().getAsString());
+
+ assertFalse(parser.hasNext());
+ assertFalse(parser.hasNext());
+ }
+
+ public void testCallingNextBeyondAvailableInput() {
+ parser.next();
+ parser.next();
+ try {
+ parser.next();
+ fail("Parser should not go beyond available input");
+ } catch (NoSuchElementException expected) {
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java b/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java
new file mode 100644
index 00000000..d0a06320
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/LongSerializationPolicyTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit test for the {@link LongSerializationPolicy} class.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class LongSerializationPolicyTest extends TestCase {
+
+ public void testDefaultLongSerialization() throws Exception {
+ JsonElement element = LongSerializationPolicy.DEFAULT.serialize(1556L);
+ assertTrue(element.isJsonPrimitive());
+
+ JsonPrimitive jsonPrimitive = element.getAsJsonPrimitive();
+ assertFalse(jsonPrimitive.isString());
+ assertTrue(jsonPrimitive.isNumber());
+ assertEquals(1556L, element.getAsLong());
+ }
+
+ public void testDefaultLongSerializationIntegration() {
+ Gson gson = new GsonBuilder()
+ .setLongSerializationPolicy(LongSerializationPolicy.DEFAULT)
+ .create();
+ assertEquals("[1]", gson.toJson(new long[] { 1L }, long[].class));
+ assertEquals("[1]", gson.toJson(new Long[] { 1L }, Long[].class));
+ }
+
+ public void testStringLongSerialization() throws Exception {
+ JsonElement element = LongSerializationPolicy.STRING.serialize(1556L);
+ assertTrue(element.isJsonPrimitive());
+
+ JsonPrimitive jsonPrimitive = element.getAsJsonPrimitive();
+ assertFalse(jsonPrimitive.isNumber());
+ assertTrue(jsonPrimitive.isString());
+ assertEquals("1556", element.getAsString());
+ }
+
+ public void testStringLongSerializationIntegration() {
+ Gson gson = new GsonBuilder()
+ .setLongSerializationPolicy(LongSerializationPolicy.STRING)
+ .create();
+ assertEquals("[\"1\"]", gson.toJson(new long[] { 1L }, long[].class));
+ assertEquals("[\"1\"]", gson.toJson(new Long[] { 1L }, Long[].class));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/MixedStreamTest.java b/gson/src/test/java/com/google/gson/MixedStreamTest.java
new file mode 100644
index 00000000..00eb4bc8
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/MixedStreamTest.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson;
+
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+
+public final class MixedStreamTest extends TestCase {
+
+ private static final Car BLUE_MUSTANG = new Car("mustang", 0x0000FF);
+ private static final Car BLACK_BMW = new Car("bmw", 0x000000);
+ private static final Car RED_MIATA = new Car("miata", 0xFF0000);
+ private static final String CARS_JSON = "[\n"
+ + " {\n"
+ + " \"name\": \"mustang\",\n"
+ + " \"color\": 255\n"
+ + " },\n"
+ + " {\n"
+ + " \"name\": \"bmw\",\n"
+ + " \"color\": 0\n"
+ + " },\n"
+ + " {\n"
+ + " \"name\": \"miata\",\n"
+ + " \"color\": 16711680\n"
+ + " }\n"
+ + "]";
+
+ public void testWriteMixedStreamed() throws IOException {
+ Gson gson = new Gson();
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+
+ jsonWriter.beginArray();
+ jsonWriter.setIndent(" ");
+ gson.toJson(BLUE_MUSTANG, Car.class, jsonWriter);
+ gson.toJson(BLACK_BMW, Car.class, jsonWriter);
+ gson.toJson(RED_MIATA, Car.class, jsonWriter);
+ jsonWriter.endArray();
+
+ assertEquals(CARS_JSON, stringWriter.toString());
+ }
+
+ public void testReadMixedStreamed() throws IOException {
+ Gson gson = new Gson();
+ StringReader stringReader = new StringReader(CARS_JSON);
+ JsonReader jsonReader = new JsonReader(stringReader);
+
+ jsonReader.beginArray();
+ assertEquals(BLUE_MUSTANG, gson.fromJson(jsonReader, Car.class));
+ assertEquals(BLACK_BMW, gson.fromJson(jsonReader, Car.class));
+ assertEquals(RED_MIATA, gson.fromJson(jsonReader, Car.class));
+ jsonReader.endArray();
+ }
+
+ public void testReaderDoesNotMutateState() throws IOException {
+ Gson gson = new Gson();
+ JsonReader jsonReader = new JsonReader(new StringReader(CARS_JSON));
+ jsonReader.beginArray();
+
+ jsonReader.setLenient(false);
+ gson.fromJson(jsonReader, Car.class);
+ assertFalse(jsonReader.isLenient());
+
+ jsonReader.setLenient(true);
+ gson.fromJson(jsonReader, Car.class);
+ assertTrue(jsonReader.isLenient());
+ }
+
+ public void testWriteDoesNotMutateState() throws IOException {
+ Gson gson = new Gson();
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ jsonWriter.beginArray();
+
+ jsonWriter.setHtmlSafe(true);
+ jsonWriter.setLenient(true);
+ gson.toJson(BLUE_MUSTANG, Car.class, jsonWriter);
+ assertTrue(jsonWriter.isHtmlSafe());
+ assertTrue(jsonWriter.isLenient());
+
+ jsonWriter.setHtmlSafe(false);
+ jsonWriter.setLenient(false);
+ gson.toJson(BLUE_MUSTANG, Car.class, jsonWriter);
+ assertFalse(jsonWriter.isHtmlSafe());
+ assertFalse(jsonWriter.isLenient());
+ }
+
+ public void testReadInvalidState() throws IOException {
+ Gson gson = new Gson();
+ JsonReader jsonReader = new JsonReader(new StringReader(CARS_JSON));
+ jsonReader.beginArray();
+ jsonReader.beginObject();
+ try {
+ gson.fromJson(jsonReader, String.class);
+ fail();
+ } catch (JsonParseException expected) {
+ }
+ }
+
+ public void testReadClosed() throws IOException {
+ Gson gson = new Gson();
+ JsonReader jsonReader = new JsonReader(new StringReader(CARS_JSON));
+ jsonReader.close();
+ try {
+ gson.fromJson(jsonReader, new TypeToken<List<Car>>() {}.getType());
+ fail();
+ } catch (JsonParseException expected) {
+ }
+ }
+
+ public void testWriteInvalidState() throws IOException {
+ Gson gson = new Gson();
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ jsonWriter.beginObject();
+ try {
+ gson.toJson(BLUE_MUSTANG, Car.class, jsonWriter);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testWriteClosed() throws IOException {
+ Gson gson = new Gson();
+ JsonWriter jsonWriter = new JsonWriter(new StringWriter());
+ jsonWriter.beginArray();
+ jsonWriter.endArray();
+ jsonWriter.close();
+ try {
+ gson.toJson(BLUE_MUSTANG, Car.class, jsonWriter);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testWriteNulls() {
+ Gson gson = new Gson();
+ try {
+ gson.toJson(new JsonPrimitive("hello"), (JsonWriter) null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+
+ StringWriter stringWriter = new StringWriter();
+ gson.toJson(null, new JsonWriter(stringWriter));
+ assertEquals("null", stringWriter.toString());
+ }
+
+ public void testReadNulls() {
+ Gson gson = new Gson();
+ try {
+ gson.fromJson((JsonReader) null, Integer.class);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ try {
+ gson.fromJson(new JsonReader(new StringReader("true")), null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testWriteHtmlSafe() {
+ List<String> contents = Arrays.asList("<", ">", "&", "=", "'");
+ Type type = new TypeToken<List<String>>() {}.getType();
+
+ StringWriter writer = new StringWriter();
+ new Gson().toJson(contents, type, new JsonWriter(writer));
+ assertEquals("[\"\\u003c\",\"\\u003e\",\"\\u0026\",\"\\u003d\",\"\\u0027\"]",
+ writer.toString());
+
+ writer = new StringWriter();
+ new GsonBuilder().disableHtmlEscaping().create()
+ .toJson(contents, type, new JsonWriter(writer));
+ assertEquals("[\"<\",\">\",\"&\",\"=\",\"'\"]",
+ writer.toString());
+ }
+
+ public void testWriteLenient() {
+ List<Double> doubles = Arrays.asList(Double.NaN, Double.NEGATIVE_INFINITY,
+ Double.POSITIVE_INFINITY, -0.0d, 0.5d, 0.0d);
+ Type type = new TypeToken<List<Double>>() {}.getType();
+
+ StringWriter writer = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(writer);
+ new GsonBuilder().serializeSpecialFloatingPointValues().create()
+ .toJson(doubles, type, jsonWriter);
+ assertEquals("[NaN,-Infinity,Infinity,-0.0,0.5,0.0]", writer.toString());
+
+ try {
+ new Gson().toJson(doubles, type, new JsonWriter(new StringWriter()));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ static final class Car {
+ String name;
+ int color;
+
+ Car(String name, int color) {
+ this.name = name;
+ this.color = color;
+ }
+
+ // used by Gson
+ Car() {}
+
+ @Override public int hashCode() {
+ return name.hashCode() ^ color;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Car
+ && ((Car) o).name.equals(name)
+ && ((Car) o).color == color;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/MockExclusionStrategy.java b/gson/src/test/java/com/google/gson/MockExclusionStrategy.java
new file mode 100644
index 00000000..2e5db94d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/MockExclusionStrategy.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+/**
+ * This is a configurable {@link ExclusionStrategy} that can be used for
+ * unit testing.
+ *
+ * @author Joel Leitch
+ */
+final class MockExclusionStrategy implements ExclusionStrategy {
+ private final boolean skipClass;
+ private final boolean skipField;
+
+ public MockExclusionStrategy(boolean skipClass, boolean skipField) {
+ this.skipClass = skipClass;
+ this.skipField = skipField;
+ }
+
+ public boolean shouldSkipField(FieldAttributes f) {
+ return skipField;
+ }
+
+ public boolean shouldSkipClass(Class<?> clazz) {
+ return skipClass;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/ObjectTypeAdapterTest.java b/gson/src/test/java/com/google/gson/ObjectTypeAdapterTest.java
new file mode 100644
index 00000000..2891bffc
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/ObjectTypeAdapterTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import junit.framework.TestCase;
+
+public final class ObjectTypeAdapterTest extends TestCase {
+ private final Gson gson = new GsonBuilder().create();
+ private final TypeAdapter<Object> adapter = gson.getAdapter(Object.class);
+
+ public void testDeserialize() throws Exception {
+ Map<?, ?> map = (Map<?, ?>) adapter.fromJson("{\"a\":5,\"b\":[1,2,null],\"c\":{\"x\":\"y\"}}");
+ assertEquals(5.0, map.get("a"));
+ assertEquals(Arrays.asList(1.0, 2.0, null), map.get("b"));
+ assertEquals(Collections.singletonMap("x", "y"), map.get("c"));
+ assertEquals(3, map.size());
+ }
+
+ public void testSerialize() throws Exception {
+ Object object = new RuntimeType();
+ assertEquals("{'a':5,'b':[1,2,null]}", adapter.toJson(object).replace("\"", "'"));
+ }
+
+ public void testSerializeNullValue() throws Exception {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
+ map.put("a", null);
+ assertEquals("{'a':null}", adapter.toJson(map).replace('"', '\''));
+ }
+
+ public void testDeserializeNullValue() throws Exception {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
+ map.put("a", null);
+ assertEquals(map, adapter.fromJson("{\"a\":null}"));
+ }
+
+ public void testSerializeObject() throws Exception {
+ assertEquals("{}", adapter.toJson(new Object()));
+ }
+
+ @SuppressWarnings("unused")
+ private class RuntimeType {
+ Object a = 5;
+ Object b = Arrays.asList(1, 2, null);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java
new file mode 100644
index 00000000..79ae1698
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/OverrideCoreTypeAdaptersTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2012 Google 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 com.google.gson;
+
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.util.Locale;
+import junit.framework.TestCase;
+
+/**
+ * @author Jesse Wilson
+ */
+public class OverrideCoreTypeAdaptersTest extends TestCase {
+ private static final TypeAdapter<Boolean> booleanAsIntAdapter = new TypeAdapter<Boolean>() {
+ @Override public void write(JsonWriter out, Boolean value) throws IOException {
+ out.value(value ? 1 : 0);
+ }
+ @Override public Boolean read(JsonReader in) throws IOException {
+ int value = in.nextInt();
+ return value != 0;
+ }
+ };
+
+ private static final TypeAdapter<String> swapCaseStringAdapter = new TypeAdapter<String>() {
+ @Override public void write(JsonWriter out, String value) throws IOException {
+ out.value(value.toUpperCase(Locale.US));
+ }
+ @Override public String read(JsonReader in) throws IOException {
+ return in.nextString().toLowerCase(Locale.US);
+ }
+ };
+
+ public void testOverrideWrapperBooleanAdapter() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Boolean.class, booleanAsIntAdapter)
+ .create();
+ assertEquals("true", gson.toJson(true, boolean.class));
+ assertEquals("1", gson.toJson(true, Boolean.class));
+ assertEquals(Boolean.TRUE, gson.fromJson("true", boolean.class));
+ assertEquals(Boolean.TRUE, gson.fromJson("1", Boolean.class));
+ assertEquals(Boolean.FALSE, gson.fromJson("0", Boolean.class));
+ }
+
+ public void testOverridePrimitiveBooleanAdapter() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(boolean.class, booleanAsIntAdapter)
+ .create();
+ assertEquals("1", gson.toJson(true, boolean.class));
+ assertEquals("true", gson.toJson(true, Boolean.class));
+ assertEquals(Boolean.TRUE, gson.fromJson("1", boolean.class));
+ assertEquals(Boolean.TRUE, gson.fromJson("true", Boolean.class));
+ assertEquals("0", gson.toJson(false, boolean.class));
+ }
+
+ public void testOverrideStringAdapter() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(String.class, swapCaseStringAdapter)
+ .create();
+ assertEquals("\"HELLO\"", gson.toJson("Hello", String.class));
+ assertEquals("hello", gson.fromJson("\"Hello\"", String.class));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/ParameterizedTypeFixtures.java b/gson/src/test/java/com/google/gson/ParameterizedTypeFixtures.java
new file mode 100644
index 00000000..6bae432f
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/ParameterizedTypeFixtures.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.$Gson$Types;
+
+import com.google.gson.internal.Primitives;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+
+/**
+ * This class contains some test fixtures for Parameterized types. These classes should ideally
+ * belong either in the common or functional package, but they are placed here because they need
+ * access to package protected elements of com.google.gson.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ParameterizedTypeFixtures {
+
+ public static class MyParameterizedType<T> {
+ public final T value;
+ public MyParameterizedType(T value) {
+ this.value = value;
+ }
+ public T getValue() {
+ return value;
+ }
+
+ public String getExpectedJson() {
+ String valueAsJson = getExpectedJson(value);
+ return String.format("{\"value\":%s}", valueAsJson);
+ }
+
+ private String getExpectedJson(Object obj) {
+ Class<?> clazz = obj.getClass();
+ if (Primitives.isWrapperType(Primitives.wrap(clazz))) {
+ return obj.toString();
+ } else if (obj.getClass().equals(String.class)) {
+ return "\"" + obj.toString() + "\"";
+ } else {
+ // Try invoking a getExpectedJson() method if it exists
+ try {
+ Method method = clazz.getMethod("getExpectedJson");
+ Object results = method.invoke(obj);
+ return (String) results;
+ } catch (SecurityException e) {
+ throw new RuntimeException(e);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalArgumentException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return value == null ? 0 : value.hashCode();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MyParameterizedType<T> other = (MyParameterizedType<T>) obj;
+ if (value == null) {
+ if (other.value != null) {
+ return false;
+ }
+ } else if (!value.equals(other.value)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ public static class MyParameterizedTypeInstanceCreator<T>
+ implements InstanceCreator<MyParameterizedType<T>>{
+ private final T instanceOfT;
+ /**
+ * Caution the specified instance is reused by the instance creator for each call.
+ * This means that the fields of the same objects will be overwritten by Gson.
+ * This is usually fine in tests since there we deserialize just once, but quite
+ * dangerous in practice.
+ *
+ * @param instanceOfT
+ */
+ public MyParameterizedTypeInstanceCreator(T instanceOfT) {
+ this.instanceOfT = instanceOfT;
+ }
+ public MyParameterizedType<T> createInstance(Type type) {
+ return new MyParameterizedType<T>(instanceOfT);
+ }
+ }
+
+ public static class MyParameterizedTypeAdapter<T>
+ implements JsonSerializer<MyParameterizedType<T>>, JsonDeserializer<MyParameterizedType<T>> {
+ @SuppressWarnings("unchecked")
+ public static<T> String getExpectedJson(MyParameterizedType<T> obj) {
+ Class<T> clazz = (Class<T>) obj.value.getClass();
+ boolean addQuotes = !clazz.isArray() && !Primitives.unwrap(clazz).isPrimitive();
+ StringBuilder sb = new StringBuilder("{\"");
+ sb.append(obj.value.getClass().getSimpleName()).append("\":");
+ if (addQuotes) {
+ sb.append("\"");
+ }
+ sb.append(obj.value.toString());
+ if (addQuotes) {
+ sb.append("\"");
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ public JsonElement serialize(MyParameterizedType<T> src, Type classOfSrc,
+ JsonSerializationContext context) {
+ JsonObject json = new JsonObject();
+ T value = src.getValue();
+ json.add(value.getClass().getSimpleName(), context.serialize(value));
+ return json;
+ }
+
+ @SuppressWarnings("unchecked")
+ public MyParameterizedType<T> deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ Type genericClass = ((ParameterizedType) typeOfT).getActualTypeArguments()[0];
+ Class<?> rawType = $Gson$Types.getRawType(genericClass);
+ String className = rawType.getSimpleName();
+ JsonElement jsonElement = json.getAsJsonObject().get(className);
+
+ T value;
+ if (genericClass == Integer.class) {
+ value = (T) Integer.valueOf(jsonElement.getAsInt());
+ } else if (genericClass == String.class) {
+ value = (T) jsonElement.getAsString();
+ } else {
+ value = (T) jsonElement;
+ }
+
+ if (Primitives.isPrimitive(genericClass)) {
+ PrimitiveTypeAdapter typeAdapter = new PrimitiveTypeAdapter();
+ value = (T) typeAdapter.adaptType(value, rawType);
+ }
+ return new MyParameterizedType<T>(value);
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/ParameterizedTypeTest.java b/gson/src/test/java/com/google/gson/ParameterizedTypeTest.java
new file mode 100644
index 00000000..8b56579e
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/ParameterizedTypeTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Unit tests for {@code ParamterizedType}s created by the {@link $Gson$Types} class.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ParameterizedTypeTest extends TestCase {
+ private ParameterizedType ourType;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ ourType = $Gson$Types.newParameterizedTypeWithOwner(null, List.class, String.class);
+ }
+
+ public void testOurTypeFunctionality() throws Exception {
+ Type parameterizedType = new TypeToken<List<String>>() {}.getType();
+ assertNull(ourType.getOwnerType());
+ assertEquals(String.class, ourType.getActualTypeArguments()[0]);
+ assertEquals(List.class, ourType.getRawType());
+ assertEquals(parameterizedType, ourType);
+ assertEquals(parameterizedType.hashCode(), ourType.hashCode());
+ }
+
+ public void testNotEquals() throws Exception {
+ Type differentParameterizedType = new TypeToken<List<Integer>>() {}.getType();
+ assertFalse(differentParameterizedType.equals(ourType));
+ assertFalse(ourType.equals(differentParameterizedType));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/PrimitiveTypeAdapter.java b/gson/src/test/java/com/google/gson/PrimitiveTypeAdapter.java
new file mode 100644
index 00000000..fb38687b
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/PrimitiveTypeAdapter.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.internal.Primitives;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Handles type conversion from some object to some primitive (or primitive
+ * wrapper instance).
+ *
+ * @author Joel Leitch
+ */
+final class PrimitiveTypeAdapter {
+
+ @SuppressWarnings("unchecked")
+ public <T> T adaptType(Object from, Class<T> to) {
+ Class<?> aClass = Primitives.wrap(to);
+ if (Primitives.isWrapperType(aClass)) {
+ if (aClass == Character.class) {
+ String value = from.toString();
+ if (value.length() == 1) {
+ return (T) (Character) from.toString().charAt(0);
+ }
+ throw new JsonParseException("The value: " + value + " contains more than a character.");
+ }
+
+ try {
+ Constructor<?> constructor = aClass.getConstructor(String.class);
+ return (T) constructor.newInstance(from.toString());
+ } catch (NoSuchMethodException e) {
+ throw new JsonParseException(e);
+ } catch (IllegalAccessException e) {
+ throw new JsonParseException(e);
+ } catch (InvocationTargetException e) {
+ throw new JsonParseException(e);
+ } catch (InstantiationException e) {
+ throw new JsonParseException(e);
+ }
+ } else if (Enum.class.isAssignableFrom(to)) {
+ // Case where the type being adapted to is an Enum
+ // We will try to convert from.toString() to the enum
+ try {
+ Method valuesMethod = to.getMethod("valueOf", String.class);
+ return (T) valuesMethod.invoke(null, from.toString());
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new JsonParseException("Can not adapt type " + from.getClass() + " to " + to);
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java b/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java
new file mode 100644
index 00000000..d878850e
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/VersionExclusionStrategyTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson;
+
+import com.google.gson.annotations.Since;
+import com.google.gson.internal.Excluder;
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the {@link Excluder} class.
+ *
+ * @author Joel Leitch
+ */
+public class VersionExclusionStrategyTest extends TestCase {
+ private static final double VERSION = 5.0D;
+
+ public void testClassAndFieldAreAtSameVersion() throws Exception {
+ Excluder excluder = Excluder.DEFAULT.withVersion(VERSION);
+ assertFalse(excluder.excludeClass(MockObject.class, true));
+ assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true));
+ }
+
+ public void testClassAndFieldAreBehindInVersion() throws Exception {
+ Excluder excluder = Excluder.DEFAULT.withVersion(VERSION + 1);
+ assertFalse(excluder.excludeClass(MockObject.class, true));
+ assertFalse(excluder.excludeField(MockObject.class.getField("someField"), true));
+ }
+
+ public void testClassAndFieldAreAheadInVersion() throws Exception {
+ Excluder excluder = Excluder.DEFAULT.withVersion(VERSION - 1);
+ assertTrue(excluder.excludeClass(MockObject.class, true));
+ assertTrue(excluder.excludeField(MockObject.class.getField("someField"), true));
+ }
+
+ @Since(VERSION)
+ private static class MockObject {
+
+ @Since(VERSION)
+ public final int someField = 0;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/common/MoreAsserts.java b/gson/src/test/java/com/google/gson/common/MoreAsserts.java
new file mode 100644
index 00000000..5e05832a
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/common/MoreAsserts.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.common;
+
+import junit.framework.Assert;
+
+import java.util.Collection;
+
+/**
+ * Handy asserts that we wish were present in {@link Assert}
+ * so that we didn't have to write them.
+ *
+ * @author Inderjeet Singh
+ */
+public class MoreAsserts {
+
+ public static void assertEquals(int[] expected, int[] target) {
+ if (expected == null) {
+ Assert.assertNull(target);
+ }
+ Assert.assertEquals(expected.length, target.length);
+ for (int i = 0; i < expected.length; ++i) {
+ Assert.assertEquals(expected[i], target[i]);
+ }
+ }
+
+ public static void assertEquals(Integer[] expected, Integer[] target) {
+ if (expected == null) {
+ Assert.assertNull(target);
+ }
+ Assert.assertEquals(expected.length, target.length);
+ for (int i = 0; i < expected.length; ++i) {
+ Assert.assertEquals(expected[i], target[i]);
+ }
+ }
+
+ /**
+ * Asserts that the specified {@code value} is not present in {@code collection}
+ * @param collection the collection to look into
+ * @param value the value that needs to be checked for presence
+ */
+ public static <T> void assertContains(Collection<T> collection, T value) {
+ for (T entry : collection) {
+ if (entry.equals(value)) {
+ return;
+ }
+ }
+ Assert.fail(value + " not present in " + collection);
+ }
+
+ public static void assertEqualsAndHashCode(Object a, Object b) {
+ Assert.assertTrue(a.equals(b));
+ Assert.assertTrue(b.equals(a));
+ Assert.assertEquals(a.hashCode(), b.hashCode());
+ Assert.assertFalse(a.equals(null));
+ Assert.assertFalse(a.equals(new Object()));
+ }
+
+}
diff --git a/gson/src/test/java/com/google/gson/common/TestTypes.java b/gson/src/test/java/com/google/gson/common/TestTypes.java
new file mode 100644
index 00000000..4754d97b
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/common/TestTypes.java
@@ -0,0 +1,418 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.common;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * Types used for testing JSON serialization and deserialization
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class TestTypes {
+
+ public static class Base {
+ public static final String BASE_NAME = Base.class.getSimpleName();
+ public static final String BASE_FIELD_KEY = "baseName";
+ public static final String SERIALIZER_KEY = "serializerName";
+ public String baseName = BASE_NAME;
+ public String serializerName;
+ }
+
+ public static class Sub extends Base {
+ public static final String SUB_NAME = Sub.class.getSimpleName();
+ public static final String SUB_FIELD_KEY = "subName";
+ public final String subName = SUB_NAME;
+ }
+
+ public static class ClassWithBaseField {
+ public static final String FIELD_KEY = "base";
+ public final Base base;
+ public ClassWithBaseField(Base base) {
+ this.base = base;
+ }
+ }
+
+ public static class ClassWithBaseArrayField {
+ public static final String FIELD_KEY = "base";
+ public final Base[] base;
+ public ClassWithBaseArrayField(Base[] base) {
+ this.base = base;
+ }
+ }
+
+ public static class ClassWithBaseCollectionField {
+ public static final String FIELD_KEY = "base";
+ public final Collection<Base> base;
+ public ClassWithBaseCollectionField(Collection<Base> base) {
+ this.base = base;
+ }
+ }
+
+ public static class BaseSerializer implements JsonSerializer<Base> {
+ public static final String NAME = BaseSerializer.class.getSimpleName();
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty(Base.SERIALIZER_KEY, NAME);
+ return obj;
+ }
+ }
+ public static class SubSerializer implements JsonSerializer<Sub> {
+ public static final String NAME = SubSerializer.class.getSimpleName();
+ public JsonElement serialize(Sub src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty(Base.SERIALIZER_KEY, NAME);
+ return obj;
+ }
+ }
+
+ public static class StringWrapper {
+ public final String someConstantStringInstanceField;
+
+ public StringWrapper(String value) {
+ someConstantStringInstanceField = value;
+ }
+ }
+
+ public static class BagOfPrimitives {
+ public static final long DEFAULT_VALUE = 0;
+ public long longValue;
+ public int intValue;
+ public boolean booleanValue;
+ public String stringValue;
+
+ public BagOfPrimitives() {
+ this(DEFAULT_VALUE, 0, false, "");
+ }
+
+ public BagOfPrimitives(long longValue, int intValue, boolean booleanValue, String stringValue) {
+ this.longValue = longValue;
+ this.intValue = intValue;
+ this.booleanValue = booleanValue;
+ this.stringValue = stringValue;
+ }
+
+ public int getIntValue() {
+ return intValue;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("\"longValue\":").append(longValue).append(",");
+ sb.append("\"intValue\":").append(intValue).append(",");
+ sb.append("\"booleanValue\":").append(booleanValue).append(",");
+ sb.append("\"stringValue\":\"").append(stringValue).append("\"");
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (booleanValue ? 1231 : 1237);
+ result = prime * result + intValue;
+ result = prime * result + (int) (longValue ^ (longValue >>> 32));
+ result = prime * result + ((stringValue == null) ? 0 : stringValue.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ BagOfPrimitives other = (BagOfPrimitives) obj;
+ if (booleanValue != other.booleanValue)
+ return false;
+ if (intValue != other.intValue)
+ return false;
+ if (longValue != other.longValue)
+ return false;
+ if (stringValue == null) {
+ if (other.stringValue != null)
+ return false;
+ } else if (!stringValue.equals(other.stringValue))
+ return false;
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("(longValue=%d,intValue=%d,booleanValue=%b,stringValue=%s)",
+ longValue, intValue, booleanValue, stringValue);
+ }
+ }
+
+ public static class BagOfPrimitiveWrappers {
+ private final Long longValue;
+ private final Integer intValue;
+ private final Boolean booleanValue;
+
+ public BagOfPrimitiveWrappers(Long longValue, Integer intValue, Boolean booleanValue) {
+ this.longValue = longValue;
+ this.intValue = intValue;
+ this.booleanValue = booleanValue;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("\"longValue\":").append(longValue).append(",");
+ sb.append("\"intValue\":").append(intValue).append(",");
+ sb.append("\"booleanValue\":").append(booleanValue);
+ sb.append("}");
+ return sb.toString();
+ }
+ }
+
+ public static class PrimitiveArray {
+ private final long[] longArray;
+
+ public PrimitiveArray() {
+ this(new long[0]);
+ }
+
+ public PrimitiveArray(long[] longArray) {
+ this.longArray = longArray;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{\"longArray\":[");
+
+ boolean first = true;
+ for (long l : longArray) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ sb.append(l);
+ }
+
+ sb.append("]}");
+ return sb.toString();
+ }
+ }
+
+ public static class ClassWithNoFields {
+ // Nothing here.. .
+ @Override
+ public boolean equals(Object other) {
+ return other.getClass() == ClassWithNoFields.class;
+ }
+ }
+
+ public static class Nested {
+ private final BagOfPrimitives primitive1;
+ private final BagOfPrimitives primitive2;
+
+ public Nested() {
+ this(null, null);
+ }
+
+ public Nested(BagOfPrimitives primitive1, BagOfPrimitives primitive2) {
+ this.primitive1 = primitive1;
+ this.primitive2 = primitive2;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ appendFields(sb);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ public void appendFields(StringBuilder sb) {
+ if (primitive1 != null) {
+ sb.append("\"primitive1\":").append(primitive1.getExpectedJson());
+ }
+ if (primitive1 != null && primitive2 != null) {
+ sb.append(",");
+ }
+ if (primitive2 != null) {
+ sb.append("\"primitive2\":").append(primitive2.getExpectedJson());
+ }
+ }
+ }
+
+ public static class ClassWithTransientFields<T> {
+ public transient T transientT;
+ public final transient long transientLongValue;
+ private final long[] longValue;
+
+ public ClassWithTransientFields() {
+ this(0L);
+ }
+
+ public ClassWithTransientFields(long value) {
+ longValue = new long[] { value };
+ transientLongValue = value + 1;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("\"longValue\":[").append(longValue[0]).append("]");
+ sb.append("}");
+ return sb.toString();
+ }
+ }
+
+ public static class ClassWithCustomTypeConverter {
+ private final BagOfPrimitives bag;
+ private final int value;
+
+ public ClassWithCustomTypeConverter() {
+ this(new BagOfPrimitives(), 10);
+ }
+
+ public ClassWithCustomTypeConverter(int value) {
+ this(new BagOfPrimitives(value, value, false, ""), value);
+ }
+
+ public ClassWithCustomTypeConverter(BagOfPrimitives bag, int value) {
+ this.bag = bag;
+ this.value = value;
+ }
+
+ public BagOfPrimitives getBag() {
+ return bag;
+ }
+
+ public String getExpectedJson() {
+ return "{\"url\":\"" + bag.getExpectedJson() + "\",\"value\":" + value + "}";
+ }
+
+ public int getValue() {
+ return value;
+ }
+ }
+
+ public static class ArrayOfObjects {
+ private final BagOfPrimitives[] elements;
+ public ArrayOfObjects() {
+ elements = new BagOfPrimitives[3];
+ for (int i = 0; i < elements.length; ++i) {
+ elements[i] = new BagOfPrimitives(i, i+2, false, "i"+i);
+ }
+ }
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder("{\"elements\":[");
+ boolean first = true;
+ for (BagOfPrimitives element : elements) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(element.getExpectedJson());
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+ }
+
+ public static class ClassOverridingEquals {
+ public ClassOverridingEquals ref;
+
+ public String getExpectedJson() {
+ if (ref == null) {
+ return "{}";
+ }
+ return "{\"ref\":" + ref.getExpectedJson() + "}";
+ }
+ @Override
+ public boolean equals(Object obj) {
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 1;
+ }
+ }
+
+ public static class ClassWithArray {
+ public final Object[] array;
+ public ClassWithArray() {
+ array = null;
+ }
+
+ public ClassWithArray(Object[] array) {
+ this.array = array;
+ }
+ }
+
+ public static class ClassWithObjects {
+ public final BagOfPrimitives bag;
+ public ClassWithObjects() {
+ this(new BagOfPrimitives());
+ }
+ public ClassWithObjects(BagOfPrimitives bag) {
+ this.bag = bag;
+ }
+ }
+
+ public static class ClassWithSerializedNameFields {
+ @SerializedName("fooBar") public final int f;
+ @SerializedName("Another Foo") public final int g;
+
+ public ClassWithSerializedNameFields() {
+ this(1, 4);
+ }
+ public ClassWithSerializedNameFields(int f, int g) {
+ this.f = f;
+ this.g = g;
+ }
+
+ public String getExpectedJson() {
+ return '{' + "\"fooBar\":" + f + ",\"Another Foo\":" + g + '}';
+ }
+ }
+
+ public static class CrazyLongTypeAdapter
+ implements JsonSerializer<Long>, JsonDeserializer<Long> {
+ public static final long DIFFERENCE = 5L;
+ public JsonElement serialize(Long src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src + DIFFERENCE);
+ }
+
+ public Long deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return json.getAsLong() - DIFFERENCE;
+ }
+}
+} \ No newline at end of file
diff --git a/gson/src/test/java/com/google/gson/functional/ArrayTest.java b/gson/src/test/java/com/google/gson/functional/ArrayTest.java
new file mode 100644
index 00000000..11388e90
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ArrayTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonParseException;
+import com.google.gson.common.MoreAsserts;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.ClassWithObjects;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Collection;
+/**
+ * Functional tests for Json serialization and deserialization of arrays.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ArrayTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testTopLevelArrayOfIntsSerialization() {
+ int[] target = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ assertEquals("[1,2,3,4,5,6,7,8,9]", gson.toJson(target));
+ }
+
+ public void testTopLevelArrayOfIntsDeserialization() {
+ int[] expected = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ int[] actual = gson.fromJson("[1,2,3,4,5,6,7,8,9]", int[].class);
+ MoreAsserts.assertEquals(expected, actual);
+ }
+
+ public void testInvalidArrayDeserialization() {
+ String json = "[1, 2 3, 4, 5]";
+ try {
+ gson.fromJson(json, int[].class);
+ fail("Gson should not deserialize array elements with missing ,");
+ } catch (JsonParseException expected) {
+ }
+ }
+
+ public void testEmptyArraySerialization() {
+ int[] target = {};
+ assertEquals("[]", gson.toJson(target));
+ }
+
+ public void testEmptyArrayDeserialization() {
+ int[] actualObject = gson.fromJson("[]", int[].class);
+ assertTrue(actualObject.length == 0);
+
+ Integer[] actualObject2 = gson.fromJson("[]", Integer[].class);
+ assertTrue(actualObject2.length == 0);
+
+ actualObject = gson.fromJson("[ ]", int[].class);
+ assertTrue(actualObject.length == 0);
+ }
+
+ public void testNullsInArraySerialization() {
+ String[] array = {"foo", null, "bar"};
+ String expected = "[\"foo\",null,\"bar\"]";
+ String json = gson.toJson(array);
+ assertEquals(expected, json);
+ }
+
+ public void testNullsInArrayDeserialization() {
+ String json = "[\"foo\",null,\"bar\"]";
+ String[] expected = {"foo", null, "bar"};
+ String[] target = gson.fromJson(json, expected.getClass());
+ for (int i = 0; i < expected.length; ++i) {
+ assertEquals(expected[i], target[i]);
+ }
+ }
+
+ public void testSingleNullInArraySerialization() {
+ BagOfPrimitives[] array = new BagOfPrimitives[1];
+ array[0] = null;
+ String json = gson.toJson(array);
+ assertEquals("[null]", json);
+ }
+
+ public void testSingleNullInArrayDeserialization() {
+ BagOfPrimitives[] array = gson.fromJson("[null]", BagOfPrimitives[].class);
+ assertNull(array[0]);
+ }
+
+ public void testNullsInArrayWithSerializeNullPropertySetSerialization() {
+ gson = new GsonBuilder().serializeNulls().create();
+ String[] array = {"foo", null, "bar"};
+ String expected = "[\"foo\",null,\"bar\"]";
+ String json = gson.toJson(array);
+ assertEquals(expected, json);
+ }
+
+ public void testArrayOfStringsSerialization() {
+ String[] target = {"Hello", "World"};
+ assertEquals("[\"Hello\",\"World\"]", gson.toJson(target));
+ }
+
+ public void testArrayOfStringsDeserialization() {
+ String json = "[\"Hello\",\"World\"]";
+ String[] target = gson.fromJson(json, String[].class);
+ assertEquals("Hello", target[0]);
+ assertEquals("World", target[1]);
+ }
+
+ public void testSingleStringArraySerialization() throws Exception {
+ String[] s = { "hello" };
+ String output = gson.toJson(s);
+ assertEquals("[\"hello\"]", output);
+ }
+
+ public void testSingleStringArrayDeserialization() throws Exception {
+ String json = "[\"hello\"]";
+ String[] arrayType = gson.fromJson(json, String[].class);
+ assertEquals(1, arrayType.length);
+ assertEquals("hello", arrayType[0]);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testArrayOfCollectionSerialization() throws Exception {
+ StringBuilder sb = new StringBuilder("[");
+ int arraySize = 3;
+
+ Type typeToSerialize = new TypeToken<Collection<Integer>[]>() {}.getType();
+ Collection<Integer>[] arrayOfCollection = new ArrayList[arraySize];
+ for (int i = 0; i < arraySize; ++i) {
+ int startValue = (3 * i) + 1;
+ sb.append('[').append(startValue).append(',').append(startValue + 1).append(']');
+ ArrayList<Integer> tmpList = new ArrayList<Integer>();
+ tmpList.add(startValue);
+ tmpList.add(startValue + 1);
+ arrayOfCollection[i] = tmpList;
+
+ if (i < arraySize - 1) {
+ sb.append(',');
+ }
+ }
+ sb.append(']');
+
+ String json = gson.toJson(arrayOfCollection, typeToSerialize);
+ assertEquals(sb.toString(), json);
+ }
+
+ public void testArrayOfCollectionDeserialization() throws Exception {
+ String json = "[[1,2],[3,4]]";
+ Type type = new TypeToken<Collection<Integer>[]>() {}.getType();
+ Collection<Integer>[] target = gson.fromJson(json, type);
+
+ assertEquals(2, target.length);
+ MoreAsserts.assertEquals(new Integer[] { 1, 2 }, target[0].toArray(new Integer[0]));
+ MoreAsserts.assertEquals(new Integer[] { 3, 4 }, target[1].toArray(new Integer[0]));
+ }
+
+ public void testArrayOfPrimitivesAsObjectsSerialization() throws Exception {
+ Object[] objs = new Object[] {1, "abc", 0.3f, 5L};
+ String json = gson.toJson(objs);
+ assertTrue(json.contains("abc"));
+ assertTrue(json.contains("0.3"));
+ assertTrue(json.contains("5"));
+ }
+
+ public void testArrayOfPrimitivesAsObjectsDeserialization() throws Exception {
+ String json = "[1,'abc',0.3,1.1,5]";
+ Object[] objs = gson.fromJson(json, Object[].class);
+ assertEquals(1, ((Number)objs[0]).intValue());
+ assertEquals("abc", objs[1]);
+ assertEquals(0.3, ((Number)objs[2]).doubleValue());
+ assertEquals(new BigDecimal("1.1"), new BigDecimal(objs[3].toString()));
+ assertEquals(5, ((Number)objs[4]).shortValue());
+ }
+
+ public void testObjectArrayWithNonPrimitivesSerialization() throws Exception {
+ ClassWithObjects classWithObjects = new ClassWithObjects();
+ BagOfPrimitives bagOfPrimitives = new BagOfPrimitives();
+ String classWithObjectsJson = gson.toJson(classWithObjects);
+ String bagOfPrimitivesJson = gson.toJson(bagOfPrimitives);
+
+ Object[] objects = new Object[] { classWithObjects, bagOfPrimitives };
+ String json = gson.toJson(objects);
+
+ assertTrue(json.contains(classWithObjectsJson));
+ assertTrue(json.contains(bagOfPrimitivesJson));
+ }
+
+ public void testArrayOfNullSerialization() {
+ Object[] array = new Object[] {null};
+ String json = gson.toJson(array);
+ assertEquals("[null]", json);
+ }
+
+ public void testArrayOfNullDeserialization() {
+ String[] values = gson.fromJson("[null]", String[].class);
+ assertNull(values[0]);
+ }
+
+ /**
+ * Regression tests for Issue 272
+ */
+ public void testMultidimenstionalArraysSerialization() {
+ String[][] items = new String[][]{
+ {"3m Co", "71.72", "0.02", "0.03", "4/2 12:00am", "Manufacturing"},
+ {"Alcoa Inc", "29.01", "0.42", "1.47", "4/1 12:00am", "Manufacturing"}
+ };
+ String json = gson.toJson(items);
+ assertTrue(json.contains("[[\"3m Co"));
+ assertTrue(json.contains("Manufacturing\"]]"));
+ }
+
+ public void testMultiDimenstionalObjectArraysSerialization() {
+ Object[][] array = new Object[][] { new Object[] { 1, 2 } };
+ assertEquals("[[1,2]]", gson.toJson(array));
+ }
+
+ /**
+ * Regression test for Issue 205
+ */
+ public void testMixingTypesInObjectArraySerialization() {
+ Object[] array = new Object[] { 1, 2, new Object[] { "one", "two", 3 } };
+ assertEquals("[1,2,[\"one\",\"two\",3]]", gson.toJson(array));
+ }
+
+ /**
+ * Regression tests for Issue 272
+ */
+ public void testMultidimenstionalArraysDeserialization() {
+ String json = "[['3m Co','71.72','0.02','0.03','4/2 12:00am','Manufacturing'],"
+ + "['Alcoa Inc','29.01','0.42','1.47','4/1 12:00am','Manufacturing']]";
+ String[][] items = gson.fromJson(json, String[][].class);
+ assertEquals("3m Co", items[0][0]);
+ assertEquals("Manufacturing", items[1][5]);
+ }
+
+ /** http://code.google.com/p/google-gson/issues/detail?id=342 */
+ public void testArrayElementsAreArrays() {
+ Object[] stringArrays = {
+ new String[] {"test1", "test2"},
+ new String[] {"test3", "test4"}
+ };
+ assertEquals("[[\"test1\",\"test2\"],[\"test3\",\"test4\"]]",
+ new Gson().toJson(stringArrays));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/CircularReferenceTest.java b/gson/src/test/java/com/google/gson/functional/CircularReferenceTest.java
new file mode 100644
index 00000000..d352e241
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/CircularReferenceTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.common.TestTypes.ClassOverridingEquals;
+
+/**
+ * Functional tests related to circular reference detection and error reporting.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class CircularReferenceTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testCircularSerialization() throws Exception {
+ ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
+ ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
+ a.children.add(b);
+ b.children.add(a);
+ try {
+ gson.toJson(a);
+ fail("Circular types should not get printed!");
+ } catch (StackOverflowError expected) {
+ }
+ }
+
+ public void testSelfReferenceIgnoredInSerialization() throws Exception {
+ ClassOverridingEquals objA = new ClassOverridingEquals();
+ objA.ref = objA;
+
+ String json = gson.toJson(objA);
+ assertFalse(json.contains("ref")); // self-reference is ignored
+ }
+
+ public void testSelfReferenceArrayFieldSerialization() throws Exception {
+ ClassWithSelfReferenceArray objA = new ClassWithSelfReferenceArray();
+ objA.children = new ClassWithSelfReferenceArray[]{objA};
+
+ try {
+ gson.toJson(objA);
+ fail("Circular reference to self can not be serialized!");
+ } catch (StackOverflowError expected) {
+ }
+ }
+
+ public void testSelfReferenceCustomHandlerSerialization() throws Exception {
+ ClassWithSelfReference obj = new ClassWithSelfReference();
+ obj.child = obj;
+ Gson gson = new GsonBuilder().registerTypeAdapter(ClassWithSelfReference.class, new JsonSerializer<ClassWithSelfReference>() {
+ public JsonElement serialize(ClassWithSelfReference src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("property", "value");
+ obj.add("child", context.serialize(src.child));
+ return obj;
+ }
+ }).create();
+ try {
+ gson.toJson(obj);
+ fail("Circular reference to self can not be serialized!");
+ } catch (StackOverflowError expected) {
+ }
+ }
+
+ public void testDirectedAcyclicGraphSerialization() throws Exception {
+ ContainsReferenceToSelfType a = new ContainsReferenceToSelfType();
+ ContainsReferenceToSelfType b = new ContainsReferenceToSelfType();
+ ContainsReferenceToSelfType c = new ContainsReferenceToSelfType();
+ a.children.add(b);
+ a.children.add(c);
+ b.children.add(c);
+ assertNotNull(gson.toJson(a));
+ }
+
+ public void testDirectedAcyclicGraphDeserialization() throws Exception {
+ String json = "{\"children\":[{\"children\":[{\"children\":[]}]},{\"children\":[]}]}";
+ ContainsReferenceToSelfType target = gson.fromJson(json, ContainsReferenceToSelfType.class);
+ assertNotNull(target);
+ assertEquals(2, target.children.size());
+ }
+
+ private static class ContainsReferenceToSelfType {
+ Collection<ContainsReferenceToSelfType> children = new ArrayList<ContainsReferenceToSelfType>();
+ }
+
+ private static class ClassWithSelfReference {
+ ClassWithSelfReference child;
+ }
+
+ private static class ClassWithSelfReferenceArray {
+ @SuppressWarnings("unused")
+ ClassWithSelfReferenceArray[] children;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/CollectionTest.java b/gson/src/test/java/com/google/gson/functional/CollectionTest.java
new file mode 100644
index 00000000..ac6fec93
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/CollectionTest.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.common.MoreAsserts;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+
+/**
+ * Functional tests for Json serialization and deserialization of collections.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class CollectionTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testTopLevelCollectionOfIntegersSerialization() {
+ Collection<Integer> target = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ Type targetType = new TypeToken<Collection<Integer>>() {}.getType();
+ String json = gson.toJson(target, targetType);
+ assertEquals("[1,2,3,4,5,6,7,8,9]", json);
+ }
+
+ public void testTopLevelCollectionOfIntegersDeserialization() {
+ String json = "[0,1,2,3,4,5,6,7,8,9]";
+ Type collectionType = new TypeToken<Collection<Integer>>() { }.getType();
+ Collection<Integer> target = gson.fromJson(json, collectionType);
+ int[] expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ MoreAsserts.assertEquals(expected, toIntArray(target));
+ }
+
+ public void testTopLevelListOfIntegerCollectionsDeserialization() throws Exception {
+ String json = "[[1,2,3],[4,5,6],[7,8,9]]";
+ Type collectionType = new TypeToken<Collection<Collection<Integer>>>() {}.getType();
+ List<Collection<Integer>> target = gson.fromJson(json, collectionType);
+ int[][] expected = new int[3][3];
+ for (int i = 0; i < 3; ++i) {
+ int start = (3 * i) + 1;
+ for (int j = 0; j < 3; ++j) {
+ expected[i][j] = start + j;
+ }
+ }
+
+ for (int i = 0; i < 3; i++) {
+ MoreAsserts.assertEquals(expected[i], toIntArray(target.get(i)));
+ }
+ }
+
+ public void testLinkedListSerialization() {
+ List<String> list = new LinkedList<String>();
+ list.add("a1");
+ list.add("a2");
+ Type linkedListType = new TypeToken<LinkedList<String>>() {}.getType();
+ String json = gson.toJson(list, linkedListType);
+ assertTrue(json.contains("a1"));
+ assertTrue(json.contains("a2"));
+ }
+
+ public void testLinkedListDeserialization() {
+ String json = "['a1','a2']";
+ Type linkedListType = new TypeToken<LinkedList<String>>() {}.getType();
+ List<String> list = gson.fromJson(json, linkedListType);
+ assertEquals("a1", list.get(0));
+ assertEquals("a2", list.get(1));
+ }
+
+ public void testQueueSerialization() {
+ Queue<String> queue = new LinkedList<String>();
+ queue.add("a1");
+ queue.add("a2");
+ Type queueType = new TypeToken<Queue<String>>() {}.getType();
+ String json = gson.toJson(queue, queueType);
+ assertTrue(json.contains("a1"));
+ assertTrue(json.contains("a2"));
+ }
+
+ public void testQueueDeserialization() {
+ String json = "['a1','a2']";
+ Type queueType = new TypeToken<Queue<String>>() {}.getType();
+ Queue<String> queue = gson.fromJson(json, queueType);
+ assertEquals("a1", queue.element());
+ queue.remove();
+ assertEquals("a2", queue.element());
+ }
+
+ public void testNullsInListSerialization() {
+ List<String> list = new ArrayList<String>();
+ list.add("foo");
+ list.add(null);
+ list.add("bar");
+ String expected = "[\"foo\",null,\"bar\"]";
+ Type typeOfList = new TypeToken<List<String>>() {}.getType();
+ String json = gson.toJson(list, typeOfList);
+ assertEquals(expected, json);
+ }
+
+ public void testNullsInListDeserialization() {
+ List<String> expected = new ArrayList<String>();
+ expected.add("foo");
+ expected.add(null);
+ expected.add("bar");
+ String json = "[\"foo\",null,\"bar\"]";
+ Type expectedType = new TypeToken<List<String>>() {}.getType();
+ List<String> target = gson.fromJson(json, expectedType);
+ for (int i = 0; i < expected.size(); ++i) {
+ assertEquals(expected.get(i), target.get(i));
+ }
+ }
+
+ public void testCollectionOfObjectSerialization() {
+ List<Object> target = new ArrayList<Object>();
+ target.add("Hello");
+ target.add("World");
+ assertEquals("[\"Hello\",\"World\"]", gson.toJson(target));
+
+ Type type = new TypeToken<List<Object>>() {}.getType();
+ assertEquals("[\"Hello\",\"World\"]", gson.toJson(target, type));
+ }
+
+ public void testCollectionOfObjectWithNullSerialization() {
+ List<Object> target = new ArrayList<Object>();
+ target.add("Hello");
+ target.add(null);
+ target.add("World");
+ assertEquals("[\"Hello\",null,\"World\"]", gson.toJson(target));
+
+ Type type = new TypeToken<List<Object>>() {}.getType();
+ assertEquals("[\"Hello\",null,\"World\"]", gson.toJson(target, type));
+ }
+
+ public void testCollectionOfStringsSerialization() {
+ List<String> target = new ArrayList<String>();
+ target.add("Hello");
+ target.add("World");
+ assertEquals("[\"Hello\",\"World\"]", gson.toJson(target));
+ }
+
+ public void testCollectionOfBagOfPrimitivesSerialization() {
+ List<BagOfPrimitives> target = new ArrayList<BagOfPrimitives>();
+ BagOfPrimitives objA = new BagOfPrimitives(3L, 1, true, "blah");
+ BagOfPrimitives objB = new BagOfPrimitives(2L, 6, false, "blahB");
+ target.add(objA);
+ target.add(objB);
+
+ String result = gson.toJson(target);
+ assertTrue(result.startsWith("["));
+ assertTrue(result.endsWith("]"));
+ for (BagOfPrimitives obj : target) {
+ assertTrue(result.contains(obj.getExpectedJson()));
+ }
+ }
+
+ public void testCollectionOfStringsDeserialization() {
+ String json = "[\"Hello\",\"World\"]";
+ Type collectionType = new TypeToken<Collection<String>>() { }.getType();
+ Collection<String> target = gson.fromJson(json, collectionType);
+
+ assertTrue(target.contains("Hello"));
+ assertTrue(target.contains("World"));
+ }
+
+ public void testRawCollectionOfIntegersSerialization() {
+ Collection<Integer> target = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ assertEquals("[1,2,3,4,5,6,7,8,9]", gson.toJson(target));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void testRawCollectionSerialization() {
+ BagOfPrimitives bag1 = new BagOfPrimitives();
+ Collection target = Arrays.asList(bag1, bag1);
+ String json = gson.toJson(target);
+ assertTrue(json.contains(bag1.getExpectedJson()));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void testRawCollectionDeserializationNotAlllowed() {
+ String json = "[0,1,2,3,4,5,6,7,8,9]";
+ Collection integers = gson.fromJson(json, Collection.class);
+ // JsonReader converts numbers to double by default so we need a floating point comparison
+ assertEquals(Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0), integers);
+
+ json = "[\"Hello\", \"World\"]";
+ Collection strings = gson.fromJson(json, Collection.class);
+ assertTrue(strings.contains("Hello"));
+ assertTrue(strings.contains("World"));
+ }
+
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public void testRawCollectionOfBagOfPrimitivesNotAllowed() {
+ BagOfPrimitives bag = new BagOfPrimitives(10, 20, false, "stringValue");
+ String json = '[' + bag.getExpectedJson() + ',' + bag.getExpectedJson() + ']';
+ Collection target = gson.fromJson(json, Collection.class);
+ assertEquals(2, target.size());
+ for (Object bag1 : target) {
+ // Gson 2.0 converts raw objects into maps
+ Map<String, Object> values = (Map<String, Object>) bag1;
+ assertTrue(values.containsValue(10.0));
+ assertTrue(values.containsValue(20.0));
+ assertTrue(values.containsValue("stringValue"));
+ }
+ }
+
+ public void testWildcardPrimitiveCollectionSerilaization() throws Exception {
+ Collection<? extends Integer> target = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
+ Type collectionType = new TypeToken<Collection<? extends Integer>>() { }.getType();
+ String json = gson.toJson(target, collectionType);
+ assertEquals("[1,2,3,4,5,6,7,8,9]", json);
+
+ json = gson.toJson(target);
+ assertEquals("[1,2,3,4,5,6,7,8,9]", json);
+ }
+
+ public void testWildcardPrimitiveCollectionDeserilaization() throws Exception {
+ String json = "[1,2,3,4,5,6,7,8,9]";
+ Type collectionType = new TypeToken<Collection<? extends Integer>>() { }.getType();
+ Collection<? extends Integer> target = gson.fromJson(json, collectionType);
+ assertEquals(9, target.size());
+ assertTrue(target.contains(1));
+ assertTrue(target.contains(9));
+ }
+
+ public void testWildcardCollectionField() throws Exception {
+ Collection<BagOfPrimitives> collection = new ArrayList<BagOfPrimitives>();
+ BagOfPrimitives objA = new BagOfPrimitives(3L, 1, true, "blah");
+ BagOfPrimitives objB = new BagOfPrimitives(2L, 6, false, "blahB");
+ collection.add(objA);
+ collection.add(objB);
+
+ ObjectWithWildcardCollection target = new ObjectWithWildcardCollection(collection);
+ String json = gson.toJson(target);
+ assertTrue(json.contains(objA.getExpectedJson()));
+ assertTrue(json.contains(objB.getExpectedJson()));
+
+ target = gson.fromJson(json, ObjectWithWildcardCollection.class);
+ Collection<? extends BagOfPrimitives> deserializedCollection = target.getCollection();
+ assertEquals(2, deserializedCollection.size());
+ assertTrue(deserializedCollection.contains(objA));
+ assertTrue(deserializedCollection.contains(objB));
+ }
+
+ public void testFieldIsArrayList() {
+ HasArrayListField object = new HasArrayListField();
+ object.longs.add(1L);
+ object.longs.add(3L);
+ String json = gson.toJson(object, HasArrayListField.class);
+ assertEquals("{\"longs\":[1,3]}", json);
+ HasArrayListField copy = gson.fromJson("{\"longs\":[1,3]}", HasArrayListField.class);
+ assertEquals(Arrays.asList(1L, 3L), copy.longs);
+ }
+
+ public void testUserCollectionTypeAdapter() {
+ Type listOfString = new TypeToken<List<String>>() {}.getType();
+ Object stringListSerializer = new JsonSerializer<List<String>>() {
+ public JsonElement serialize(List<String> src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ return new JsonPrimitive(src.get(0) + ";" + src.get(1));
+ }
+ };
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(listOfString, stringListSerializer)
+ .create();
+ assertEquals("\"ab;cd\"", gson.toJson(Arrays.asList("ab", "cd"), listOfString));
+ }
+
+ static class HasArrayListField {
+ ArrayList<Long> longs = new ArrayList<Long>();
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static int[] toIntArray(Collection collection) {
+ int[] ints = new int[collection.size()];
+ int i = 0;
+ for (Iterator iterator = collection.iterator(); iterator.hasNext(); ++i) {
+ Object obj = iterator.next();
+ if (obj instanceof Integer) {
+ ints[i] = ((Integer)obj).intValue();
+ } else if (obj instanceof Long) {
+ ints[i] = ((Long)obj).intValue();
+ }
+ }
+ return ints;
+ }
+
+ private static class ObjectWithWildcardCollection {
+ private final Collection<? extends BagOfPrimitives> collection;
+
+ public ObjectWithWildcardCollection(Collection<? extends BagOfPrimitives> collection) {
+ this.collection = collection;
+ }
+
+ public Collection<? extends BagOfPrimitives> getCollection() {
+ return collection;
+ }
+ }
+
+ private static class Entry {
+ int value;
+ Entry(int value) {
+ this.value = value;
+ }
+ }
+ public void testSetSerialization() {
+ Set<Entry> set = new HashSet<Entry>();
+ set.add(new Entry(1));
+ set.add(new Entry(2));
+ String json = gson.toJson(set);
+ assertTrue(json.contains("1"));
+ assertTrue(json.contains("2"));
+ }
+ public void testSetDeserialization() {
+ String json = "[{value:1},{value:2}]";
+ Type type = new TypeToken<Set<Entry>>() {}.getType();
+ Set<Entry> set = gson.fromJson(json, type);
+ assertEquals(2, set.size());
+ for (Entry entry : set) {
+ assertTrue(entry.value == 1 || entry.value == 2);
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ConcurrencyTest.java b/gson/src/test/java/com/google/gson/functional/ConcurrencyTest.java
new file mode 100755
index 00000000..2dccf4b6
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ConcurrencyTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+
+/**
+ * Tests for ensuring Gson thread-safety.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ConcurrencyTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ /**
+ * Source-code based on
+ * http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
+ */
+ public void testSingleThreadSerialization() {
+ MyObject myObj = new MyObject();
+ for (int i = 0; i < 10; i++) {
+ gson.toJson(myObj);
+ }
+ }
+
+ /**
+ * Source-code based on
+ * http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
+ */
+ public void testSingleThreadDeserialization() {
+ for (int i = 0; i < 10; i++) {
+ gson.fromJson("{'a':'hello','b':'world','i':1}", MyObject.class);
+ }
+ }
+
+ /**
+ * Source-code based on
+ * http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
+ */
+ public void testMultiThreadSerialization() throws InterruptedException {
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final CountDownLatch finishedLatch = new CountDownLatch(10);
+ final AtomicBoolean failed = new AtomicBoolean(false);
+ ExecutorService executor = Executors.newFixedThreadPool(10);
+ for (int taskCount = 0; taskCount < 10; taskCount++) {
+ executor.execute(new Runnable() {
+ public void run() {
+ MyObject myObj = new MyObject();
+ try {
+ startLatch.await();
+ for (int i = 0; i < 10; i++) {
+ gson.toJson(myObj);
+ }
+ } catch (Throwable t) {
+ failed.set(true);
+ } finally {
+ finishedLatch.countDown();
+ }
+ }
+ });
+ }
+ startLatch.countDown();
+ finishedLatch.await();
+ assertFalse(failed.get());
+ }
+
+ /**
+ * Source-code based on
+ * http://groups.google.com/group/google-gson/browse_thread/thread/563bb51ee2495081
+ */
+ public void testMultiThreadDeserialization() throws InterruptedException {
+ final CountDownLatch startLatch = new CountDownLatch(1);
+ final CountDownLatch finishedLatch = new CountDownLatch(10);
+ final AtomicBoolean failed = new AtomicBoolean(false);
+ ExecutorService executor = Executors.newFixedThreadPool(10);
+ for (int taskCount = 0; taskCount < 10; taskCount++) {
+ executor.execute(new Runnable() {
+ public void run() {
+ try {
+ startLatch.await();
+ for (int i = 0; i < 10; i++) {
+ gson.fromJson("{'a':'hello','b':'world','i':1}", MyObject.class);
+ }
+ } catch (Throwable t) {
+ failed.set(true);
+ } finally {
+ finishedLatch.countDown();
+ }
+ }
+ });
+ }
+ startLatch.countDown();
+ finishedLatch.await();
+ assertFalse(failed.get());
+ }
+
+ @SuppressWarnings("unused")
+ private static class MyObject {
+ String a;
+ String b;
+ int i;
+
+ MyObject() {
+ this("hello", "world", 42);
+ }
+
+ public MyObject(String a, String b, int i) {
+ this.a = a;
+ this.b = b;
+ this.i = i;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/CustomDeserializerTest.java b/gson/src/test/java/com/google/gson/functional/CustomDeserializerTest.java
new file mode 100644
index 00000000..54ecade2
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/CustomDeserializerTest.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.common.TestTypes.Base;
+import com.google.gson.common.TestTypes.ClassWithBaseField;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+
+/**
+ * Functional Test exercising custom deserialization only. When test applies to both
+ * serialization and deserialization then add it to CustomTypeAdapterTest.
+ *
+ * @author Joel Leitch
+ */
+public class CustomDeserializerTest extends TestCase {
+ private static final String DEFAULT_VALUE = "test123";
+ private static final String SUFFIX = "blah";
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new GsonBuilder().registerTypeAdapter(DataHolder.class, new DataHolderDeserializer()).create();
+ }
+
+ public void testDefaultConstructorNotCalledOnObject() throws Exception {
+ DataHolder data = new DataHolder(DEFAULT_VALUE);
+ String json = gson.toJson(data);
+
+ DataHolder actual = gson.fromJson(json, DataHolder.class);
+ assertEquals(DEFAULT_VALUE + SUFFIX, actual.getData());
+ }
+
+ public void testDefaultConstructorNotCalledOnField() throws Exception {
+ DataHolderWrapper dataWrapper = new DataHolderWrapper(new DataHolder(DEFAULT_VALUE));
+ String json = gson.toJson(dataWrapper);
+
+ DataHolderWrapper actual = gson.fromJson(json, DataHolderWrapper.class);
+ assertEquals(DEFAULT_VALUE + SUFFIX, actual.getWrappedData().getData());
+ }
+
+ private static class DataHolder {
+ private final String data;
+
+ // For use by Gson
+ @SuppressWarnings("unused")
+ private DataHolder() {
+ throw new IllegalStateException();
+ }
+
+ public DataHolder(String data) {
+ this.data = data;
+ }
+
+ public String getData() {
+ return data;
+ }
+ }
+
+ private static class DataHolderWrapper {
+ private final DataHolder wrappedData;
+
+ // For use by Gson
+ @SuppressWarnings("unused")
+ private DataHolderWrapper() {
+ this(new DataHolder(DEFAULT_VALUE));
+ }
+
+ public DataHolderWrapper(DataHolder data) {
+ this.wrappedData = data;
+ }
+
+ public DataHolder getWrappedData() {
+ return wrappedData;
+ }
+ }
+
+ private static class DataHolderDeserializer implements JsonDeserializer<DataHolder> {
+ public DataHolder deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject jsonObj = json.getAsJsonObject();
+ String dataString = jsonObj.get("data").getAsString();
+ return new DataHolder(dataString + SUFFIX);
+ }
+ }
+
+ public void testJsonTypeFieldBasedDeserialization() {
+ String json = "{field1:'abc',field2:'def',__type__:'SUB_TYPE1'}";
+ Gson gson = new GsonBuilder().registerTypeAdapter(MyBase.class, new JsonDeserializer<MyBase>() {
+ public MyBase deserialize(JsonElement json, Type pojoType,
+ JsonDeserializationContext context) throws JsonParseException {
+ String type = json.getAsJsonObject().get(MyBase.TYPE_ACCESS).getAsString();
+ return context.deserialize(json, SubTypes.valueOf(type).getSubclass());
+ }
+ }).create();
+ SubType1 target = (SubType1) gson.fromJson(json, MyBase.class);
+ assertEquals("abc", target.field1);
+ }
+
+ private static class MyBase {
+ static final String TYPE_ACCESS = "__type__";
+ }
+
+ private enum SubTypes {
+ SUB_TYPE1(SubType1.class),
+ SUB_TYPE2(SubType2.class);
+ private final Type subClass;
+ private SubTypes(Type subClass) {
+ this.subClass = subClass;
+ }
+ public Type getSubclass() {
+ return subClass;
+ }
+ }
+
+ private static class SubType1 extends MyBase {
+ String field1;
+ }
+
+ private static class SubType2 extends MyBase {
+ @SuppressWarnings("unused")
+ String field2;
+ }
+
+ public void testCustomDeserializerReturnsNullForTopLevelObject() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new JsonDeserializer<Base>() {
+ public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return null;
+ }
+ }).create();
+ String json = "{baseName:'Base',subName:'SubRevised'}";
+ Base target = gson.fromJson(json, Base.class);
+ assertNull(target);
+ }
+
+ public void testCustomDeserializerReturnsNull() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new JsonDeserializer<Base>() {
+ public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return null;
+ }
+ }).create();
+ String json = "{base:{baseName:'Base',subName:'SubRevised'}}";
+ ClassWithBaseField target = gson.fromJson(json, ClassWithBaseField.class);
+ assertNull(target.base);
+ }
+
+ public void testCustomDeserializerReturnsNullForArrayElements() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new JsonDeserializer<Base>() {
+ public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return null;
+ }
+ }).create();
+ String json = "[{baseName:'Base'},{baseName:'Base'}]";
+ Base[] target = gson.fromJson(json, Base[].class);
+ assertNull(target[0]);
+ assertNull(target[1]);
+ }
+
+ public void testCustomDeserializerReturnsNullForArrayElementsForArrayField() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new JsonDeserializer<Base>() {
+ public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return null;
+ }
+ }).create();
+ String json = "{bases:[{baseName:'Base'},{baseName:'Base'}]}";
+ ClassWithBaseArray target = gson.fromJson(json, ClassWithBaseArray.class);
+ assertNull(target.bases[0]);
+ assertNull(target.bases[1]);
+ }
+
+ private static class ClassWithBaseArray {
+ Base[] bases;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/CustomSerializerTest.java b/gson/src/test/java/com/google/gson/functional/CustomSerializerTest.java
new file mode 100644
index 00000000..c8095463
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/CustomSerializerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.common.TestTypes.Base;
+import com.google.gson.common.TestTypes.BaseSerializer;
+import com.google.gson.common.TestTypes.ClassWithBaseArrayField;
+import com.google.gson.common.TestTypes.ClassWithBaseField;
+import com.google.gson.common.TestTypes.Sub;
+import com.google.gson.common.TestTypes.SubSerializer;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+
+/**
+ * Functional Test exercising custom serialization only. When test applies to both
+ * serialization and deserialization then add it to CustomTypeAdapterTest.
+ *
+ * @author Inderjeet Singh
+ */
+public class CustomSerializerTest extends TestCase {
+
+ public void testBaseClassSerializerInvokedForBaseClassFields() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new BaseSerializer())
+ .registerTypeAdapter(Sub.class, new SubSerializer())
+ .create();
+ ClassWithBaseField target = new ClassWithBaseField(new Base());
+ JsonObject json = (JsonObject) gson.toJsonTree(target);
+ JsonObject base = json.get("base").getAsJsonObject();
+ assertEquals(BaseSerializer.NAME, base.get(Base.SERIALIZER_KEY).getAsString());
+ }
+
+ public void testSubClassSerializerInvokedForBaseClassFieldsHoldingSubClassInstances() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new BaseSerializer())
+ .registerTypeAdapter(Sub.class, new SubSerializer())
+ .create();
+ ClassWithBaseField target = new ClassWithBaseField(new Sub());
+ JsonObject json = (JsonObject) gson.toJsonTree(target);
+ JsonObject base = json.get("base").getAsJsonObject();
+ assertEquals(SubSerializer.NAME, base.get(Base.SERIALIZER_KEY).getAsString());
+ }
+
+ public void testSubClassSerializerInvokedForBaseClassFieldsHoldingArrayOfSubClassInstances() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new BaseSerializer())
+ .registerTypeAdapter(Sub.class, new SubSerializer())
+ .create();
+ ClassWithBaseArrayField target = new ClassWithBaseArrayField(new Base[] {new Sub(), new Sub()});
+ JsonObject json = (JsonObject) gson.toJsonTree(target);
+ JsonArray array = json.get("base").getAsJsonArray();
+ for (JsonElement element : array) {
+ JsonElement serializerKey = element.getAsJsonObject().get(Base.SERIALIZER_KEY);
+ assertEquals(SubSerializer.NAME, serializerKey.getAsString());
+ }
+ }
+
+ public void testBaseClassSerializerInvokedForBaseClassFieldsHoldingSubClassInstances() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new BaseSerializer())
+ .create();
+ ClassWithBaseField target = new ClassWithBaseField(new Sub());
+ JsonObject json = (JsonObject) gson.toJsonTree(target);
+ JsonObject base = json.get("base").getAsJsonObject();
+ assertEquals(BaseSerializer.NAME, base.get(Base.SERIALIZER_KEY).getAsString());
+ }
+
+ public void testSerializerReturnsNull() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new JsonSerializer<Base>() {
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ return null;
+ }
+ })
+ .create();
+ JsonElement json = gson.toJsonTree(new Base());
+ assertTrue(json.isJsonNull());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java
new file mode 100644
index 00000000..93ec7858
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/CustomTypeAdaptersTest.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.ClassWithCustomTypeConverter;
+import com.google.gson.reflect.TypeToken;
+
+import java.util.Date;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Functional tests for the support of custom serializer and deserializers.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class CustomTypeAdaptersTest extends TestCase {
+ private GsonBuilder builder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ builder = new GsonBuilder();
+ }
+
+ public void testCustomSerializers() {
+ Gson gson = builder.registerTypeAdapter(
+ ClassWithCustomTypeConverter.class, new JsonSerializer<ClassWithCustomTypeConverter>() {
+ public JsonElement serialize(ClassWithCustomTypeConverter src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ JsonObject json = new JsonObject();
+ json.addProperty("bag", 5);
+ json.addProperty("value", 25);
+ return json;
+ }
+ }).create();
+ ClassWithCustomTypeConverter target = new ClassWithCustomTypeConverter();
+ assertEquals("{\"bag\":5,\"value\":25}", gson.toJson(target));
+ }
+
+ public void testCustomDeserializers() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ ClassWithCustomTypeConverter.class, new JsonDeserializer<ClassWithCustomTypeConverter>() {
+ public ClassWithCustomTypeConverter deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) {
+ JsonObject jsonObject = json.getAsJsonObject();
+ int value = jsonObject.get("bag").getAsInt();
+ return new ClassWithCustomTypeConverter(new BagOfPrimitives(value,
+ value, false, ""), value);
+ }
+ }).create();
+ String json = "{\"bag\":5,\"value\":25}";
+ ClassWithCustomTypeConverter target = gson.fromJson(json, ClassWithCustomTypeConverter.class);
+ assertEquals(5, target.getBag().getIntValue());
+ }
+
+ public void disable_testCustomSerializersOfSelf() {
+ Gson gson = createGsonObjectWithFooTypeAdapter();
+ Gson basicGson = new Gson();
+ Foo newFooObject = new Foo(1, 2L);
+ String jsonFromCustomSerializer = gson.toJson(newFooObject);
+ String jsonFromGson = basicGson.toJson(newFooObject);
+
+ assertEquals(jsonFromGson, jsonFromCustomSerializer);
+ }
+
+ public void disable_testCustomDeserializersOfSelf() {
+ Gson gson = createGsonObjectWithFooTypeAdapter();
+ Gson basicGson = new Gson();
+ Foo expectedFoo = new Foo(1, 2L);
+ String json = basicGson.toJson(expectedFoo);
+ Foo newFooObject = gson.fromJson(json, Foo.class);
+
+ assertEquals(expectedFoo.key, newFooObject.key);
+ assertEquals(expectedFoo.value, newFooObject.value);
+ }
+
+ public void testCustomNestedSerializers() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ BagOfPrimitives.class, new JsonSerializer<BagOfPrimitives>() {
+ public JsonElement serialize(BagOfPrimitives src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ return new JsonPrimitive(6);
+ }
+ }).create();
+ ClassWithCustomTypeConverter target = new ClassWithCustomTypeConverter();
+ assertEquals("{\"bag\":6,\"value\":10}", gson.toJson(target));
+ }
+
+ public void testCustomNestedDeserializers() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ BagOfPrimitives.class, new JsonDeserializer<BagOfPrimitives>() {
+ public BagOfPrimitives deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ int value = json.getAsInt();
+ return new BagOfPrimitives(value, value, false, "");
+ }
+ }).create();
+ String json = "{\"bag\":7,\"value\":25}";
+ ClassWithCustomTypeConverter target = gson.fromJson(json, ClassWithCustomTypeConverter.class);
+ assertEquals(7, target.getBag().getIntValue());
+ }
+
+ public void testCustomTypeAdapterDoesNotAppliesToSubClasses() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(Base.class, new JsonSerializer<Base> () {
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject json = new JsonObject();
+ json.addProperty("value", src.baseValue);
+ return json;
+ }
+ }).create();
+ Base b = new Base();
+ String json = gson.toJson(b);
+ assertTrue(json.contains("value"));
+ b = new Derived();
+ json = gson.toJson(b);
+ assertTrue(json.contains("derivedValue"));
+ }
+
+ public void testCustomTypeAdapterAppliesToSubClassesSerializedAsBaseClass() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(Base.class, new JsonSerializer<Base> () {
+ public JsonElement serialize(Base src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject json = new JsonObject();
+ json.addProperty("value", src.baseValue);
+ return json;
+ }
+ }).create();
+ Base b = new Base();
+ String json = gson.toJson(b);
+ assertTrue(json.contains("value"));
+ b = new Derived();
+ json = gson.toJson(b, Base.class);
+ assertTrue(json.contains("value"));
+ assertFalse(json.contains("derivedValue"));
+ }
+
+ private static class Base {
+ int baseValue = 2;
+ }
+
+ private static class Derived extends Base {
+ @SuppressWarnings("unused")
+ int derivedValue = 3;
+ }
+
+
+ private Gson createGsonObjectWithFooTypeAdapter() {
+ return new GsonBuilder().registerTypeAdapter(Foo.class, new FooTypeAdapter()).create();
+ }
+
+ public static class Foo {
+ private final int key;
+ private final long value;
+
+ public Foo() {
+ this(0, 0L);
+ }
+
+ public Foo(int key, long value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
+
+ public static class FooTypeAdapter implements JsonSerializer<Foo>, JsonDeserializer<Foo> {
+ public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return context.deserialize(json, typeOfT);
+ }
+
+ public JsonElement serialize(Foo src, Type typeOfSrc, JsonSerializationContext context) {
+ return context.serialize(src, typeOfSrc);
+ }
+ }
+
+ public void testCustomSerializerInvokedForPrimitives() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(boolean.class, new JsonSerializer<Boolean>() {
+ public JsonElement serialize(Boolean s, Type t, JsonSerializationContext c) {
+ return new JsonPrimitive(s ? 1 : 0);
+ }
+ })
+ .create();
+ assertEquals("1", gson.toJson(true, boolean.class));
+ assertEquals("true", gson.toJson(true, Boolean.class));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void testCustomDeserializerInvokedForPrimitives() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(boolean.class, new JsonDeserializer() {
+ public Object deserialize(JsonElement json, Type t, JsonDeserializationContext context) {
+ return json.getAsInt() != 0;
+ }
+ })
+ .create();
+ assertEquals(Boolean.TRUE, gson.fromJson("1", boolean.class));
+ assertEquals(Boolean.TRUE, gson.fromJson("true", Boolean.class));
+ }
+
+ public void testCustomByteArraySerializer() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(byte[].class, new JsonSerializer<byte[]>() {
+ public JsonElement serialize(byte[] src, Type typeOfSrc, JsonSerializationContext context) {
+ StringBuilder sb = new StringBuilder(src.length);
+ for (byte b : src) {
+ sb.append(b);
+ }
+ return new JsonPrimitive(sb.toString());
+ }
+ }).create();
+ byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ String json = gson.toJson(data);
+ assertEquals("\"0123456789\"", json);
+ }
+
+ public void testCustomByteArrayDeserializerAndInstanceCreator() {
+ GsonBuilder gsonBuilder = new GsonBuilder().registerTypeAdapter(byte[].class,
+ new JsonDeserializer<byte[]>() {
+ public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ String str = json.getAsString();
+ byte[] data = new byte[str.length()];
+ for (int i = 0; i < data.length; ++i) {
+ data[i] = Byte.parseByte(""+str.charAt(i));
+ }
+ return data;
+ }
+ });
+ Gson gson = gsonBuilder.create();
+ String json = "'0123456789'";
+ byte[] actual = gson.fromJson(json, byte[].class);
+ byte[] expected = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+ for (int i = 0; i < actual.length; ++i) {
+ assertEquals(expected[i], actual[i]);
+ }
+ }
+
+ private static class StringHolder {
+ String part1;
+ String part2;
+
+ public StringHolder(String string) {
+ String[] parts = string.split(":");
+ part1 = parts[0];
+ part2 = parts[1];
+ }
+ public StringHolder(String part1, String part2) {
+ this.part1 = part1;
+ this.part2 = part2;
+ }
+ }
+
+ private static class StringHolderTypeAdapter implements JsonSerializer<StringHolder>,
+ JsonDeserializer<StringHolder>, InstanceCreator<StringHolder> {
+
+ public StringHolder createInstance(Type type) {
+ //Fill up with objects that will be thrown away
+ return new StringHolder("unknown:thing");
+ }
+
+ public StringHolder deserialize(JsonElement src, Type type,
+ JsonDeserializationContext context) {
+ return new StringHolder(src.getAsString());
+ }
+
+ public JsonElement serialize(StringHolder src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ String contents = src.part1 + ':' + src.part2;
+ return new JsonPrimitive(contents);
+ }
+ }
+
+ // Test created from Issue 70
+ public void testCustomAdapterInvokedForCollectionElementSerializationWithType() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(StringHolder.class, new StringHolderTypeAdapter())
+ .create();
+ Type setType = new TypeToken<Set<StringHolder>>() {}.getType();
+ StringHolder holder = new StringHolder("Jacob", "Tomaw");
+ Set<StringHolder> setOfHolders = new HashSet<StringHolder>();
+ setOfHolders.add(holder);
+ String json = gson.toJson(setOfHolders, setType);
+ assertTrue(json.contains("Jacob:Tomaw"));
+ }
+
+ // Test created from Issue 70
+ public void testCustomAdapterInvokedForCollectionElementSerialization() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(StringHolder.class, new StringHolderTypeAdapter())
+ .create();
+ StringHolder holder = new StringHolder("Jacob", "Tomaw");
+ Set<StringHolder> setOfHolders = new HashSet<StringHolder>();
+ setOfHolders.add(holder);
+ String json = gson.toJson(setOfHolders);
+ assertTrue(json.contains("Jacob:Tomaw"));
+ }
+
+ // Test created from Issue 70
+ public void testCustomAdapterInvokedForCollectionElementDeserialization() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(StringHolder.class, new StringHolderTypeAdapter())
+ .create();
+ Type setType = new TypeToken<Set<StringHolder>>() {}.getType();
+ Set<StringHolder> setOfHolders = gson.fromJson("['Jacob:Tomaw']", setType);
+ assertEquals(1, setOfHolders.size());
+ StringHolder foo = setOfHolders.iterator().next();
+ assertEquals("Jacob", foo.part1);
+ assertEquals("Tomaw", foo.part2);
+ }
+
+ // Test created from Issue 70
+ public void testCustomAdapterInvokedForMapElementSerializationWithType() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(StringHolder.class, new StringHolderTypeAdapter())
+ .create();
+ Type mapType = new TypeToken<Map<String,StringHolder>>() {}.getType();
+ StringHolder holder = new StringHolder("Jacob", "Tomaw");
+ Map<String, StringHolder> mapOfHolders = new HashMap<String, StringHolder>();
+ mapOfHolders.put("foo", holder);
+ String json = gson.toJson(mapOfHolders, mapType);
+ assertTrue(json.contains("\"foo\":\"Jacob:Tomaw\""));
+ }
+
+ // Test created from Issue 70
+ public void testCustomAdapterInvokedForMapElementSerialization() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(StringHolder.class, new StringHolderTypeAdapter())
+ .create();
+ StringHolder holder = new StringHolder("Jacob", "Tomaw");
+ Map<String, StringHolder> mapOfHolders = new HashMap<String, StringHolder>();
+ mapOfHolders.put("foo", holder);
+ String json = gson.toJson(mapOfHolders);
+ assertTrue(json.contains("\"foo\":\"Jacob:Tomaw\""));
+ }
+
+ // Test created from Issue 70
+ public void testCustomAdapterInvokedForMapElementDeserialization() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(StringHolder.class, new StringHolderTypeAdapter())
+ .create();
+ Type mapType = new TypeToken<Map<String, StringHolder>>() {}.getType();
+ Map<String, StringHolder> mapOfFoo = gson.fromJson("{'foo':'Jacob:Tomaw'}", mapType);
+ assertEquals(1, mapOfFoo.size());
+ StringHolder foo = mapOfFoo.get("foo");
+ assertEquals("Jacob", foo.part1);
+ assertEquals("Tomaw", foo.part2);
+ }
+
+ public void testEnsureCustomSerializerNotInvokedForNullValues() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(DataHolder.class, new DataHolderSerializer())
+ .create();
+ DataHolderWrapper target = new DataHolderWrapper(new DataHolder("abc"));
+ String json = gson.toJson(target);
+ assertEquals("{\"wrappedData\":{\"myData\":\"abc\"}}", json);
+ }
+
+ public void testEnsureCustomDeserializerNotInvokedForNullValues() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(DataHolder.class, new DataHolderDeserializer())
+ .create();
+ String json = "{wrappedData:null}";
+ DataHolderWrapper actual = gson.fromJson(json, DataHolderWrapper.class);
+ assertNull(actual.wrappedData);
+ }
+
+ // Test created from Issue 352
+ public void testRegisterHierarchyAdapterForDate() {
+ Gson gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Date.class, new DateTypeAdapter())
+ .create();
+ assertEquals("0", gson.toJson(new Date(0)));
+ assertEquals("0", gson.toJson(new java.sql.Date(0)));
+ assertEquals(new Date(0), gson.fromJson("0", Date.class));
+ assertEquals(new java.sql.Date(0), gson.fromJson("0", java.sql.Date.class));
+ }
+
+ private static class DataHolder {
+ final String data;
+
+ public DataHolder(String data) {
+ this.data = data;
+ }
+ }
+
+ private static class DataHolderWrapper {
+ final DataHolder wrappedData;
+
+ public DataHolderWrapper(DataHolder data) {
+ this.wrappedData = data;
+ }
+ }
+
+ private static class DataHolderSerializer implements JsonSerializer<DataHolder> {
+ public JsonElement serialize(DataHolder src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("myData", src.data);
+ return obj;
+ }
+ }
+
+ private static class DataHolderDeserializer implements JsonDeserializer<DataHolder> {
+ public DataHolder deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ JsonObject jsonObj = json.getAsJsonObject();
+ JsonElement jsonElement = jsonObj.get("data");
+ if (jsonElement == null || jsonElement.isJsonNull()) {
+ return new DataHolder(null);
+ }
+ return new DataHolder(jsonElement.getAsString());
+ }
+ }
+
+ private static class DateTypeAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
+ public Date deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ return typeOfT == Date.class
+ ? new Date(json.getAsLong())
+ : new java.sql.Date(json.getAsLong());
+ }
+ public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.getTime());
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java
new file mode 100644
index 00000000..2b4db893
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Functional test for Json serialization and deserialization for common classes for which default
+ * support is provided in Gson. The tests for Map types are available in {@link MapTest}.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class DefaultTypeAdaptersTest extends TestCase {
+ private Gson gson;
+ private TimeZone oldTimeZone;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ this.oldTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
+ Locale.setDefault(Locale.US);
+ gson = new Gson();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TimeZone.setDefault(oldTimeZone);
+ super.tearDown();
+ }
+
+ public void testClassSerialization() {
+ try {
+ gson.toJson(String.class);
+ } catch (UnsupportedOperationException expected) {}
+ // Override with a custom type adapter for class.
+ gson = new GsonBuilder().registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create();
+ assertEquals("\"java.lang.String\"", gson.toJson(String.class));
+ }
+
+ public void testClassDeserialization() {
+ try {
+ gson.fromJson("String.class", String.class.getClass());
+ } catch (UnsupportedOperationException expected) {}
+ // Override with a custom type adapter for class.
+ gson = new GsonBuilder().registerTypeAdapter(Class.class, new MyClassTypeAdapter()).create();
+ assertEquals(String.class, gson.fromJson("java.lang.String", Class.class));
+ }
+
+ public void testUrlSerialization() throws Exception {
+ String urlValue = "http://google.com/";
+ URL url = new URL(urlValue);
+ assertEquals("\"http://google.com/\"", gson.toJson(url));
+ }
+
+ public void testUrlDeserialization() {
+ String urlValue = "http://google.com/";
+ String json = "'http:\\/\\/google.com\\/'";
+ URL target = gson.fromJson(json, URL.class);
+ assertEquals(urlValue, target.toExternalForm());
+
+ gson.fromJson('"' + urlValue + '"', URL.class);
+ assertEquals(urlValue, target.toExternalForm());
+ }
+
+ public void testUrlNullSerialization() throws Exception {
+ ClassWithUrlField target = new ClassWithUrlField();
+ assertEquals("{}", gson.toJson(target));
+ }
+
+ public void testUrlNullDeserialization() {
+ String json = "{}";
+ ClassWithUrlField target = gson.fromJson(json, ClassWithUrlField.class);
+ assertNull(target.url);
+ }
+
+ private static class ClassWithUrlField {
+ URL url;
+ }
+
+ public void testUriSerialization() throws Exception {
+ String uriValue = "http://google.com/";
+ URI uri = new URI(uriValue);
+ assertEquals("\"http://google.com/\"", gson.toJson(uri));
+ }
+
+ public void testUriDeserialization() {
+ String uriValue = "http://google.com/";
+ String json = '"' + uriValue + '"';
+ URI target = gson.fromJson(json, URI.class);
+ assertEquals(uriValue, target.toASCIIString());
+ }
+
+ public void testNullSerialization() throws Exception {
+ testNullSerializationAndDeserialization(Boolean.class);
+ testNullSerializationAndDeserialization(Byte.class);
+ testNullSerializationAndDeserialization(Short.class);
+ testNullSerializationAndDeserialization(Integer.class);
+ testNullSerializationAndDeserialization(Long.class);
+ testNullSerializationAndDeserialization(Double.class);
+ testNullSerializationAndDeserialization(Float.class);
+ testNullSerializationAndDeserialization(Number.class);
+ testNullSerializationAndDeserialization(Character.class);
+ testNullSerializationAndDeserialization(String.class);
+ testNullSerializationAndDeserialization(StringBuilder.class);
+ testNullSerializationAndDeserialization(StringBuffer.class);
+ testNullSerializationAndDeserialization(BigDecimal.class);
+ testNullSerializationAndDeserialization(BigInteger.class);
+ testNullSerializationAndDeserialization(TreeSet.class);
+ testNullSerializationAndDeserialization(ArrayList.class);
+ testNullSerializationAndDeserialization(HashSet.class);
+ testNullSerializationAndDeserialization(Properties.class);
+ testNullSerializationAndDeserialization(URL.class);
+ testNullSerializationAndDeserialization(URI.class);
+ testNullSerializationAndDeserialization(UUID.class);
+ testNullSerializationAndDeserialization(Locale.class);
+ testNullSerializationAndDeserialization(InetAddress.class);
+ testNullSerializationAndDeserialization(BitSet.class);
+ testNullSerializationAndDeserialization(Date.class);
+ testNullSerializationAndDeserialization(GregorianCalendar.class);
+ testNullSerializationAndDeserialization(Calendar.class);
+ testNullSerializationAndDeserialization(Time.class);
+ testNullSerializationAndDeserialization(Timestamp.class);
+ testNullSerializationAndDeserialization(java.sql.Date.class);
+ testNullSerializationAndDeserialization(Enum.class);
+ testNullSerializationAndDeserialization(Class.class);
+ }
+
+ private void testNullSerializationAndDeserialization(Class<?> c) {
+ assertEquals("null", gson.toJson(null, c));
+ assertEquals(null, gson.fromJson("null", c));
+ }
+
+ public void testUuidSerialization() throws Exception {
+ String uuidValue = "c237bec1-19ef-4858-a98e-521cf0aad4c0";
+ UUID uuid = UUID.fromString(uuidValue);
+ assertEquals('"' + uuidValue + '"', gson.toJson(uuid));
+ }
+
+ public void testUuidDeserialization() {
+ String uuidValue = "c237bec1-19ef-4858-a98e-521cf0aad4c0";
+ String json = '"' + uuidValue + '"';
+ UUID target = gson.fromJson(json, UUID.class);
+ assertEquals(uuidValue, target.toString());
+ }
+
+ public void testLocaleSerializationWithLanguage() {
+ Locale target = new Locale("en");
+ assertEquals("\"en\"", gson.toJson(target));
+ }
+
+ public void testLocaleDeserializationWithLanguage() {
+ String json = "\"en\"";
+ Locale locale = gson.fromJson(json, Locale.class);
+ assertEquals("en", locale.getLanguage());
+ }
+
+ public void testLocaleSerializationWithLanguageCountry() {
+ Locale target = Locale.CANADA_FRENCH;
+ assertEquals("\"fr_CA\"", gson.toJson(target));
+ }
+
+ public void testLocaleDeserializationWithLanguageCountry() {
+ String json = "\"fr_CA\"";
+ Locale locale = gson.fromJson(json, Locale.class);
+ assertEquals(Locale.CANADA_FRENCH, locale);
+ }
+
+ public void testLocaleSerializationWithLanguageCountryVariant() {
+ Locale target = new Locale("de", "DE", "EURO");
+ String json = gson.toJson(target);
+ assertEquals("\"de_DE_EURO\"", json);
+ }
+
+ public void testLocaleDeserializationWithLanguageCountryVariant() {
+ String json = "\"de_DE_EURO\"";
+ Locale locale = gson.fromJson(json, Locale.class);
+ assertEquals("de", locale.getLanguage());
+ assertEquals("DE", locale.getCountry());
+ assertEquals("EURO", locale.getVariant());
+ }
+
+ public void testBigDecimalFieldSerialization() {
+ ClassWithBigDecimal target = new ClassWithBigDecimal("-122.01e-21");
+ String json = gson.toJson(target);
+ String actual = json.substring(json.indexOf(':') + 1, json.indexOf('}'));
+ assertEquals(target.value, new BigDecimal(actual));
+ }
+
+ public void testBigDecimalFieldDeserialization() {
+ ClassWithBigDecimal expected = new ClassWithBigDecimal("-122.01e-21");
+ String json = expected.getExpectedJson();
+ ClassWithBigDecimal actual = gson.fromJson(json, ClassWithBigDecimal.class);
+ assertEquals(expected.value, actual.value);
+ }
+
+ public void testBadValueForBigDecimalDeserialization() {
+ try {
+ gson.fromJson("{\"value\"=1.5e-1.0031}", ClassWithBigDecimal.class);
+ fail("Exponent of a BigDecimal must be an integer value.");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testBigIntegerFieldSerialization() {
+ ClassWithBigInteger target = new ClassWithBigInteger("23232323215323234234324324324324324324");
+ String json = gson.toJson(target);
+ assertEquals(target.getExpectedJson(), json);
+ }
+
+ public void testBigIntegerFieldDeserialization() {
+ ClassWithBigInteger expected = new ClassWithBigInteger("879697697697697697697697697697697697");
+ String json = expected.getExpectedJson();
+ ClassWithBigInteger actual = gson.fromJson(json, ClassWithBigInteger.class);
+ assertEquals(expected.value, actual.value);
+ }
+
+ public void testOverrideBigIntegerTypeAdapter() throws Exception {
+ gson = new GsonBuilder()
+ .registerTypeAdapter(BigInteger.class, new NumberAsStringAdapter(BigInteger.class))
+ .create();
+ assertEquals("\"123\"", gson.toJson(new BigInteger("123"), BigInteger.class));
+ assertEquals(new BigInteger("123"), gson.fromJson("\"123\"", BigInteger.class));
+ }
+
+ public void testOverrideBigDecimalTypeAdapter() throws Exception {
+ gson = new GsonBuilder()
+ .registerTypeAdapter(BigDecimal.class, new NumberAsStringAdapter(BigDecimal.class))
+ .create();
+ assertEquals("\"1.1\"", gson.toJson(new BigDecimal("1.1"), BigDecimal.class));
+ assertEquals(new BigDecimal("1.1"), gson.fromJson("\"1.1\"", BigDecimal.class));
+ }
+
+ public void testSetSerialization() throws Exception {
+ Gson gson = new Gson();
+ HashSet<String> s = new HashSet<String>();
+ s.add("blah");
+ String json = gson.toJson(s);
+ assertEquals("[\"blah\"]", json);
+
+ json = gson.toJson(s, Set.class);
+ assertEquals("[\"blah\"]", json);
+ }
+
+ public void testBitSetSerialization() throws Exception {
+ Gson gson = new Gson();
+ BitSet bits = new BitSet();
+ bits.set(1);
+ bits.set(3, 6);
+ bits.set(9);
+ String json = gson.toJson(bits);
+ assertEquals("[0,1,0,1,1,1,0,0,0,1]", json);
+ }
+
+ public void testBitSetDeserialization() throws Exception {
+ BitSet expected = new BitSet();
+ expected.set(0);
+ expected.set(2, 6);
+ expected.set(8);
+
+ Gson gson = new Gson();
+ String json = gson.toJson(expected);
+ assertEquals(expected, gson.fromJson(json, BitSet.class));
+
+ json = "[1,0,1,1,1,1,0,0,1,0,0,0]";
+ assertEquals(expected, gson.fromJson(json, BitSet.class));
+
+ json = "[\"1\",\"0\",\"1\",\"1\",\"1\",\"1\",\"0\",\"0\",\"1\"]";
+ assertEquals(expected, gson.fromJson(json, BitSet.class));
+
+ json = "[true,false,true,true,true,true,false,false,true,false,false]";
+ assertEquals(expected, gson.fromJson(json, BitSet.class));
+ }
+
+ public void testDefaultDateSerialization() {
+ Date now = new Date(1315806903103L);
+ String json = gson.toJson(now);
+ assertEquals("\"Sep 11, 2011 10:55:03 PM\"", json);
+ }
+
+ public void testDefaultDateDeserialization() {
+ String json = "'Dec 13, 2009 07:18:02 AM'";
+ Date extracted = gson.fromJson(json, Date.class);
+ assertEqualsDate(extracted, 2009, 11, 13);
+ assertEqualsTime(extracted, 7, 18, 2);
+ }
+
+ // Date can not directly be compared with another instance since the deserialization loses the
+ // millisecond portion.
+ @SuppressWarnings("deprecation")
+ private void assertEqualsDate(Date date, int year, int month, int day) {
+ assertEquals(year-1900, date.getYear());
+ assertEquals(month, date.getMonth());
+ assertEquals(day, date.getDate());
+ }
+
+ @SuppressWarnings("deprecation")
+ private void assertEqualsTime(Date date, int hours, int minutes, int seconds) {
+ assertEquals(hours, date.getHours());
+ assertEquals(minutes, date.getMinutes());
+ assertEquals(seconds, date.getSeconds());
+ }
+
+ public void testDefaultJavaSqlDateSerialization() {
+ java.sql.Date instant = new java.sql.Date(1259875082000L);
+ String json = gson.toJson(instant);
+ assertEquals("\"Dec 3, 2009\"", json);
+ }
+
+ public void testDefaultJavaSqlDateDeserialization() {
+ String json = "'Dec 3, 2009'";
+ java.sql.Date extracted = gson.fromJson(json, java.sql.Date.class);
+ assertEqualsDate(extracted, 2009, 11, 3);
+ }
+
+ public void testDefaultJavaSqlTimestampSerialization() {
+ Timestamp now = new java.sql.Timestamp(1259875082000L);
+ String json = gson.toJson(now);
+ assertEquals("\"Dec 3, 2009 1:18:02 PM\"", json);
+ }
+
+ public void testDefaultJavaSqlTimestampDeserialization() {
+ String json = "'Dec 3, 2009 1:18:02 PM'";
+ Timestamp extracted = gson.fromJson(json, Timestamp.class);
+ assertEqualsDate(extracted, 2009, 11, 3);
+ assertEqualsTime(extracted, 13, 18, 2);
+ }
+
+ public void testDefaultJavaSqlTimeSerialization() {
+ Time now = new Time(1259875082000L);
+ String json = gson.toJson(now);
+ assertEquals("\"01:18:02 PM\"", json);
+ }
+
+ public void testDefaultJavaSqlTimeDeserialization() {
+ String json = "'1:18:02 PM'";
+ Time extracted = gson.fromJson(json, Time.class);
+ assertEqualsTime(extracted, 13, 18, 2);
+ }
+
+ public void testDefaultDateSerializationUsingBuilder() throws Exception {
+ Gson gson = new GsonBuilder().create();
+ Date now = new Date(1315806903103L);
+ String json = gson.toJson(now);
+ assertEquals("\"Sep 11, 2011 10:55:03 PM\"", json);
+ }
+
+ public void testDefaultDateDeserializationUsingBuilder() throws Exception {
+ Gson gson = new GsonBuilder().create();
+ Date now = new Date(1315806903103L);
+ String json = gson.toJson(now);
+ Date extracted = gson.fromJson(json, Date.class);
+ assertEquals(now.toString(), extracted.toString());
+ }
+
+ public void testDefaultCalendarSerialization() throws Exception {
+ Gson gson = new GsonBuilder().create();
+ String json = gson.toJson(Calendar.getInstance());
+ assertTrue(json.contains("year"));
+ assertTrue(json.contains("month"));
+ assertTrue(json.contains("dayOfMonth"));
+ assertTrue(json.contains("hourOfDay"));
+ assertTrue(json.contains("minute"));
+ assertTrue(json.contains("second"));
+ }
+
+ public void testDefaultCalendarDeserialization() throws Exception {
+ Gson gson = new GsonBuilder().create();
+ String json = "{year:2009,month:2,dayOfMonth:11,hourOfDay:14,minute:29,second:23}";
+ Calendar cal = gson.fromJson(json, Calendar.class);
+ assertEquals(2009, cal.get(Calendar.YEAR));
+ assertEquals(2, cal.get(Calendar.MONTH));
+ assertEquals(11, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(14, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(29, cal.get(Calendar.MINUTE));
+ assertEquals(23, cal.get(Calendar.SECOND));
+ }
+
+ public void testDefaultGregorianCalendarSerialization() throws Exception {
+ Gson gson = new GsonBuilder().create();
+ GregorianCalendar cal = new GregorianCalendar();
+ String json = gson.toJson(cal);
+ assertTrue(json.contains("year"));
+ assertTrue(json.contains("month"));
+ assertTrue(json.contains("dayOfMonth"));
+ assertTrue(json.contains("hourOfDay"));
+ assertTrue(json.contains("minute"));
+ assertTrue(json.contains("second"));
+ }
+
+ public void testDefaultGregorianCalendarDeserialization() throws Exception {
+ Gson gson = new GsonBuilder().create();
+ String json = "{year:2009,month:2,dayOfMonth:11,hourOfDay:14,minute:29,second:23}";
+ GregorianCalendar cal = gson.fromJson(json, GregorianCalendar.class);
+ assertEquals(2009, cal.get(Calendar.YEAR));
+ assertEquals(2, cal.get(Calendar.MONTH));
+ assertEquals(11, cal.get(Calendar.DAY_OF_MONTH));
+ assertEquals(14, cal.get(Calendar.HOUR_OF_DAY));
+ assertEquals(29, cal.get(Calendar.MINUTE));
+ assertEquals(23, cal.get(Calendar.SECOND));
+ }
+
+ public void testDateSerializationWithPattern() throws Exception {
+ String pattern = "yyyy-MM-dd";
+ Gson gson = new GsonBuilder().setDateFormat(DateFormat.FULL).setDateFormat(pattern).create();
+ Date now = new Date(1315806903103L);
+ String json = gson.toJson(now);
+ assertEquals("\"2011-09-11\"", json);
+ }
+
+ @SuppressWarnings("deprecation")
+ public void testDateDeserializationWithPattern() throws Exception {
+ String pattern = "yyyy-MM-dd";
+ Gson gson = new GsonBuilder().setDateFormat(DateFormat.FULL).setDateFormat(pattern).create();
+ Date now = new Date(1315806903103L);
+ String json = gson.toJson(now);
+ Date extracted = gson.fromJson(json, Date.class);
+ assertEquals(now.getYear(), extracted.getYear());
+ assertEquals(now.getMonth(), extracted.getMonth());
+ assertEquals(now.getDay(), extracted.getDay());
+ }
+
+ public void testDateSerializationWithPatternNotOverridenByTypeAdapter() throws Exception {
+ String pattern = "yyyy-MM-dd";
+ Gson gson = new GsonBuilder()
+ .setDateFormat(pattern)
+ .registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
+ public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return new Date(1315806903103L);
+ }
+ })
+ .create();
+
+ Date now = new Date(1315806903103L);
+ String json = gson.toJson(now);
+ assertEquals("\"2011-09-11\"", json);
+ }
+
+ // http://code.google.com/p/google-gson/issues/detail?id=230
+ public void testDateSerializationInCollection() throws Exception {
+ Type listOfDates = new TypeToken<List<Date>>() {}.getType();
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ try {
+ Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
+ List<Date> dates = Arrays.asList(new Date(0));
+ String json = gson.toJson(dates, listOfDates);
+ assertEquals("[\"1970-01-01\"]", json);
+ assertEquals(0L, gson.<List<Date>>fromJson("[\"1970-01-01\"]", listOfDates).get(0).getTime());
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ // http://code.google.com/p/google-gson/issues/detail?id=230
+ public void testTimestampSerialization() throws Exception {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ try {
+ Timestamp timestamp = new Timestamp(0L);
+ Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
+ String json = gson.toJson(timestamp, Timestamp.class);
+ assertEquals("\"1970-01-01\"", json);
+ assertEquals(0, gson.fromJson("\"1970-01-01\"", Timestamp.class).getTime());
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ // http://code.google.com/p/google-gson/issues/detail?id=230
+ public void testSqlDateSerialization() throws Exception {
+ TimeZone defaultTimeZone = TimeZone.getDefault();
+ TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
+ Locale defaultLocale = Locale.getDefault();
+ Locale.setDefault(Locale.US);
+ try {
+ java.sql.Date sqlDate = new java.sql.Date(0L);
+ Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd").create();
+ String json = gson.toJson(sqlDate, Timestamp.class);
+ assertEquals("\"1970-01-01\"", json);
+ assertEquals(0, gson.fromJson("\"1970-01-01\"", java.sql.Date.class).getTime());
+ } finally {
+ TimeZone.setDefault(defaultTimeZone);
+ Locale.setDefault(defaultLocale);
+ }
+ }
+
+ public void testJsonPrimitiveSerialization() {
+ assertEquals("5", gson.toJson(new JsonPrimitive(5), JsonElement.class));
+ assertEquals("true", gson.toJson(new JsonPrimitive(true), JsonElement.class));
+ assertEquals("\"foo\"", gson.toJson(new JsonPrimitive("foo"), JsonElement.class));
+ assertEquals("\"a\"", gson.toJson(new JsonPrimitive('a'), JsonElement.class));
+ }
+
+ public void testJsonPrimitiveDeserialization() {
+ assertEquals(new JsonPrimitive(5), gson.fromJson("5", JsonElement.class));
+ assertEquals(new JsonPrimitive(5), gson.fromJson("5", JsonPrimitive.class));
+ assertEquals(new JsonPrimitive(true), gson.fromJson("true", JsonElement.class));
+ assertEquals(new JsonPrimitive(true), gson.fromJson("true", JsonPrimitive.class));
+ assertEquals(new JsonPrimitive("foo"), gson.fromJson("\"foo\"", JsonElement.class));
+ assertEquals(new JsonPrimitive("foo"), gson.fromJson("\"foo\"", JsonPrimitive.class));
+ assertEquals(new JsonPrimitive('a'), gson.fromJson("\"a\"", JsonElement.class));
+ assertEquals(new JsonPrimitive('a'), gson.fromJson("\"a\"", JsonPrimitive.class));
+ }
+
+ public void testJsonNullSerialization() {
+ assertEquals("null", gson.toJson(JsonNull.INSTANCE, JsonElement.class));
+ assertEquals("null", gson.toJson(JsonNull.INSTANCE, JsonNull.class));
+ }
+
+ public void testNullJsonElementSerialization() {
+ assertEquals("null", gson.toJson(null, JsonElement.class));
+ assertEquals("null", gson.toJson(null, JsonNull.class));
+ }
+
+ public void testJsonArraySerialization() {
+ JsonArray array = new JsonArray();
+ array.add(new JsonPrimitive(1));
+ array.add(new JsonPrimitive(2));
+ array.add(new JsonPrimitive(3));
+ assertEquals("[1,2,3]", gson.toJson(array, JsonElement.class));
+ }
+
+ public void testJsonArrayDeserialization() {
+ JsonArray array = new JsonArray();
+ array.add(new JsonPrimitive(1));
+ array.add(new JsonPrimitive(2));
+ array.add(new JsonPrimitive(3));
+
+ String json = "[1,2,3]";
+ assertEquals(array, gson.fromJson(json, JsonElement.class));
+ assertEquals(array, gson.fromJson(json, JsonArray.class));
+ }
+
+ public void testJsonObjectSerialization() {
+ JsonObject object = new JsonObject();
+ object.add("foo", new JsonPrimitive(1));
+ object.add("bar", new JsonPrimitive(2));
+ assertEquals("{\"foo\":1,\"bar\":2}", gson.toJson(object, JsonElement.class));
+ }
+
+ public void testJsonObjectDeserialization() {
+ JsonObject object = new JsonObject();
+ object.add("foo", new JsonPrimitive(1));
+ object.add("bar", new JsonPrimitive(2));
+
+ String json = "{\"foo\":1,\"bar\":2}";
+ JsonElement actual = gson.fromJson(json, JsonElement.class);
+ assertEquals(object, actual);
+
+ JsonObject actualObj = gson.fromJson(json, JsonObject.class);
+ assertEquals(object, actualObj);
+ }
+
+ public void testJsonNullDeserialization() {
+ assertEquals(JsonNull.INSTANCE, gson.fromJson("null", JsonElement.class));
+ assertEquals(JsonNull.INSTANCE, gson.fromJson("null", JsonNull.class));
+ }
+
+ private static class ClassWithBigDecimal {
+ BigDecimal value;
+ ClassWithBigDecimal(String value) {
+ this.value = new BigDecimal(value);
+ }
+ String getExpectedJson() {
+ return "{\"value\":" + value.toEngineeringString() + "}";
+ }
+ }
+
+ private static class ClassWithBigInteger {
+ BigInteger value;
+ ClassWithBigInteger(String value) {
+ this.value = new BigInteger(value);
+ }
+ String getExpectedJson() {
+ return "{\"value\":" + value + "}";
+ }
+ }
+
+ public void testPropertiesSerialization() {
+ Properties props = new Properties();
+ props.setProperty("foo", "bar");
+ String json = gson.toJson(props);
+ String expected = "{\"foo\":\"bar\"}";
+ assertEquals(expected, json);
+ }
+
+ public void testPropertiesDeserialization() {
+ String json = "{foo:'bar'}";
+ Properties props = gson.fromJson(json, Properties.class);
+ assertEquals("bar", props.getProperty("foo"));
+ }
+
+ public void testTreeSetSerialization() {
+ TreeSet<String> treeSet = new TreeSet<String>();
+ treeSet.add("Value1");
+ String json = gson.toJson(treeSet);
+ assertEquals("[\"Value1\"]", json);
+ }
+
+ public void testTreeSetDeserialization() {
+ String json = "['Value1']";
+ Type type = new TypeToken<TreeSet<String>>() {}.getType();
+ TreeSet<String> treeSet = gson.fromJson(json, type);
+ assertTrue(treeSet.contains("Value1"));
+ }
+
+ public void testStringBuilderSerialization() {
+ StringBuilder sb = new StringBuilder("abc");
+ String json = gson.toJson(sb);
+ assertEquals("\"abc\"", json);
+ }
+
+ public void testStringBuilderDeserialization() {
+ StringBuilder sb = gson.fromJson("'abc'", StringBuilder.class);
+ assertEquals("abc", sb.toString());
+ }
+
+ public void testStringBufferSerialization() {
+ StringBuffer sb = new StringBuffer("abc");
+ String json = gson.toJson(sb);
+ assertEquals("\"abc\"", json);
+ }
+
+ public void testStringBufferDeserialization() {
+ StringBuffer sb = gson.fromJson("'abc'", StringBuffer.class);
+ assertEquals("abc", sb.toString());
+ }
+
+ @SuppressWarnings("rawtypes")
+ private static class MyClassTypeAdapter extends TypeAdapter<Class> {
+ @Override
+ public void write(JsonWriter out, Class value) throws IOException {
+ out.value(value.getName());
+ }
+ @Override
+ public Class read(JsonReader in) throws IOException {
+ String className = in.nextString();
+ try {
+ return Class.forName(className);
+ } catch (ClassNotFoundException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ static class NumberAsStringAdapter extends TypeAdapter<Number> {
+ private final Constructor<? extends Number> constructor;
+ NumberAsStringAdapter(Class<? extends Number> type) throws Exception {
+ this.constructor = type.getConstructor(String.class);
+ }
+ @Override public void write(JsonWriter out, Number value) throws IOException {
+ out.value(value.toString());
+ }
+ @Override public Number read(JsonReader in) throws IOException {
+ try {
+ return constructor.newInstance(in.nextString());
+ } catch (Exception e) {
+ throw new AssertionError(e);
+ }
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java b/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java
new file mode 100644
index 00000000..885330d8
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/DelegateTypeAdapterTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2012 Google 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 com.google.gson.functional;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Functional tests for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)} method.
+ *
+ * @author Inderjeet Singh
+ */
+public class DelegateTypeAdapterTest extends TestCase {
+
+ private StatsTypeAdapterFactory stats;
+ private Gson gson;
+ protected void setUp() throws Exception {
+ super.setUp();
+ stats = new StatsTypeAdapterFactory();
+ gson = new GsonBuilder()
+ .registerTypeAdapterFactory(stats)
+ .create();
+ }
+
+ public void testDelegateInvoked() {
+ List<BagOfPrimitives> bags = new ArrayList<BagOfPrimitives>();
+ for (int i = 0; i < 10; ++i) {
+ bags.add(new BagOfPrimitives(i, i, i % 2 == 0, String.valueOf(i)));
+ }
+ String json = gson.toJson(bags);
+ bags = gson.fromJson(json, new TypeToken<List<BagOfPrimitives>>(){}.getType());
+ // 11: 1 list object, and 10 entries. stats invoked on all 5 fields
+ assertEquals(51, stats.numReads);
+ assertEquals(51, stats.numWrites);
+ }
+
+ public void testDelegateInvokedOnStrings() {
+ String[] bags = {"1", "2", "3", "4"};
+ String json = gson.toJson(bags);
+ bags = gson.fromJson(json, String[].class);
+ // 1 array object with 4 elements.
+ assertEquals(5, stats.numReads);
+ assertEquals(5, stats.numWrites);
+ }
+
+ private static class StatsTypeAdapterFactory implements TypeAdapterFactory {
+ public int numReads = 0;
+ public int numWrites = 0;
+
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+ final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
+ return new TypeAdapter<T>() {
+ @Override
+ public void write(JsonWriter out, T value) throws IOException {
+ ++numWrites;
+ delegate.write(out, value);
+ }
+
+ @Override
+ public T read(JsonReader in) throws IOException {
+ ++numReads;
+ return delegate.read(in);
+ }
+ };
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/EnumTest.java b/gson/src/test/java/com/google/gson/functional/EnumTest.java
new file mode 100644
index 00000000..2c21526d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/EnumTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.common.MoreAsserts;
+import com.google.gson.reflect.TypeToken;
+
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Set;
+
+import junit.framework.TestCase;
+/**
+ * Functional tests for Java 5.0 enums.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class EnumTest extends TestCase {
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testTopLevelEnumSerialization() throws Exception {
+ String result = gson.toJson(MyEnum.VALUE1);
+ assertEquals('"' + MyEnum.VALUE1.toString() + '"', result);
+ }
+
+ public void testTopLevelEnumDeserialization() throws Exception {
+ MyEnum result = gson.fromJson('"' + MyEnum.VALUE1.toString() + '"', MyEnum.class);
+ assertEquals(MyEnum.VALUE1, result);
+ }
+
+ public void testCollectionOfEnumsSerialization() {
+ Type type = new TypeToken<Collection<MyEnum>>() {}.getType();
+ Collection<MyEnum> target = new ArrayList<MyEnum>();
+ target.add(MyEnum.VALUE1);
+ target.add(MyEnum.VALUE2);
+ String expectedJson = "[\"VALUE1\",\"VALUE2\"]";
+ String actualJson = gson.toJson(target);
+ assertEquals(expectedJson, actualJson);
+ actualJson = gson.toJson(target, type);
+ assertEquals(expectedJson, actualJson);
+ }
+
+ public void testCollectionOfEnumsDeserialization() {
+ Type type = new TypeToken<Collection<MyEnum>>() {}.getType();
+ String json = "[\"VALUE1\",\"VALUE2\"]";
+ Collection<MyEnum> target = gson.fromJson(json, type);
+ MoreAsserts.assertContains(target, MyEnum.VALUE1);
+ MoreAsserts.assertContains(target, MyEnum.VALUE2);
+ }
+
+ public void testClassWithEnumFieldSerialization() throws Exception {
+ ClassWithEnumFields target = new ClassWithEnumFields();
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testClassWithEnumFieldDeserialization() throws Exception {
+ String json = "{value1:'VALUE1',value2:'VALUE2'}";
+ ClassWithEnumFields target = gson.fromJson(json, ClassWithEnumFields.class);
+ assertEquals(MyEnum.VALUE1,target.value1);
+ assertEquals(MyEnum.VALUE2,target.value2);
+ }
+
+ private static enum MyEnum {
+ VALUE1, VALUE2
+ }
+
+ private static class ClassWithEnumFields {
+ private final MyEnum value1 = MyEnum.VALUE1;
+ private final MyEnum value2 = MyEnum.VALUE2;
+ public String getExpectedJson() {
+ return "{\"value1\":\"" + value1 + "\",\"value2\":\"" + value2 + "\"}";
+ }
+ }
+
+ /**
+ * Test for issue 226.
+ */
+ public void testEnumSubclass() {
+ assertFalse(Roshambo.class == Roshambo.ROCK.getClass());
+ assertEquals("\"ROCK\"", gson.toJson(Roshambo.ROCK));
+ assertEquals("[\"ROCK\",\"PAPER\",\"SCISSORS\"]", gson.toJson(EnumSet.allOf(Roshambo.class)));
+ assertEquals(Roshambo.ROCK, gson.fromJson("\"ROCK\"", Roshambo.class));
+ assertEquals(EnumSet.allOf(Roshambo.class),
+ gson.fromJson("[\"ROCK\",\"PAPER\",\"SCISSORS\"]", new TypeToken<Set<Roshambo>>() {}.getType()));
+ }
+
+ public void testEnumSubclassWithRegisteredTypeAdapter() {
+ gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Roshambo.class, new MyEnumTypeAdapter())
+ .create();
+ assertFalse(Roshambo.class == Roshambo.ROCK.getClass());
+ assertEquals("\"123ROCK\"", gson.toJson(Roshambo.ROCK));
+ assertEquals("[\"123ROCK\",\"123PAPER\",\"123SCISSORS\"]", gson.toJson(EnumSet.allOf(Roshambo.class)));
+ assertEquals(Roshambo.ROCK, gson.fromJson("\"123ROCK\"", Roshambo.class));
+ assertEquals(EnumSet.allOf(Roshambo.class),
+ gson.fromJson("[\"123ROCK\",\"123PAPER\",\"123SCISSORS\"]", new TypeToken<Set<Roshambo>>() {}.getType()));
+ }
+
+ public void testEnumSubclassAsParameterizedType() {
+ Collection<Roshambo> list = new ArrayList<Roshambo>();
+ list.add(Roshambo.ROCK);
+ list.add(Roshambo.PAPER);
+
+ String json = gson.toJson(list);
+ assertEquals("[\"ROCK\",\"PAPER\"]", json);
+
+ Type collectionType = new TypeToken<Collection<Roshambo>>() {}.getType();
+ Collection<Roshambo> actualJsonList = gson.fromJson(json, collectionType);
+ MoreAsserts.assertContains(actualJsonList, Roshambo.ROCK);
+ MoreAsserts.assertContains(actualJsonList, Roshambo.PAPER);
+ }
+
+ public void testEnumCaseMapping() {
+ assertEquals(Gender.MALE, gson.fromJson("\"boy\"", Gender.class));
+ assertEquals("\"boy\"", gson.toJson(Gender.MALE, Gender.class));
+ }
+
+ public void testEnumSet() {
+ EnumSet<Roshambo> foo = EnumSet.of(Roshambo.ROCK, Roshambo.PAPER);
+ String json = gson.toJson(foo);
+ Type type = new TypeToken<EnumSet<Roshambo>>() {}.getType();
+ EnumSet<Roshambo> bar = gson.fromJson(json, type);
+ assertTrue(bar.contains(Roshambo.ROCK));
+ assertTrue(bar.contains(Roshambo.PAPER));
+ assertFalse(bar.contains(Roshambo.SCISSORS));
+ }
+
+ public enum Roshambo {
+ ROCK {
+ @Override Roshambo defeats() {
+ return SCISSORS;
+ }
+ },
+ PAPER {
+ @Override Roshambo defeats() {
+ return ROCK;
+ }
+ },
+ SCISSORS {
+ @Override Roshambo defeats() {
+ return PAPER;
+ }
+ };
+
+ abstract Roshambo defeats();
+ }
+
+ private static class MyEnumTypeAdapter
+ implements JsonSerializer<Roshambo>, JsonDeserializer<Roshambo> {
+ public JsonElement serialize(Roshambo src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive("123" + src.name());
+ }
+
+ public Roshambo deserialize(JsonElement json, Type classOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ return Roshambo.valueOf(json.getAsString().substring(3));
+ }
+ }
+
+ public enum Gender {
+ @SerializedName("boy")
+ MALE,
+
+ @SerializedName("girl")
+ FEMALE
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/EscapingTest.java b/gson/src/test/java/com/google/gson/functional/EscapingTest.java
new file mode 100644
index 00000000..1581f451
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/EscapingTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * Performs some functional test involving JSON output escaping.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class EscapingTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testEscapingQuotesInStringArray() throws Exception {
+ String[] valueWithQuotes = { "beforeQuote\"afterQuote" };
+ String jsonRepresentation = gson.toJson(valueWithQuotes);
+ String[] target = gson.fromJson(jsonRepresentation, String[].class);
+ assertEquals(1, target.length);
+ assertEquals(valueWithQuotes[0], target[0]);
+ }
+
+ public void testEscapeAllHtmlCharacters() {
+ List<String> strings = new ArrayList<String>();
+ strings.add("<");
+ strings.add(">");
+ strings.add("=");
+ strings.add("&");
+ strings.add("'");
+ strings.add("\"");
+ assertEquals("[\"\\u003c\",\"\\u003e\",\"\\u003d\",\"\\u0026\",\"\\u0027\",\"\\\"\"]",
+ gson.toJson(strings));
+ }
+
+ public void testEscapingObjectFields() throws Exception {
+ BagOfPrimitives objWithPrimitives = new BagOfPrimitives(1L, 1, true, "test with\" <script>");
+ String jsonRepresentation = gson.toJson(objWithPrimitives);
+ assertFalse(jsonRepresentation.contains("<"));
+ assertFalse(jsonRepresentation.contains(">"));
+ assertTrue(jsonRepresentation.contains("\\\""));
+
+ BagOfPrimitives expectedObject = gson.fromJson(jsonRepresentation, BagOfPrimitives.class);
+ assertEquals(objWithPrimitives.getExpectedJson(), expectedObject.getExpectedJson());
+ }
+
+ public void testGsonAcceptsEscapedAndNonEscapedJsonDeserialization() throws Exception {
+ Gson escapeHtmlGson = new GsonBuilder().create();
+ Gson noEscapeHtmlGson = new GsonBuilder().disableHtmlEscaping().create();
+
+ BagOfPrimitives target = new BagOfPrimitives(1L, 1, true, "test' / w'ith\" / \\ <script>");
+ String escapedJsonForm = escapeHtmlGson.toJson(target);
+ String nonEscapedJsonForm = noEscapeHtmlGson.toJson(target);
+ assertFalse(escapedJsonForm.equals(nonEscapedJsonForm));
+
+ assertEquals(target, noEscapeHtmlGson.fromJson(escapedJsonForm, BagOfPrimitives.class));
+ assertEquals(target, escapeHtmlGson.fromJson(nonEscapedJsonForm, BagOfPrimitives.class));
+ }
+
+ public void testGsonDoubleDeserialization() {
+ BagOfPrimitives expected = new BagOfPrimitives(3L, 4, true, "value1");
+ String json = gson.toJson(gson.toJson(expected));
+ String value = gson.fromJson(json, String.class);
+ BagOfPrimitives actual = gson.fromJson(value, BagOfPrimitives.class);
+ assertEquals(expected, actual);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ExclusionStrategyFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/ExclusionStrategyFunctionalTest.java
new file mode 100644
index 00000000..baeab840
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ExclusionStrategyFunctionalTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import junit.framework.TestCase;
+
+/**
+ * Performs some functional tests when Gson is instantiated with some common user defined
+ * {@link ExclusionStrategy} objects.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ExclusionStrategyFunctionalTest extends TestCase {
+ private static final ExclusionStrategy EXCLUDE_SAMPLE_OBJECT_FOR_TEST = new ExclusionStrategy() {
+ public boolean shouldSkipField(FieldAttributes f) {
+ return false;
+ }
+ public boolean shouldSkipClass(Class<?> clazz) {
+ return clazz == SampleObjectForTest.class;
+ }
+ };
+
+ private SampleObjectForTest src;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ src = new SampleObjectForTest();
+ }
+
+ public void testExclusionStrategySerialization() throws Exception {
+ Gson gson = createGson(new MyExclusionStrategy(String.class), true);
+ String json = gson.toJson(src);
+ assertFalse(json.contains("\"stringField\""));
+ assertFalse(json.contains("\"annotatedField\""));
+ assertTrue(json.contains("\"longField\""));
+ }
+
+ public void testExclusionStrategySerializationDoesNotImpactDeserialization() {
+ String json = "{\"annotatedField\":1,\"stringField\":\"x\",\"longField\":2}";
+ Gson gson = createGson(new MyExclusionStrategy(String.class), true);
+ SampleObjectForTest value = gson.fromJson(json, SampleObjectForTest.class);
+ assertEquals(1, value.annotatedField);
+ assertEquals("x", value.stringField);
+ assertEquals(2, value.longField);
+ }
+
+ public void testExclusionStrategyDeserialization() throws Exception {
+ Gson gson = createGson(new MyExclusionStrategy(String.class), false);
+ JsonObject json = new JsonObject();
+ json.add("annotatedField", new JsonPrimitive(src.annotatedField + 5));
+ json.add("stringField", new JsonPrimitive(src.stringField + "blah,blah"));
+ json.add("longField", new JsonPrimitive(1212311L));
+
+ SampleObjectForTest target = gson.fromJson(json, SampleObjectForTest.class);
+ assertEquals(1212311L, target.longField);
+
+ // assert excluded fields are set to the defaults
+ assertEquals(src.annotatedField, target.annotatedField);
+ assertEquals(src.stringField, target.stringField);
+ }
+
+ public void testExclusionStrategySerializationDoesNotImpactSerialization() throws Exception {
+ Gson gson = createGson(new MyExclusionStrategy(String.class), false);
+ String json = gson.toJson(src);
+ assertTrue(json.contains("\"stringField\""));
+ assertTrue(json.contains("\"annotatedField\""));
+ assertTrue(json.contains("\"longField\""));
+ }
+
+ public void testExclusionStrategyWithMode() throws Exception {
+ SampleObjectForTest testObj = new SampleObjectForTest(
+ src.annotatedField + 5, src.stringField + "blah,blah",
+ src.longField + 655L);
+
+ Gson gson = createGson(new MyExclusionStrategy(String.class), false);
+ JsonObject json = gson.toJsonTree(testObj).getAsJsonObject();
+ assertEquals(testObj.annotatedField, json.get("annotatedField").getAsInt());
+ assertEquals(testObj.stringField, json.get("stringField").getAsString());
+ assertEquals(testObj.longField, json.get("longField").getAsLong());
+
+ SampleObjectForTest target = gson.fromJson(json, SampleObjectForTest.class);
+ assertEquals(testObj.longField, target.longField);
+
+ // assert excluded fields are set to the defaults
+ assertEquals(src.annotatedField, target.annotatedField);
+ assertEquals(src.stringField, target.stringField);
+ }
+
+ public void testExcludeTopLevelClassSerialization() {
+ Gson gson = new GsonBuilder()
+ .addSerializationExclusionStrategy(EXCLUDE_SAMPLE_OBJECT_FOR_TEST)
+ .create();
+ assertEquals("null", gson.toJson(new SampleObjectForTest(), SampleObjectForTest.class));
+ }
+
+ public void testExcludeTopLevelClassSerializationDoesNotImpactDeserialization() {
+ Gson gson = new GsonBuilder()
+ .addSerializationExclusionStrategy(EXCLUDE_SAMPLE_OBJECT_FOR_TEST)
+ .create();
+ String json = "{\"annotatedField\":1,\"stringField\":\"x\",\"longField\":2}";
+ SampleObjectForTest value = gson.fromJson(json, SampleObjectForTest.class);
+ assertEquals(1, value.annotatedField);
+ assertEquals("x", value.stringField);
+ assertEquals(2, value.longField);
+ }
+
+ public void testExcludeTopLevelClassDeserialization() {
+ Gson gson = new GsonBuilder()
+ .addDeserializationExclusionStrategy(EXCLUDE_SAMPLE_OBJECT_FOR_TEST)
+ .create();
+ String json = "{\"annotatedField\":1,\"stringField\":\"x\",\"longField\":2}";
+ SampleObjectForTest value = gson.fromJson(json, SampleObjectForTest.class);
+ assertNull(value);
+ }
+
+ public void testExcludeTopLevelClassDeserializationDoesNotImpactSerialization() {
+ Gson gson = new GsonBuilder()
+ .addDeserializationExclusionStrategy(EXCLUDE_SAMPLE_OBJECT_FOR_TEST)
+ .create();
+ String json = gson.toJson(new SampleObjectForTest(), SampleObjectForTest.class);
+ assertTrue(json.contains("\"stringField\""));
+ assertTrue(json.contains("\"annotatedField\""));
+ assertTrue(json.contains("\"longField\""));
+ }
+
+ private static Gson createGson(ExclusionStrategy exclusionStrategy, boolean serialization) {
+ GsonBuilder gsonBuilder = new GsonBuilder();
+ if (serialization) {
+ gsonBuilder.addSerializationExclusionStrategy(exclusionStrategy);
+ } else {
+ gsonBuilder.addDeserializationExclusionStrategy(exclusionStrategy);
+ }
+ return gsonBuilder
+ .serializeNulls()
+ .create();
+ }
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target({ElementType.FIELD})
+ private static @interface Foo {
+ // Field tag only annotation
+ }
+
+ private static class SampleObjectForTest {
+ @Foo
+ private final int annotatedField;
+ private final String stringField;
+ private final long longField;
+
+ public SampleObjectForTest() {
+ this(5, "someDefaultValue", 12345L);
+ }
+
+ public SampleObjectForTest(int annotatedField, String stringField, long longField) {
+ this.annotatedField = annotatedField;
+ this.stringField = stringField;
+ this.longField = longField;
+ }
+ }
+
+ private static class MyExclusionStrategy implements ExclusionStrategy {
+ private final Class<?> typeToSkip;
+
+ private MyExclusionStrategy(Class<?> typeToSkip) {
+ this.typeToSkip = typeToSkip;
+ }
+
+ public boolean shouldSkipClass(Class<?> clazz) {
+ return (clazz == typeToSkip);
+ }
+
+ public boolean shouldSkipField(FieldAttributes f) {
+ return f.getAnnotation(Foo.class) != null;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java b/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java
new file mode 100644
index 00000000..0ec5c433
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ExposeFieldsTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.annotations.Expose;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for the regarding functional "@Expose" type tests.
+ *
+ * @author Joel Leitch
+ */
+public class ExposeFieldsTest extends TestCase {
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new GsonBuilder()
+ .excludeFieldsWithoutExposeAnnotation()
+ .registerTypeAdapter(SomeInterface.class, new SomeInterfaceInstanceCreator())
+ .create();
+ }
+
+ public void testNullExposeFieldSerialization() throws Exception {
+ ClassWithExposedFields object = new ClassWithExposedFields(null, 1);
+ String json = gson.toJson(object);
+
+ assertEquals(object.getExpectedJson(), json);
+ }
+
+ public void testArrayWithOneNullExposeFieldObjectSerialization() throws Exception {
+ ClassWithExposedFields object1 = new ClassWithExposedFields(1, 1);
+ ClassWithExposedFields object2 = new ClassWithExposedFields(null, 1);
+ ClassWithExposedFields object3 = new ClassWithExposedFields(2, 2);
+ ClassWithExposedFields[] objects = { object1, object2, object3 };
+
+ String json = gson.toJson(objects);
+ String expected = new StringBuilder()
+ .append('[').append(object1.getExpectedJson()).append(',')
+ .append(object2.getExpectedJson()).append(',')
+ .append(object3.getExpectedJson()).append(']')
+ .toString();
+
+ assertEquals(expected, json);
+ }
+
+ public void testExposeAnnotationSerialization() throws Exception {
+ ClassWithExposedFields target = new ClassWithExposedFields(1, 2);
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testExposeAnnotationDeserialization() throws Exception {
+ String json = "{a:3,b:4,d:20.0}";
+ ClassWithExposedFields target = gson.fromJson(json, ClassWithExposedFields.class);
+
+ assertEquals(3, (int) target.a);
+ assertNull(target.b);
+ assertFalse(target.d == 20);
+ }
+
+ public void testNoExposedFieldSerialization() throws Exception {
+ ClassWithNoExposedFields obj = new ClassWithNoExposedFields();
+ String json = gson.toJson(obj);
+
+ assertEquals("{}", json);
+ }
+
+ public void testNoExposedFieldDeserialization() throws Exception {
+ String json = "{a:4,b:5}";
+ ClassWithNoExposedFields obj = gson.fromJson(json, ClassWithNoExposedFields.class);
+
+ assertEquals(0, obj.a);
+ assertEquals(1, obj.b);
+ }
+
+ public void testExposedInterfaceFieldSerialization() throws Exception {
+ String expected = "{\"interfaceField\":{}}";
+ ClassWithInterfaceField target = new ClassWithInterfaceField(new SomeObject());
+ String actual = gson.toJson(target);
+
+ assertEquals(expected, actual);
+ }
+
+ public void testExposedInterfaceFieldDeserialization() throws Exception {
+ String json = "{\"interfaceField\":{}}";
+ ClassWithInterfaceField obj = gson.fromJson(json, ClassWithInterfaceField.class);
+
+ assertNotNull(obj.interfaceField);
+ }
+
+ private static class ClassWithExposedFields {
+ @Expose private final Integer a;
+ private final Integer b;
+ @Expose(serialize = false) final long c;
+ @Expose(deserialize = false) final double d;
+ @Expose(serialize = false, deserialize = false) final char e;
+
+ public ClassWithExposedFields(Integer a, Integer b) {
+ this(a, b, 1L, 2.0, 'a');
+ }
+ public ClassWithExposedFields(Integer a, Integer b, long c, double d, char e) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder("{");
+ if (a != null) {
+ sb.append("\"a\":").append(a).append(",");
+ }
+ sb.append("\"d\":").append(d);
+ sb.append("}");
+ return sb.toString();
+ }
+ }
+
+ private static class ClassWithNoExposedFields {
+ private final int a = 0;
+ private final int b = 1;
+ }
+
+ private static interface SomeInterface {
+ // Empty interface
+ }
+
+ private static class SomeObject implements SomeInterface {
+ // Do nothing
+ }
+
+ private static class SomeInterfaceInstanceCreator implements InstanceCreator<SomeInterface> {
+ public SomeInterface createInstance(Type type) {
+ return new SomeObject();
+ }
+ }
+
+ private static class ClassWithInterfaceField {
+ @Expose
+ private final SomeInterface interfaceField;
+
+ public ClassWithInterfaceField(SomeInterface interfaceField) {
+ this.interfaceField = interfaceField;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/FieldExclusionTest.java b/gson/src/test/java/com/google/gson/functional/FieldExclusionTest.java
new file mode 100644
index 00000000..080a8234
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/FieldExclusionTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import junit.framework.TestCase;
+
+/**
+ * Performs some functional testing to ensure GSON infrastructure properly serializes/deserializes
+ * fields that either should or should not be included in the output based on the GSON
+ * configuration.
+ *
+ * @author Joel Leitch
+ */
+public class FieldExclusionTest extends TestCase {
+ private static final String VALUE = "blah_1234";
+
+ private Outer outer;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ outer = new Outer();
+ }
+
+ public void testDefaultInnerClassExclusion() throws Exception {
+ Gson gson = new Gson();
+ Outer.Inner target = outer.new Inner(VALUE);
+ String result = gson.toJson(target);
+ assertEquals(target.toJson(), result);
+
+ gson = new GsonBuilder().create();
+ target = outer.new Inner(VALUE);
+ result = gson.toJson(target);
+ assertEquals(target.toJson(), result);
+ }
+
+ public void testInnerClassExclusion() throws Exception {
+ Gson gson = new GsonBuilder().disableInnerClassSerialization().create();
+ Outer.Inner target = outer.new Inner(VALUE);
+ String result = gson.toJson(target);
+ assertEquals("null", result);
+ }
+
+ public void testDefaultNestedStaticClassIncluded() throws Exception {
+ Gson gson = new Gson();
+ Outer.Inner target = outer.new Inner(VALUE);
+ String result = gson.toJson(target);
+ assertEquals(target.toJson(), result);
+
+ gson = new GsonBuilder().create();
+ target = outer.new Inner(VALUE);
+ result = gson.toJson(target);
+ assertEquals(target.toJson(), result);
+ }
+
+ private static class Outer {
+ private class Inner extends NestedClass {
+ public Inner(String value) {
+ super(value);
+ }
+ }
+
+ }
+
+ private static class NestedClass {
+ private final String value;
+ public NestedClass(String value) {
+ this.value = value;
+ }
+
+ public String toJson() {
+ return "{\"value\":\"" + value + "\"}";
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java b/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java
new file mode 100644
index 00000000..5d326af8
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/FieldNamingTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.functional;
+
+import static com.google.gson.FieldNamingPolicy.IDENTITY;
+import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_DASHES;
+import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
+import static com.google.gson.FieldNamingPolicy.UPPER_CAMEL_CASE;
+import static com.google.gson.FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import junit.framework.TestCase;
+
+public final class FieldNamingTest extends TestCase {
+ public void testIdentity() {
+ Gson gson = getGsonWithNamingPolicy(IDENTITY);
+ assertEquals("{'lowerCamel':1,'UpperCamel':2,'_lowerCamelLeadingUnderscore':3," +
+ "'_UpperCamelLeadingUnderscore':4,'lower_words':5,'UPPER_WORDS':6," +
+ "'annotatedName':7,'lowerId':8}",
+ gson.toJson(new TestNames()).replace('\"', '\''));
+ }
+
+ public void testUpperCamelCase() {
+ Gson gson = getGsonWithNamingPolicy(UPPER_CAMEL_CASE);
+ assertEquals("{'LowerCamel':1,'UpperCamel':2,'_LowerCamelLeadingUnderscore':3," +
+ "'_UpperCamelLeadingUnderscore':4,'Lower_words':5,'UPPER_WORDS':6," +
+ "'annotatedName':7,'LowerId':8}",
+ gson.toJson(new TestNames()).replace('\"', '\''));
+ }
+
+ public void testUpperCamelCaseWithSpaces() {
+ Gson gson = getGsonWithNamingPolicy(UPPER_CAMEL_CASE_WITH_SPACES);
+ assertEquals("{'Lower Camel':1,'Upper Camel':2,'_Lower Camel Leading Underscore':3," +
+ "'_ Upper Camel Leading Underscore':4,'Lower_words':5,'U P P E R_ W O R D S':6," +
+ "'annotatedName':7,'Lower Id':8}",
+ gson.toJson(new TestNames()).replace('\"', '\''));
+ }
+
+ public void testLowerCaseWithUnderscores() {
+ Gson gson = getGsonWithNamingPolicy(LOWER_CASE_WITH_UNDERSCORES);
+ assertEquals("{'lower_camel':1,'upper_camel':2,'_lower_camel_leading_underscore':3," +
+ "'__upper_camel_leading_underscore':4,'lower_words':5,'u_p_p_e_r__w_o_r_d_s':6," +
+ "'annotatedName':7,'lower_id':8}",
+ gson.toJson(new TestNames()).replace('\"', '\''));
+ }
+
+ public void testLowerCaseWithDashes() {
+ Gson gson = getGsonWithNamingPolicy(LOWER_CASE_WITH_DASHES);
+ assertEquals("{'lower-camel':1,'upper-camel':2,'_lower-camel-leading-underscore':3," +
+ "'_-upper-camel-leading-underscore':4,'lower_words':5,'u-p-p-e-r_-w-o-r-d-s':6," +
+ "'annotatedName':7,'lower-id':8}",
+ gson.toJson(new TestNames()).replace('\"', '\''));
+ }
+
+ private Gson getGsonWithNamingPolicy(FieldNamingPolicy fieldNamingPolicy){
+ return new GsonBuilder()
+ .setFieldNamingPolicy(fieldNamingPolicy)
+ .create();
+ }
+
+ @SuppressWarnings("unused") // fields are used reflectively
+ private static class TestNames {
+ int lowerCamel = 1;
+ int UpperCamel = 2;
+ int _lowerCamelLeadingUnderscore = 3;
+ int _UpperCamelLeadingUnderscore = 4;
+ int lower_words = 5;
+ int UPPER_WORDS = 6;
+ @SerializedName("annotatedName") int annotated = 7;
+ int lowerId = 8;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/InheritanceTest.java b/gson/src/test/java/com/google/gson/functional/InheritanceTest.java
new file mode 100644
index 00000000..b93ba0b5
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/InheritanceTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.Base;
+import com.google.gson.common.TestTypes.ClassWithBaseArrayField;
+import com.google.gson.common.TestTypes.ClassWithBaseCollectionField;
+import com.google.gson.common.TestTypes.ClassWithBaseField;
+import com.google.gson.common.TestTypes.Nested;
+import com.google.gson.common.TestTypes.Sub;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Functional tests for Json serialization and deserialization of classes with
+ * inheritance hierarchies.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class InheritanceTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testSubClassSerialization() throws Exception {
+ SubTypeOfNested target = new SubTypeOfNested(new BagOfPrimitives(10, 20, false, "stringValue"),
+ new BagOfPrimitives(30, 40, true, "stringValue"));
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testSubClassDeserialization() throws Exception {
+ String json = "{\"value\":5,\"primitive1\":{\"longValue\":10,\"intValue\":20,"
+ + "\"booleanValue\":false,\"stringValue\":\"stringValue\"},\"primitive2\":"
+ + "{\"longValue\":30,\"intValue\":40,\"booleanValue\":true,"
+ + "\"stringValue\":\"stringValue\"}}";
+ SubTypeOfNested target = gson.fromJson(json, SubTypeOfNested.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ public void testClassWithBaseFieldSerialization() {
+ ClassWithBaseField sub = new ClassWithBaseField(new Sub());
+ JsonObject json = (JsonObject) gson.toJsonTree(sub);
+ JsonElement base = json.getAsJsonObject().get(ClassWithBaseField.FIELD_KEY);
+ assertEquals(Sub.SUB_NAME, base.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString());
+ }
+
+ public void testClassWithBaseArrayFieldSerialization() {
+ Base[] baseClasses = new Base[]{ new Sub(), new Sub()};
+ ClassWithBaseArrayField sub = new ClassWithBaseArrayField(baseClasses);
+ JsonObject json = gson.toJsonTree(sub).getAsJsonObject();
+ JsonArray bases = json.get(ClassWithBaseArrayField.FIELD_KEY).getAsJsonArray();
+ for (JsonElement element : bases) {
+ assertEquals(Sub.SUB_NAME, element.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString());
+ }
+ }
+
+ public void testClassWithBaseCollectionFieldSerialization() {
+ Collection<Base> baseClasses = new ArrayList<Base>();
+ baseClasses.add(new Sub());
+ baseClasses.add(new Sub());
+ ClassWithBaseCollectionField sub = new ClassWithBaseCollectionField(baseClasses);
+ JsonObject json = gson.toJsonTree(sub).getAsJsonObject();
+ JsonArray bases = json.get(ClassWithBaseArrayField.FIELD_KEY).getAsJsonArray();
+ for (JsonElement element : bases) {
+ assertEquals(Sub.SUB_NAME, element.getAsJsonObject().get(Sub.SUB_FIELD_KEY).getAsString());
+ }
+ }
+
+ public void testBaseSerializedAsSub() {
+ Base base = new Sub();
+ JsonObject json = gson.toJsonTree(base).getAsJsonObject();
+ assertEquals(Sub.SUB_NAME, json.get(Sub.SUB_FIELD_KEY).getAsString());
+ }
+
+ public void testBaseSerializedAsSubForToJsonMethod() {
+ Base base = new Sub();
+ String json = gson.toJson(base);
+ assertTrue(json.contains(Sub.SUB_NAME));
+ }
+
+ public void testBaseSerializedAsBaseWhenSpecifiedWithExplicitType() {
+ Base base = new Sub();
+ JsonObject json = gson.toJsonTree(base, Base.class).getAsJsonObject();
+ assertEquals(Base.BASE_NAME, json.get(Base.BASE_FIELD_KEY).getAsString());
+ assertNull(json.get(Sub.SUB_FIELD_KEY));
+ }
+
+ public void testBaseSerializedAsBaseWhenSpecifiedWithExplicitTypeForToJsonMethod() {
+ Base base = new Sub();
+ String json = gson.toJson(base, Base.class);
+ assertTrue(json.contains(Base.BASE_NAME));
+ assertFalse(json.contains(Sub.SUB_FIELD_KEY));
+ }
+
+ public void testBaseSerializedAsSubWhenSpecifiedWithExplicitType() {
+ Base base = new Sub();
+ JsonObject json = gson.toJsonTree(base, Sub.class).getAsJsonObject();
+ assertEquals(Sub.SUB_NAME, json.get(Sub.SUB_FIELD_KEY).getAsString());
+ }
+
+ public void testBaseSerializedAsSubWhenSpecifiedWithExplicitTypeForToJsonMethod() {
+ Base base = new Sub();
+ String json = gson.toJson(base, Sub.class);
+ assertTrue(json.contains(Sub.SUB_NAME));
+ }
+
+ private static class SubTypeOfNested extends Nested {
+ private final long value = 5;
+
+ public SubTypeOfNested(BagOfPrimitives primitive1, BagOfPrimitives primitive2) {
+ super(primitive1, primitive2);
+ }
+
+ @Override
+ public void appendFields(StringBuilder sb) {
+ sb.append("\"value\":").append(value).append(",");
+ super.appendFields(sb);
+ }
+ }
+
+ public void testSubInterfacesOfCollectionSerialization() throws Exception {
+ List<Integer> list = new LinkedList<Integer>();
+ list.add(0);
+ list.add(1);
+ list.add(2);
+ list.add(3);
+ Queue<Long> queue = new LinkedList<Long>();
+ queue.add(0L);
+ queue.add(1L);
+ queue.add(2L);
+ queue.add(3L);
+ Set<Float> set = new TreeSet<Float>();
+ set.add(0.1F);
+ set.add(0.2F);
+ set.add(0.3F);
+ set.add(0.4F);
+ SortedSet<Character> sortedSet = new TreeSet<Character>();
+ sortedSet.add('a');
+ sortedSet.add('b');
+ sortedSet.add('c');
+ sortedSet.add('d');
+ ClassWithSubInterfacesOfCollection target =
+ new ClassWithSubInterfacesOfCollection(list, queue, set, sortedSet);
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testSubInterfacesOfCollectionDeserialization() throws Exception {
+ String json = "{\"list\":[0,1,2,3],\"queue\":[0,1,2,3],\"set\":[0.1,0.2,0.3,0.4],"
+ + "\"sortedSet\":[\"a\",\"b\",\"c\",\"d\"]"
+ + "}";
+ ClassWithSubInterfacesOfCollection target =
+ gson.fromJson(json, ClassWithSubInterfacesOfCollection.class);
+ assertTrue(target.listContains(0, 1, 2, 3));
+ assertTrue(target.queueContains(0, 1, 2, 3));
+ assertTrue(target.setContains(0.1F, 0.2F, 0.3F, 0.4F));
+ assertTrue(target.sortedSetContains('a', 'b', 'c', 'd'));
+ }
+
+ private static class ClassWithSubInterfacesOfCollection {
+ private List<Integer> list;
+ private Queue<Long> queue;
+ private Set<Float> set;
+ private SortedSet<Character> sortedSet;
+
+ public ClassWithSubInterfacesOfCollection(List<Integer> list, Queue<Long> queue, Set<Float> set,
+ SortedSet<Character> sortedSet) {
+ this.list = list;
+ this.queue = queue;
+ this.set = set;
+ this.sortedSet = sortedSet;
+ }
+
+ boolean listContains(int... values) {
+ for (int value : values) {
+ if (!list.contains(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean queueContains(long... values) {
+ for (long value : values) {
+ if (!queue.contains(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean setContains(float... values) {
+ for (float value : values) {
+ if (!set.contains(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ boolean sortedSetContains(char... values) {
+ for (char value : values) {
+ if (!sortedSet.contains(value)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("{");
+ sb.append("\"list\":");
+ append(sb, list).append(",");
+ sb.append("\"queue\":");
+ append(sb, queue).append(",");
+ sb.append("\"set\":");
+ append(sb, set).append(",");
+ sb.append("\"sortedSet\":");
+ append(sb, sortedSet);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ private StringBuilder append(StringBuilder sb, Collection<?> c) {
+ sb.append("[");
+ boolean first = true;
+ for (Object o : c) {
+ if (!first) {
+ sb.append(",");
+ } else {
+ first = false;
+ }
+ if (o instanceof String || o instanceof Character) {
+ sb.append('\"');
+ }
+ sb.append(o.toString());
+ if (o instanceof String || o instanceof Character) {
+ sb.append('\"');
+ }
+ }
+ sb.append("]");
+ return sb;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/InstanceCreatorTest.java b/gson/src/test/java/com/google/gson/functional/InstanceCreatorTest.java
new file mode 100644
index 00000000..0fda10af
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/InstanceCreatorTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2009 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.common.TestTypes.Base;
+import com.google.gson.common.TestTypes.ClassWithBaseField;
+import com.google.gson.common.TestTypes.Sub;
+
+import com.google.gson.reflect.TypeToken;
+import java.util.ArrayList;
+import java.util.List;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Functional Test exercising custom serialization only. When test applies to both
+ * serialization and deserialization then add it to CustomTypeAdapterTest.
+ *
+ * @author Inderjeet Singh
+ */
+public class InstanceCreatorTest extends TestCase {
+
+ public void testInstanceCreatorReturnsBaseType() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new InstanceCreator<Base>() {
+ public Base createInstance(Type type) {
+ return new Base();
+ }
+ })
+ .create();
+ String json = "{baseName:'BaseRevised',subName:'Sub'}";
+ Base base = gson.fromJson(json, Base.class);
+ assertEquals("BaseRevised", base.baseName);
+ }
+
+ public void testInstanceCreatorReturnsSubTypeForTopLevelObject() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new InstanceCreator<Base>() {
+ public Base createInstance(Type type) {
+ return new Sub();
+ }
+ })
+ .create();
+
+ String json = "{baseName:'Base',subName:'SubRevised'}";
+ Base base = gson.fromJson(json, Base.class);
+ assertTrue(base instanceof Sub);
+
+ Sub sub = (Sub) base;
+ assertFalse("SubRevised".equals(sub.subName));
+ assertEquals(Sub.SUB_NAME, sub.subName);
+ }
+
+ public void testInstanceCreatorReturnsSubTypeForField() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Base.class, new InstanceCreator<Base>() {
+ public Base createInstance(Type type) {
+ return new Sub();
+ }
+ })
+ .create();
+ String json = "{base:{baseName:'Base',subName:'SubRevised'}}";
+ ClassWithBaseField target = gson.fromJson(json, ClassWithBaseField.class);
+ assertTrue(target.base instanceof Sub);
+ assertEquals(Sub.SUB_NAME, ((Sub)target.base).subName);
+ }
+
+ // This regressed in Gson 2.0 and 2.1
+ public void testInstanceCreatorForCollectionType() {
+ @SuppressWarnings("serial")
+ class SubArrayList<T> extends ArrayList<T> {}
+ InstanceCreator<List<String>> listCreator = new InstanceCreator<List<String>>() {
+ public List<String> createInstance(Type type) {
+ return new SubArrayList<String>();
+ }
+ };
+ Type listOfStringType = new TypeToken<List<String>>() {}.getType();
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(listOfStringType, listCreator)
+ .create();
+ List<String> list = gson.fromJson("[\"a\"]", listOfStringType);
+ assertEquals(SubArrayList.class, list.getClass());
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public void testInstanceCreatorForParametrizedType() throws Exception {
+ @SuppressWarnings("serial")
+ class SubTreeSet<T> extends TreeSet<T> {}
+ InstanceCreator<SortedSet> sortedSetCreator = new InstanceCreator<SortedSet>() {
+ public SortedSet createInstance(Type type) {
+ return new SubTreeSet();
+ }
+ };
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(SortedSet.class, sortedSetCreator)
+ .create();
+
+ Type sortedSetType = new TypeToken<SortedSet<String>>() {}.getType();
+ SortedSet<String> set = gson.fromJson("[\"a\"]", sortedSetType);
+ assertEquals(set.first(), "a");
+ assertEquals(SubTreeSet.class, set.getClass());
+
+ set = gson.fromJson("[\"b\"]", SortedSet.class);
+ assertEquals(set.first(), "b");
+ assertEquals(SubTreeSet.class, set.getClass());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/InterfaceTest.java b/gson/src/test/java/com/google/gson/functional/InterfaceTest.java
new file mode 100644
index 00000000..6851f1e9
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/InterfaceTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+
+import junit.framework.TestCase;
+
+/**
+ * Functional tests involving interfaces.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class InterfaceTest extends TestCase {
+ private static final String OBJ_JSON = "{\"someStringValue\":\"StringValue\"}";
+
+ private Gson gson;
+ private TestObject obj;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ obj = new TestObject("StringValue");
+ }
+
+ public void testSerializingObjectImplementingInterface() throws Exception {
+ assertEquals(OBJ_JSON, gson.toJson(obj));
+ }
+
+ public void testSerializingInterfaceObjectField() throws Exception {
+ TestObjectWrapper objWrapper = new TestObjectWrapper(obj);
+ assertEquals("{\"obj\":" + OBJ_JSON + "}", gson.toJson(objWrapper));
+ }
+
+ private static interface TestObjectInterface {
+ // Holder
+ }
+
+ private static class TestObject implements TestObjectInterface {
+ @SuppressWarnings("unused")
+ private String someStringValue;
+
+ private TestObject(String value) {
+ this.someStringValue = value;
+ }
+ }
+
+ private static class TestObjectWrapper {
+ @SuppressWarnings("unused")
+ private TestObjectInterface obj;
+
+ private TestObjectWrapper(TestObjectInterface obj) {
+ this.obj = obj;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/InternationalizationTest.java b/gson/src/test/java/com/google/gson/functional/InternationalizationTest.java
new file mode 100644
index 00000000..169c37a5
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/InternationalizationTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for internationalized strings.
+ *
+ * @author Inderjeet Singh
+ */
+public class InternationalizationTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ /*
+ public void testStringsWithRawChineseCharactersSerialization() throws Exception {
+ String target = "好好好";
+ String json = gson.toJson(target);
+ String expected = "\"\\u597d\\u597d\\u597d\"";
+ assertEquals(expected, json);
+ }
+ */
+
+ public void testStringsWithRawChineseCharactersDeserialization() throws Exception {
+ String expected = "好好好";
+ String json = "\"" + expected + "\"";
+ String actual = gson.fromJson(json, String.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testStringsWithUnicodeChineseCharactersSerialization() throws Exception {
+ String target = "\u597d\u597d\u597d";
+ String json = gson.toJson(target);
+ String expected = "\"\u597d\u597d\u597d\"";
+ assertEquals(expected, json);
+ }
+
+ public void testStringsWithUnicodeChineseCharactersDeserialization() throws Exception {
+ String expected = "\u597d\u597d\u597d";
+ String json = "\"" + expected + "\"";
+ String actual = gson.fromJson(json, String.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testStringsWithUnicodeChineseCharactersEscapedDeserialization() throws Exception {
+ String actual = gson.fromJson("'\\u597d\\u597d\\u597d'", String.class);
+ assertEquals("\u597d\u597d\u597d", actual);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java
new file mode 100644
index 00000000..1b9800d8
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnClassesTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.util.Locale;
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on classes.
+ */
+public final class JsonAdapterAnnotationOnClassesTest extends TestCase {
+
+ public void testJsonAdapterInvoked() {
+ Gson gson = new Gson();
+ String json = gson.toJson(new A("bar"));
+ assertEquals("\"jsonAdapter\"", json);
+
+ // Also invoke the JsonAdapter javadoc sample
+ json = gson.toJson(new User("Inderjeet", "Singh"));
+ assertEquals("{\"name\":\"Inderjeet Singh\"}", json);
+ User user = gson.fromJson("{'name':'Joel Leitch'}", User.class);
+ assertEquals("Joel", user.firstName);
+ assertEquals("Leitch", user.lastName);
+
+ json = gson.toJson(Foo.BAR);
+ assertEquals("\"bar\"", json);
+ Foo baz = gson.fromJson("\"baz\"", Foo.class);
+ assertEquals(Foo.BAZ, baz);
+ }
+
+ public void testJsonAdapterFactoryInvoked() {
+ Gson gson = new Gson();
+ String json = gson.toJson(new C("bar"));
+ assertEquals("\"jsonAdapterFactory\"", json);
+ C c = gson.fromJson("\"bar\"", C.class);
+ assertEquals("jsonAdapterFactory", c.value);
+ }
+
+ public void testRegisteredAdapterOverridesJsonAdapter() {
+ TypeAdapter<A> typeAdapter = new TypeAdapter<A>() {
+ @Override public void write(JsonWriter out, A value) throws IOException {
+ out.value("registeredAdapter");
+ }
+ @Override public A read(JsonReader in) throws IOException {
+ return new A(in.nextString());
+ }
+ };
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(A.class, typeAdapter)
+ .create();
+ String json = gson.toJson(new A("abcd"));
+ assertEquals("\"registeredAdapter\"", json);
+ }
+
+ /**
+ * The serializer overrides field adapter, but for deserializer the fieldAdapter is used.
+ */
+ public void testRegisteredSerializerOverridesJsonAdapter() {
+ JsonSerializer<A> serializer = new JsonSerializer<A>() {
+ public JsonElement serialize(A src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ return new JsonPrimitive("registeredSerializer");
+ }
+ };
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(A.class, serializer)
+ .create();
+ String json = gson.toJson(new A("abcd"));
+ assertEquals("\"registeredSerializer\"", json);
+ A target = gson.fromJson("abcd", A.class);
+ assertEquals("jsonAdapter", target.value);
+ }
+
+ /**
+ * The deserializer overrides Json adapter, but for serializer the jsonAdapter is used.
+ */
+ public void testRegisteredDeserializerOverridesJsonAdapter() {
+ JsonDeserializer<A> deserializer = new JsonDeserializer<A>() {
+ public A deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ return new A("registeredDeserializer");
+ }
+ };
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(A.class, deserializer)
+ .create();
+ String json = gson.toJson(new A("abcd"));
+ assertEquals("\"jsonAdapter\"", json);
+ A target = gson.fromJson("abcd", A.class);
+ assertEquals("registeredDeserializer", target.value);
+ }
+
+ public void testIncorrectTypeAdapterFails() {
+ try {
+ String json = new Gson().toJson(new ClassWithIncorrectJsonAdapter("bar"));
+ fail(json);
+ } catch (ClassCastException expected) {}
+ }
+
+ public void testSuperclassTypeAdapterNotInvoked() {
+ String json = new Gson().toJson(new B("bar"));
+ assertFalse(json.contains("jsonAdapter"));
+ }
+
+ @JsonAdapter(A.JsonAdapter.class)
+ private static class A {
+ final String value;
+ A(String value) {
+ this.value = value;
+ }
+ static final class JsonAdapter extends TypeAdapter<A> {
+ @Override public void write(JsonWriter out, A value) throws IOException {
+ out.value("jsonAdapter");
+ }
+ @Override public A read(JsonReader in) throws IOException {
+ in.nextString();
+ return new A("jsonAdapter");
+ }
+ }
+ }
+
+ @JsonAdapter(C.JsonAdapterFactory.class)
+ private static class C {
+ final String value;
+ C(String value) {
+ this.value = value;
+ }
+ static final class JsonAdapterFactory implements TypeAdapterFactory {
+ public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
+ return new TypeAdapter<T>() {
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ out.value("jsonAdapterFactory");
+ }
+ @SuppressWarnings("unchecked")
+ @Override public T read(JsonReader in) throws IOException {
+ in.nextString();
+ return (T) new C("jsonAdapterFactory");
+ }
+ };
+ }
+ }
+ }
+
+ private static final class B extends A {
+ B(String value) {
+ super(value);
+ }
+ }
+ // Note that the type is NOT TypeAdapter<ClassWithIncorrectJsonAdapter> so this
+ // should cause error
+ @JsonAdapter(A.JsonAdapter.class)
+ private static final class ClassWithIncorrectJsonAdapter {
+ @SuppressWarnings("unused") final String value;
+ ClassWithIncorrectJsonAdapter(String value) {
+ this.value = value;
+ }
+ }
+
+ // This class is used in JsonAdapter Javadoc as an example
+ @JsonAdapter(UserJsonAdapter.class)
+ private static class User {
+ final String firstName, lastName;
+ User(String firstName, String lastName) {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ }
+ }
+ private static class UserJsonAdapter extends TypeAdapter<User> {
+ @Override public void write(JsonWriter out, User user) throws IOException {
+ // implement write: combine firstName and lastName into name
+ out.beginObject();
+ out.name("name");
+ out.value(user.firstName + " " + user.lastName);
+ out.endObject();
+ // implement the write method
+ }
+ @Override public User read(JsonReader in) throws IOException {
+ // implement read: split name into firstName and lastName
+ in.beginObject();
+ in.nextName();
+ String[] nameParts = in.nextString().split(" ");
+ in.endObject();
+ return new User(nameParts[0], nameParts[1]);
+ }
+ }
+
+ @JsonAdapter(FooJsonAdapter.class)
+ private static enum Foo { BAR, BAZ }
+ private static class FooJsonAdapter extends TypeAdapter<Foo> {
+ @Override public void write(JsonWriter out, Foo value) throws IOException {
+ out.value(value.name().toLowerCase(Locale.US));
+ }
+
+ @Override public Foo read(JsonReader in) throws IOException {
+ return Foo.valueOf(in.nextString().toUpperCase(Locale.US));
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java
new file mode 100644
index 00000000..d3f097ea
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/JsonAdapterAnnotationOnFieldsTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2014 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for the {@link com.google.gson.annotations.JsonAdapter} annotation on fields.
+ */
+public final class JsonAdapterAnnotationOnFieldsTest extends TestCase {
+ public void testClassAnnotationAdapterTakesPrecedenceOverDefault() {
+ Gson gson = new Gson();
+ String json = gson.toJson(new Computer(new User("Inderjeet Singh")));
+ assertEquals("{\"user\":\"UserClassAnnotationAdapter\"}", json);
+ Computer computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer.class);
+ assertEquals("UserClassAnnotationAdapter", computer.user.name);
+ }
+
+ public void testClassAnnotationAdapterFactoryTakesPrecedenceOverDefault() {
+ Gson gson = new Gson();
+ String json = gson.toJson(new Gizmo(new Part("Part")));
+ assertEquals("{\"part\":\"GizmoPartTypeAdapterFactory\"}", json);
+ Gizmo computer = gson.fromJson("{'part':'Part'}", Gizmo.class);
+ assertEquals("GizmoPartTypeAdapterFactory", computer.part.name);
+ }
+
+ public void testRegisteredTypeAdapterTakesPrecedenceOverClassAnnotationAdapter() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(User.class, new RegisteredUserAdapter())
+ .create();
+ String json = gson.toJson(new Computer(new User("Inderjeet Singh")));
+ assertEquals("{\"user\":\"RegisteredUserAdapter\"}", json);
+ Computer computer = gson.fromJson("{'user':'Inderjeet Singh'}", Computer.class);
+ assertEquals("RegisteredUserAdapter", computer.user.name);
+ }
+
+ public void testFieldAnnotationTakesPrecedenceOverRegisteredTypeAdapter() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Part.class, new TypeAdapter<Part>() {
+ @Override public void write(JsonWriter out, Part part) throws IOException {
+ throw new AssertionError();
+ }
+
+ @Override public Part read(JsonReader in) throws IOException {
+ throw new AssertionError();
+ }
+ }).create();
+ String json = gson.toJson(new Gadget(new Part("screen")));
+ assertEquals("{\"part\":\"PartJsonFieldAnnotationAdapter\"}", json);
+ Gadget gadget = gson.fromJson("{'part':'screen'}", Gadget.class);
+ assertEquals("PartJsonFieldAnnotationAdapter", gadget.part.name);
+ }
+
+ public void testFieldAnnotationTakesPrecedenceOverClassAnnotation() {
+ Gson gson = new Gson();
+ String json = gson.toJson(new Computer2(new User("Inderjeet Singh")));
+ assertEquals("{\"user\":\"UserFieldAnnotationAdapter\"}", json);
+ Computer2 target = gson.fromJson("{'user':'Interjeet Singh'}", Computer2.class);
+ assertEquals("UserFieldAnnotationAdapter", target.user.name);
+ }
+
+ private static final class Gadget {
+ @JsonAdapter(PartJsonFieldAnnotationAdapter.class)
+ final Part part;
+ Gadget(Part part) {
+ this.part = part;
+ }
+ }
+
+ private static final class Gizmo {
+ @JsonAdapter(GizmoPartTypeAdapterFactory.class)
+ final Part part;
+ Gizmo(Part part) {
+ this.part = part;
+ }
+ }
+
+ private static final class Part {
+ final String name;
+ public Part(String name) {
+ this.name = name;
+ }
+ }
+
+ private static class PartJsonFieldAnnotationAdapter extends TypeAdapter<Part> {
+ @Override public void write(JsonWriter out, Part part) throws IOException {
+ out.value("PartJsonFieldAnnotationAdapter");
+ }
+ @Override public Part read(JsonReader in) throws IOException {
+ in.nextString();
+ return new Part("PartJsonFieldAnnotationAdapter");
+ }
+ }
+
+ private static class GizmoPartTypeAdapterFactory implements TypeAdapterFactory {
+ public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
+ return new TypeAdapter<T>() {
+ @Override public void write(JsonWriter out, T value) throws IOException {
+ out.value("GizmoPartTypeAdapterFactory");
+ }
+ @SuppressWarnings("unchecked")
+ @Override public T read(JsonReader in) throws IOException {
+ in.nextString();
+ return (T) new Part("GizmoPartTypeAdapterFactory");
+ }
+ };
+ }
+ }
+
+ private static final class Computer {
+ final User user;
+ Computer(User user) {
+ this.user = user;
+ }
+ }
+
+ @JsonAdapter(UserClassAnnotationAdapter.class)
+ private static class User {
+ public final String name;
+ private User(String name) {
+ this.name = name;
+ }
+ }
+
+ private static class UserClassAnnotationAdapter extends TypeAdapter<User> {
+ @Override public void write(JsonWriter out, User user) throws IOException {
+ out.value("UserClassAnnotationAdapter");
+ }
+ @Override public User read(JsonReader in) throws IOException {
+ in.nextString();
+ return new User("UserClassAnnotationAdapter");
+ }
+ }
+
+ private static final class Computer2 {
+ // overrides the JsonAdapter annotation of User with this
+ @JsonAdapter(UserFieldAnnotationAdapter.class)
+ final User user;
+ Computer2(User user) {
+ this.user = user;
+ }
+ }
+
+ private static final class UserFieldAnnotationAdapter extends TypeAdapter<User> {
+ @Override public void write(JsonWriter out, User user) throws IOException {
+ out.value("UserFieldAnnotationAdapter");
+ }
+ @Override public User read(JsonReader in) throws IOException {
+ in.nextString();
+ return new User("UserFieldAnnotationAdapter");
+ }
+ }
+
+ private static final class RegisteredUserAdapter extends TypeAdapter<User> {
+ @Override public void write(JsonWriter out, User user) throws IOException {
+ out.value("RegisteredUserAdapter");
+ }
+ @Override public User read(JsonReader in) throws IOException {
+ in.nextString();
+ return new User("RegisteredUserAdapter");
+ }
+ }
+
+ public void testJsonAdapterInvokedOnlyForAnnotatedFields() {
+ Gson gson = new Gson();
+ String json = "{'part1':'name','part2':{'name':'name2'}}";
+ GadgetWithTwoParts gadget = gson.fromJson(json, GadgetWithTwoParts.class);
+ assertEquals("PartJsonFieldAnnotationAdapter", gadget.part1.name);
+ assertEquals("name2", gadget.part2.name);
+ }
+
+ private static final class GadgetWithTwoParts {
+ @JsonAdapter(PartJsonFieldAnnotationAdapter.class) final Part part1;
+ final Part part2; // Doesn't have the JsonAdapter annotation
+ @SuppressWarnings("unused") GadgetWithTwoParts(Part part1, Part part2) {
+ this.part1 = part1;
+ this.part2 = part2;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/JsonArrayTest.java b/gson/src/test/java/com/google/gson/functional/JsonArrayTest.java
new file mode 100644
index 00000000..22a479b8
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/JsonArrayTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.JsonArray;
+import junit.framework.TestCase;
+
+import java.math.BigInteger;
+
+/**
+ * Functional tests for adding primitives to a JsonArray.
+ *
+ * @author Dillon Dixon
+ */
+public class JsonArrayTest extends TestCase {
+
+ public void testStringPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add("Hello");
+ jsonArray.add("Goodbye");
+ jsonArray.add("Thank you");
+ jsonArray.add((String) null);
+ jsonArray.add("Yes");
+
+ assertEquals("[\"Hello\",\"Goodbye\",\"Thank you\",null,\"Yes\"]", jsonArray.toString());
+ }
+
+ public void testIntegerPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ int x = 1;
+ jsonArray.add(x);
+
+ x = 2;
+ jsonArray.add(x);
+
+ x = -3;
+ jsonArray.add(x);
+
+ jsonArray.add((Integer) null);
+
+ x = 4;
+ jsonArray.add(x);
+
+ x = 0;
+ jsonArray.add(x);
+
+ assertEquals("[1,2,-3,null,4,0]", jsonArray.toString());
+ }
+
+ public void testDoublePrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ double x = 1.0;
+ jsonArray.add(x);
+
+ x = 2.13232;
+ jsonArray.add(x);
+
+ x = 0.121;
+ jsonArray.add(x);
+
+ jsonArray.add((Double) null);
+
+ x = -0.00234;
+ jsonArray.add(x);
+
+ jsonArray.add((Double) null);
+
+ assertEquals("[1.0,2.13232,0.121,null,-0.00234,null]", jsonArray.toString());
+ }
+
+ public void testBooleanPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add(true);
+ jsonArray.add(true);
+ jsonArray.add(false);
+ jsonArray.add(false);
+ jsonArray.add((Boolean) null);
+ jsonArray.add(true);
+
+ assertEquals("[true,true,false,false,null,true]", jsonArray.toString());
+ }
+
+ public void testCharPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add('a');
+ jsonArray.add('e');
+ jsonArray.add('i');
+ jsonArray.add((char) 111);
+ jsonArray.add((Character) null);
+ jsonArray.add('u');
+ jsonArray.add("and sometimes Y");
+
+ assertEquals("[\"a\",\"e\",\"i\",\"o\",null,\"u\",\"and sometimes Y\"]", jsonArray.toString());
+ }
+
+ public void testMixedPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add('a');
+ jsonArray.add("apple");
+ jsonArray.add(12121);
+ jsonArray.add((char) 111);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Character) null);
+ jsonArray.add(12.232);
+ jsonArray.add(BigInteger.valueOf(2323));
+
+ assertEquals("[\"a\",\"apple\",12121,\"o\",null,null,12.232,2323]", jsonArray.toString());
+ }
+
+ public void testNullPrimitiveAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add((Character) null);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Integer) null);
+ jsonArray.add((Double) null);
+ jsonArray.add((Float) null);
+ jsonArray.add((BigInteger) null);
+ jsonArray.add((String) null);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Number) null);
+
+ assertEquals("[null,null,null,null,null,null,null,null,null]", jsonArray.toString());
+ }
+
+ public void testSameAddition() {
+ JsonArray jsonArray = new JsonArray();
+
+ jsonArray.add('a');
+ jsonArray.add('a');
+ jsonArray.add(true);
+ jsonArray.add(true);
+ jsonArray.add(1212);
+ jsonArray.add(1212);
+ jsonArray.add(34.34);
+ jsonArray.add(34.34);
+ jsonArray.add((Boolean) null);
+ jsonArray.add((Boolean) null);
+
+ assertEquals("[\"a\",\"a\",true,true,1212,1212,34.34,34.34,null,null]", jsonArray.toString());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/JsonParserTest.java b/gson/src/test/java/com/google/gson/functional/JsonParserTest.java
new file mode 100644
index 00000000..44f4477c
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/JsonParserTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.Nested;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.io.StringReader;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Functional tests for that use JsonParser and related Gson methods
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class JsonParserTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testParseInvalidJson() {
+ try {
+ gson.fromJson("[[]", Object[].class);
+ fail();
+ } catch (JsonSyntaxException expected) { }
+ }
+
+ public void testDeserializingCustomTree() {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("stringValue", "foo");
+ obj.addProperty("intValue", 11);
+ BagOfPrimitives target = gson.fromJson(obj, BagOfPrimitives.class);
+ assertEquals(11, target.intValue);
+ assertEquals("foo", target.stringValue);
+ }
+
+ public void testBadTypeForDeserializingCustomTree() {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("stringValue", "foo");
+ obj.addProperty("intValue", 11);
+ JsonArray array = new JsonArray();
+ array.add(obj);
+ try {
+ gson.fromJson(array, BagOfPrimitives.class);
+ fail("BagOfPrimitives is not an array");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testBadFieldTypeForCustomDeserializerCustomTree() {
+ JsonArray array = new JsonArray();
+ array.add(new JsonPrimitive("blah"));
+ JsonObject obj = new JsonObject();
+ obj.addProperty("stringValue", "foo");
+ obj.addProperty("intValue", 11);
+ obj.add("longValue", array);
+
+ try {
+ gson.fromJson(obj, BagOfPrimitives.class);
+ fail("BagOfPrimitives is not an array");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testBadFieldTypeForDeserializingCustomTree() {
+ JsonArray array = new JsonArray();
+ array.add(new JsonPrimitive("blah"));
+ JsonObject primitive1 = new JsonObject();
+ primitive1.addProperty("string", "foo");
+ primitive1.addProperty("intValue", 11);
+
+ JsonObject obj = new JsonObject();
+ obj.add("primitive1", primitive1);
+ obj.add("primitive2", array);
+
+ try {
+ gson.fromJson(obj, Nested.class);
+ fail("Nested has field BagOfPrimitives which is not an array");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testChangingCustomTreeAndDeserializing() {
+ StringReader json =
+ new StringReader("{'stringValue':'no message','intValue':10,'longValue':20}");
+ JsonObject obj = (JsonObject) new JsonParser().parse(json);
+ obj.remove("stringValue");
+ obj.addProperty("stringValue", "fooBar");
+ BagOfPrimitives target = gson.fromJson(obj, BagOfPrimitives.class);
+ assertEquals(10, target.intValue);
+ assertEquals(20, target.longValue);
+ assertEquals("fooBar", target.stringValue);
+ }
+
+ public void testExtraCommasInArrays() {
+ Type type = new TypeToken<List<String>>() {}.getType();
+ assertEquals(list("a", null, "b", null, null), gson.fromJson("[a,,b,,]", type));
+ assertEquals(list(null, null), gson.fromJson("[,]", type));
+ assertEquals(list("a", null), gson.fromJson("[a,]", type));
+ }
+
+ public void testExtraCommasInMaps() {
+ Type type = new TypeToken<Map<String, String>>() {}.getType();
+ try {
+ gson.fromJson("{a:b,}", type);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ private <T> List<T> list(T... elements) {
+ return Arrays.asList(elements);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/JsonTreeTest.java b/gson/src/test/java/com/google/gson/functional/JsonTreeTest.java
new file mode 100644
index 00000000..a6479403
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/JsonTreeTest.java
@@ -0,0 +1,89 @@
+package com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for {@link Gson#toJsonTree(Object)} and
+ * {@link Gson#toJsonTree(Object, java.lang.reflect.Type)}
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class JsonTreeTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testToJsonTree() {
+ BagOfPrimitives bag = new BagOfPrimitives(10L, 5, false, "foo");
+ JsonElement json = gson.toJsonTree(bag);
+ assertTrue(json.isJsonObject());
+ JsonObject obj = json.getAsJsonObject();
+ Set<Entry<String, JsonElement>> children = obj.entrySet();
+ assertEquals(4, children.size());
+ assertContains(obj, new JsonPrimitive(10L));
+ assertContains(obj, new JsonPrimitive(5));
+ assertContains(obj, new JsonPrimitive(false));
+ assertContains(obj, new JsonPrimitive("foo"));
+ }
+
+ public void testToJsonTreeObjectType() {
+ SubTypeOfBagOfPrimitives bag = new SubTypeOfBagOfPrimitives(10L, 5, false, "foo", 1.4F);
+ JsonElement json = gson.toJsonTree(bag, BagOfPrimitives.class);
+ assertTrue(json.isJsonObject());
+ JsonObject obj = json.getAsJsonObject();
+ Set<Entry<String, JsonElement>> children = obj.entrySet();
+ assertEquals(4, children.size());
+ assertContains(obj, new JsonPrimitive(10L));
+ assertContains(obj, new JsonPrimitive(5));
+ assertContains(obj, new JsonPrimitive(false));
+ assertContains(obj, new JsonPrimitive("foo"));
+ }
+
+ public void testJsonTreeToString() {
+ SubTypeOfBagOfPrimitives bag = new SubTypeOfBagOfPrimitives(10L, 5, false, "foo", 1.4F);
+ String json1 = gson.toJson(bag);
+ JsonElement jsonElement = gson.toJsonTree(bag, SubTypeOfBagOfPrimitives.class);
+ String json2 = gson.toJson(jsonElement);
+ assertEquals(json1, json2);
+ }
+
+ public void testJsonTreeNull() {
+ BagOfPrimitives bag = new BagOfPrimitives(10L, 5, false, null);
+ JsonObject jsonElement = (JsonObject) gson.toJsonTree(bag, BagOfPrimitives.class);
+ assertFalse(jsonElement.has("stringValue"));
+ }
+
+ private void assertContains(JsonObject json, JsonPrimitive child) {
+ for (Map.Entry<String, JsonElement> entry : json.entrySet()) {
+ JsonElement node = entry.getValue();
+ if (node.isJsonPrimitive()) {
+ if (node.getAsJsonPrimitive().equals(child)) {
+ return;
+ }
+ }
+ }
+ fail();
+ }
+
+ private static class SubTypeOfBagOfPrimitives extends BagOfPrimitives {
+ @SuppressWarnings("unused")
+ float f = 1.2F;
+ public SubTypeOfBagOfPrimitives(long l, int i, boolean b, String string, float f) {
+ super(l, i, b, string);
+ this.f = f;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/MapAsArrayTypeAdapterTest.java b/gson/src/test/java/com/google/gson/functional/MapAsArrayTypeAdapterTest.java
new file mode 100644
index 00000000..c7cfcdf9
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/MapAsArrayTypeAdapterTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import junit.framework.TestCase;
+
+public class MapAsArrayTypeAdapterTest extends TestCase {
+
+ public void testSerializeComplexMapWithTypeAdapter() {
+ Type type = new TypeToken<Map<Point, String>>() {}.getType();
+ Gson gson = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .create();
+
+ Map<Point, String> original = new LinkedHashMap<Point, String>();
+ original.put(new Point(5, 5), "a");
+ original.put(new Point(8, 8), "b");
+ String json = gson.toJson(original, type);
+ assertEquals("[[{\"x\":5,\"y\":5},\"a\"],[{\"x\":8,\"y\":8},\"b\"]]", json);
+ assertEquals(original, gson.<Map<Point, String>>fromJson(json, type));
+
+ // test that registering a type adapter for one map doesn't interfere with others
+ Map<String, Boolean> otherMap = new LinkedHashMap<String, Boolean>();
+ otherMap.put("t", true);
+ otherMap.put("f", false);
+ assertEquals("{\"t\":true,\"f\":false}",
+ gson.toJson(otherMap, Map.class));
+ assertEquals("{\"t\":true,\"f\":false}",
+ gson.toJson(otherMap, new TypeToken<Map<String, Boolean>>() {}.getType()));
+ assertEquals(otherMap, gson.<Object>fromJson("{\"t\":true,\"f\":false}",
+ new TypeToken<Map<String, Boolean>>() {}.getType()));
+ }
+
+ public void disabled_testTwoTypesCollapseToOneSerialize() {
+ Gson gson = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .create();
+
+ Map<Number, String> original = new LinkedHashMap<Number, String>();
+ original.put(new Double(1.0), "a");
+ original.put(new Float(1.0), "b");
+ try {
+ gson.toJson(original, new TypeToken<Map<Number, String>>() {}.getType());
+ fail(); // we no longer hash keys at serialization time
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testTwoTypesCollapseToOneDeserialize() {
+ Gson gson = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .create();
+
+ String s = "[[\"1.00\",\"a\"],[\"1.0\",\"b\"]]";
+ try {
+ gson.fromJson(s, new TypeToken<Map<Double, String>>() {}.getType());
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testMultipleEnableComplexKeyRegistrationHasNoEffect() throws Exception {
+ Type type = new TypeToken<Map<Point, String>>() {}.getType();
+ Gson gson = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .enableComplexMapKeySerialization()
+ .create();
+
+ Map<Point, String> original = new LinkedHashMap<Point, String>();
+ original.put(new Point(6, 5), "abc");
+ original.put(new Point(1, 8), "def");
+ String json = gson.toJson(original, type);
+ assertEquals("[[{\"x\":6,\"y\":5},\"abc\"],[{\"x\":1,\"y\":8},\"def\"]]", json);
+ assertEquals(original, gson.<Map<Point, String>>fromJson(json, type));
+ }
+
+ public void testMapWithTypeVariableSerialization() {
+ Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
+ PointWithProperty<Point> map = new PointWithProperty<Point>();
+ map.map.put(new Point(2, 3), new Point(4, 5));
+ Type type = new TypeToken<PointWithProperty<Point>>(){}.getType();
+ String json = gson.toJson(map, type);
+ assertEquals("{\"map\":[[{\"x\":2,\"y\":3},{\"x\":4,\"y\":5}]]}", json);
+ }
+
+ public void testMapWithTypeVariableDeserialization() {
+ Gson gson = new GsonBuilder().enableComplexMapKeySerialization().create();
+ String json = "{map:[[{x:2,y:3},{x:4,y:5}]]}";
+ Type type = new TypeToken<PointWithProperty<Point>>(){}.getType();
+ PointWithProperty<Point> map = gson.fromJson(json, type);
+ Point key = map.map.keySet().iterator().next();
+ Point value = map.map.values().iterator().next();
+ assertEquals(new Point(2, 3), key);
+ assertEquals(new Point(4, 5), value);
+ }
+
+ static class Point {
+ int x;
+ int y;
+ Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+ Point() {}
+ @Override public boolean equals(Object o) {
+ return o instanceof Point && ((Point) o).x == x && ((Point) o).y == y;
+ }
+ @Override public int hashCode() {
+ return x * 37 + y;
+ }
+ @Override public String toString() {
+ return "(" + x + "," + y + ")";
+ }
+ }
+
+ static class PointWithProperty<T> {
+ Map<Point, T> map = new HashMap<Point, T>();
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/MapTest.java b/gson/src/test/java/com/google/gson/functional/MapTest.java
new file mode 100755
index 00000000..c175bae5
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/MapTest.java
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonParser;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.common.TestTypes;
+import com.google.gson.internal.$Gson$Types;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * Functional test for Json serialization and deserialization for Maps
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class MapTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testMapSerialization() {
+ Map<String, Integer> map = new LinkedHashMap<String, Integer>();
+ map.put("a", 1);
+ map.put("b", 2);
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+ assertTrue(json.contains("\"a\":1"));
+ assertTrue(json.contains("\"b\":2"));
+ }
+
+ public void testMapDeserialization() {
+ String json = "{\"a\":1,\"b\":2}";
+ Type typeOfMap = new TypeToken<Map<String,Integer>>(){}.getType();
+ Map<String, Integer> target = gson.fromJson(json, typeOfMap);
+ assertEquals(1, target.get("a").intValue());
+ assertEquals(2, target.get("b").intValue());
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public void testRawMapSerialization() {
+ Map map = new LinkedHashMap();
+ map.put("a", 1);
+ map.put("b", "string");
+ String json = gson.toJson(map);
+ assertTrue(json.contains("\"a\":1"));
+ assertTrue(json.contains("\"b\":\"string\""));
+ }
+
+ public void testMapSerializationEmpty() {
+ Map<String, Integer> map = new LinkedHashMap<String, Integer>();
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+ assertEquals("{}", json);
+ }
+
+ public void testMapDeserializationEmpty() {
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ Map<String, Integer> map = gson.fromJson("{}", typeOfMap);
+ assertTrue(map.isEmpty());
+ }
+
+ public void testMapSerializationWithNullValue() {
+ Map<String, Integer> map = new LinkedHashMap<String, Integer>();
+ map.put("abc", null);
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+
+ // Maps are represented as JSON objects, so ignoring null field
+ assertEquals("{}", json);
+ }
+
+ public void testMapDeserializationWithNullValue() {
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ Map<String, Integer> map = gson.fromJson("{\"abc\":null}", typeOfMap);
+ assertEquals(1, map.size());
+ assertNull(map.get("abc"));
+ }
+
+ public void testMapSerializationWithNullValueButSerializeNulls() {
+ gson = new GsonBuilder().serializeNulls().create();
+ Map<String, Integer> map = new LinkedHashMap<String, Integer>();
+ map.put("abc", null);
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+
+ assertEquals("{\"abc\":null}", json);
+ }
+
+ public void testMapSerializationWithNullKey() {
+ Map<String, Integer> map = new LinkedHashMap<String, Integer>();
+ map.put(null, 123);
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+
+ assertEquals("{\"null\":123}", json);
+ }
+
+ public void testMapDeserializationWithNullKey() {
+ Type typeOfMap = new TypeToken<Map<String, Integer>>() {}.getType();
+ Map<String, Integer> map = gson.fromJson("{\"null\":123}", typeOfMap);
+ assertEquals(1, map.size());
+ assertEquals(123, map.get("null").intValue());
+ assertNull(map.get(null));
+
+ map = gson.fromJson("{null:123}", typeOfMap);
+ assertEquals(1, map.size());
+ assertEquals(123, map.get("null").intValue());
+ assertNull(map.get(null));
+ }
+
+ public void testMapSerializationWithIntegerKeys() {
+ Map<Integer, String> map = new LinkedHashMap<Integer, String>();
+ map.put(123, "456");
+ Type typeOfMap = new TypeToken<Map<Integer, String>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+
+ assertEquals("{\"123\":\"456\"}", json);
+ }
+
+ public void testMapDeserializationWithIntegerKeys() {
+ Type typeOfMap = new TypeToken<Map<Integer, String>>() {}.getType();
+ Map<Integer, String> map = gson.fromJson("{\"123\":\"456\"}", typeOfMap);
+ assertEquals(1, map.size());
+ assertTrue(map.containsKey(123));
+ assertEquals("456", map.get(123));
+ }
+
+ public void testHashMapDeserialization() throws Exception {
+ Type typeOfMap = new TypeToken<HashMap<Integer, String>>() {}.getType();
+ HashMap<Integer, String> map = gson.fromJson("{\"123\":\"456\"}", typeOfMap);
+ assertEquals(1, map.size());
+ assertTrue(map.containsKey(123));
+ assertEquals("456", map.get(123));
+ }
+
+ public void testSortedMap() throws Exception {
+ Type typeOfMap = new TypeToken<SortedMap<Integer, String>>() {}.getType();
+ SortedMap<Integer, String> map = gson.fromJson("{\"123\":\"456\"}", typeOfMap);
+ assertEquals(1, map.size());
+ assertTrue(map.containsKey(123));
+ assertEquals("456", map.get(123));
+ }
+
+ public void testParameterizedMapSubclassSerialization() {
+ MyParameterizedMap<String, String> map = new MyParameterizedMap<String, String>(10);
+ map.put("a", "b");
+ Type type = new TypeToken<MyParameterizedMap<String, String>>() {}.getType();
+ String json = gson.toJson(map, type);
+ assertTrue(json.contains("\"a\":\"b\""));
+ }
+
+ @SuppressWarnings({ "unused", "serial" })
+ private static class MyParameterizedMap<K, V> extends LinkedHashMap<K, V> {
+ final int foo;
+ MyParameterizedMap(int foo) {
+ this.foo = foo;
+ }
+ }
+
+ public void testMapSubclassSerialization() {
+ MyMap map = new MyMap();
+ map.put("a", "b");
+ String json = gson.toJson(map, MyMap.class);
+ assertTrue(json.contains("\"a\":\"b\""));
+ }
+
+ public void testMapStandardSubclassDeserialization() {
+ String json = "{a:'1',b:'2'}";
+ Type type = new TypeToken<LinkedHashMap<String, String>>() {}.getType();
+ LinkedHashMap<String, Integer> map = gson.fromJson(json, type);
+ assertEquals("1", map.get("a"));
+ assertEquals("2", map.get("b"));
+ }
+
+ public void testMapSubclassDeserialization() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(MyMap.class, new InstanceCreator<MyMap>() {
+ public MyMap createInstance(Type type) {
+ return new MyMap();
+ }
+ }).create();
+ String json = "{\"a\":1,\"b\":2}";
+ MyMap map = gson.fromJson(json, MyMap.class);
+ assertEquals("1", map.get("a"));
+ assertEquals("2", map.get("b"));
+ }
+
+ public void testCustomSerializerForSpecificMapType() {
+ Type type = $Gson$Types.newParameterizedTypeWithOwner(
+ null, Map.class, String.class, Long.class);
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(type, new JsonSerializer<Map<String, Long>>() {
+ public JsonElement serialize(Map<String, Long> src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ JsonArray array = new JsonArray();
+ for (long value : src.values()) {
+ array.add(new JsonPrimitive(value));
+ }
+ return array;
+ }
+ }).create();
+
+ Map<String, Long> src = new LinkedHashMap<String, Long>();
+ src.put("one", 1L);
+ src.put("two", 2L);
+ src.put("three", 3L);
+
+ assertEquals("[1,2,3]", gson.toJson(src, type));
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=99
+ */
+ private static class ClassWithAMap {
+ Map<String, String> map = new TreeMap<String, String>();
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=99
+ */
+ public void testMapSerializationWithNullValues() {
+ ClassWithAMap target = new ClassWithAMap();
+ target.map.put("name1", null);
+ target.map.put("name2", "value2");
+ String json = gson.toJson(target);
+ assertFalse(json.contains("name1"));
+ assertTrue(json.contains("name2"));
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=99
+ */
+ public void testMapSerializationWithNullValuesSerialized() {
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ ClassWithAMap target = new ClassWithAMap();
+ target.map.put("name1", null);
+ target.map.put("name2", "value2");
+ String json = gson.toJson(target);
+ assertTrue(json.contains("name1"));
+ assertTrue(json.contains("name2"));
+ }
+
+ public void testMapSerializationWithWildcardValues() {
+ Map<String, ? extends Collection<? extends Integer>> map =
+ new LinkedHashMap<String, Collection<Integer>>();
+ map.put("test", null);
+ Type typeOfMap =
+ new TypeToken<Map<String, ? extends Collection<? extends Integer>>>() {}.getType();
+ String json = gson.toJson(map, typeOfMap);
+
+ assertEquals("{}", json);
+ }
+
+ public void testMapDeserializationWithWildcardValues() {
+ Type typeOfMap = new TypeToken<Map<String, ? extends Long>>() {}.getType();
+ Map<String, ? extends Long> map = gson.fromJson("{\"test\":123}", typeOfMap);
+ assertEquals(1, map.size());
+ assertEquals(new Long(123L), map.get("test"));
+ }
+
+
+ private static class MyMap extends LinkedHashMap<String, String> {
+ private static final long serialVersionUID = 1L;
+
+ @SuppressWarnings("unused")
+ int foo = 10;
+ }
+
+ /**
+ * From bug report http://code.google.com/p/google-gson/issues/detail?id=95
+ */
+ public void testMapOfMapSerialization() {
+ Map<String, Map<String, String>> map = new HashMap<String, Map<String, String>>();
+ Map<String, String> nestedMap = new HashMap<String, String>();
+ nestedMap.put("1", "1");
+ nestedMap.put("2", "2");
+ map.put("nestedMap", nestedMap);
+ String json = gson.toJson(map);
+ assertTrue(json.contains("nestedMap"));
+ assertTrue(json.contains("\"1\":\"1\""));
+ assertTrue(json.contains("\"2\":\"2\""));
+ }
+
+ /**
+ * From bug report http://code.google.com/p/google-gson/issues/detail?id=95
+ */
+ public void testMapOfMapDeserialization() {
+ String json = "{nestedMap:{'2':'2','1':'1'}}";
+ Type type = new TypeToken<Map<String, Map<String, String>>>(){}.getType();
+ Map<String, Map<String, String>> map = gson.fromJson(json, type);
+ Map<String, String> nested = map.get("nestedMap");
+ assertEquals("1", nested.get("1"));
+ assertEquals("2", nested.get("2"));
+ }
+
+ /**
+ * From bug report http://code.google.com/p/google-gson/issues/detail?id=178
+ */
+ public void testMapWithQuotes() {
+ Map<String, String> map = new HashMap<String, String>();
+ map.put("a\"b", "c\"d");
+ String json = gson.toJson(map);
+ assertEquals("{\"a\\\"b\":\"c\\\"d\"}", json);
+ }
+
+ /**
+ * From issue 227.
+ */
+ public void testWriteMapsWithEmptyStringKey() {
+ Map<String, Boolean> map = new HashMap<String, Boolean>();
+ map.put("", true);
+ assertEquals("{\"\":true}", gson.toJson(map));
+
+ }
+
+ public void testReadMapsWithEmptyStringKey() {
+ Map<String, Boolean> map = gson.fromJson("{\"\":true}", new TypeToken<Map<String, Boolean>>() {}.getType());
+ assertEquals(Boolean.TRUE, map.get(""));
+ }
+
+ /**
+ * From bug report http://code.google.com/p/google-gson/issues/detail?id=204
+ */
+ public void testSerializeMaps() {
+ Map<String, Object> map = new LinkedHashMap<String, Object>();
+ map.put("a", 12);
+ map.put("b", null);
+
+ LinkedHashMap<String, Object> innerMap = new LinkedHashMap<String, Object>();
+ innerMap.put("test", 1);
+ innerMap.put("TestStringArray", new String[] { "one", "two" });
+ map.put("c", innerMap);
+
+ assertEquals("{\"a\":12,\"b\":null,\"c\":{\"test\":1,\"TestStringArray\":[\"one\",\"two\"]}}",
+ new GsonBuilder().serializeNulls().create().toJson(map));
+ assertEquals("{\n \"a\": 12,\n \"b\": null,\n \"c\": "
+ + "{\n \"test\": 1,\n \"TestStringArray\": "
+ + "[\n \"one\",\n \"two\"\n ]\n }\n}",
+ new GsonBuilder().setPrettyPrinting().serializeNulls().create().toJson(map));
+ assertEquals("{\"a\":12,\"c\":{\"test\":1,\"TestStringArray\":[\"one\",\"two\"]}}",
+ new GsonBuilder().create().toJson(map));
+ assertEquals("{\n \"a\": 12,\n \"c\": "
+ + "{\n \"test\": 1,\n \"TestStringArray\": "
+ + "[\n \"one\",\n \"two\"\n ]\n }\n}",
+ new GsonBuilder().setPrettyPrinting().create().toJson(map));
+
+ innerMap.put("d", "e");
+ assertEquals("{\"a\":12,\"c\":{\"test\":1,\"TestStringArray\":[\"one\",\"two\"],\"d\":\"e\"}}",
+ new Gson().toJson(map));
+ }
+
+ public final void testInterfaceTypeMap() {
+ MapClass element = new MapClass();
+ TestTypes.Sub subType = new TestTypes.Sub();
+ element.addBase("Test", subType);
+ element.addSub("Test", subType);
+
+ String subTypeJson = new Gson().toJson(subType);
+ String expected = "{\"bases\":{\"Test\":" + subTypeJson + "},"
+ + "\"subs\":{\"Test\":" + subTypeJson + "}}";
+
+ Gson gsonWithComplexKeys = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .create();
+ String json = gsonWithComplexKeys.toJson(element);
+ assertEquals(expected, json);
+
+ Gson gson = new Gson();
+ json = gson.toJson(element);
+ assertEquals(expected, json);
+ }
+
+ public final void testInterfaceTypeMapWithSerializer() {
+ MapClass element = new MapClass();
+ TestTypes.Sub subType = new TestTypes.Sub();
+ element.addBase("Test", subType);
+ element.addSub("Test", subType);
+
+ Gson tempGson = new Gson();
+ String subTypeJson = tempGson.toJson(subType);
+ final JsonElement baseTypeJsonElement = tempGson.toJsonTree(subType, TestTypes.Base.class);
+ String baseTypeJson = tempGson.toJson(baseTypeJsonElement);
+ String expected = "{\"bases\":{\"Test\":" + baseTypeJson + "},"
+ + "\"subs\":{\"Test\":" + subTypeJson + "}}";
+
+ JsonSerializer<TestTypes.Base> baseTypeAdapter = new JsonSerializer<TestTypes.Base>() {
+ public JsonElement serialize(TestTypes.Base src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ return baseTypeJsonElement;
+ }
+ };
+
+ Gson gson = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .registerTypeAdapter(TestTypes.Base.class, baseTypeAdapter)
+ .create();
+ String json = gson.toJson(element);
+ assertEquals(expected, json);
+
+ gson = new GsonBuilder()
+ .registerTypeAdapter(TestTypes.Base.class, baseTypeAdapter)
+ .create();
+ json = gson.toJson(element);
+ assertEquals(expected, json);
+ }
+
+ public void testGeneralMapField() throws Exception {
+ MapWithGeneralMapParameters map = new MapWithGeneralMapParameters();
+ map.map.put("string", "testString");
+ map.map.put("stringArray", new String[]{"one", "two"});
+ map.map.put("objectArray", new Object[]{1, 2L, "three"});
+
+ String expected = "{\"map\":{\"string\":\"testString\",\"stringArray\":"
+ + "[\"one\",\"two\"],\"objectArray\":[1,2,\"three\"]}}";
+ assertEquals(expected, gson.toJson(map));
+
+ gson = new GsonBuilder()
+ .enableComplexMapKeySerialization()
+ .create();
+ assertEquals(expected, gson.toJson(map));
+ }
+
+ public void testComplexKeysSerialization() {
+ Map<Point, String> map = new LinkedHashMap<Point, String>();
+ map.put(new Point(2, 3), "a");
+ map.put(new Point(5, 7), "b");
+ String json = "{\"2,3\":\"a\",\"5,7\":\"b\"}";
+ assertEquals(json, gson.toJson(map, new TypeToken<Map<Point, String>>() {}.getType()));
+ assertEquals(json, gson.toJson(map, Map.class));
+ }
+
+ public void testComplexKeysDeserialization() {
+ String json = "{'2,3':'a','5,7':'b'}";
+ try {
+ gson.fromJson(json, new TypeToken<Map<Point, String>>() {}.getType());
+ fail();
+ } catch (JsonParseException expected) {
+ }
+ }
+
+ public void testStringKeyDeserialization() {
+ String json = "{'2,3':'a','5,7':'b'}";
+ Map<String, String> map = new LinkedHashMap<String, String>();
+ map.put("2,3", "a");
+ map.put("5,7", "b");
+ assertEquals(map, gson.fromJson(json, new TypeToken<Map<String, String>>() {}.getType()));
+ }
+
+ public void testNumberKeyDeserialization() {
+ String json = "{'2.3':'a','5.7':'b'}";
+ Map<Double, String> map = new LinkedHashMap<Double, String>();
+ map.put(2.3, "a");
+ map.put(5.7, "b");
+ assertEquals(map, gson.fromJson(json, new TypeToken<Map<Double, String>>() {}.getType()));
+ }
+
+ public void testBooleanKeyDeserialization() {
+ String json = "{'true':'a','false':'b'}";
+ Map<Boolean, String> map = new LinkedHashMap<Boolean, String>();
+ map.put(true, "a");
+ map.put(false, "b");
+ assertEquals(map, gson.fromJson(json, new TypeToken<Map<Boolean, String>>() {}.getType()));
+ }
+
+ public void testMapDeserializationWithDuplicateKeys() {
+ try {
+ gson.fromJson("{'a':1,'a':2}", new TypeToken<Map<String, Integer>>() {}.getType());
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testSerializeMapOfMaps() {
+ Type type = new TypeToken<Map<String, Map<String, String>>>() {}.getType();
+ Map<String, Map<String, String>> map = newMap(
+ "a", newMap("ka1", "va1", "ka2", "va2"),
+ "b", newMap("kb1", "vb1", "kb2", "vb2"));
+ assertEquals("{'a':{'ka1':'va1','ka2':'va2'},'b':{'kb1':'vb1','kb2':'vb2'}}",
+ gson.toJson(map, type).replace('"', '\''));
+ }
+
+ public void testDeerializeMapOfMaps() {
+ Type type = new TypeToken<Map<String, Map<String, String>>>() {}.getType();
+ Map<String, Map<String, String>> map = newMap(
+ "a", newMap("ka1", "va1", "ka2", "va2"),
+ "b", newMap("kb1", "vb1", "kb2", "vb2"));
+ String json = "{'a':{'ka1':'va1','ka2':'va2'},'b':{'kb1':'vb1','kb2':'vb2'}}";
+ assertEquals(map, gson.fromJson(json, type));
+ }
+
+ private <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) {
+ Map<K, V> result = new LinkedHashMap<K, V>();
+ result.put(key1, value1);
+ result.put(key2, value2);
+ return result;
+ }
+
+ public void testMapNamePromotionWithJsonElementReader() {
+ String json = "{'2.3':'a'}";
+ Map<Double, String> map = new LinkedHashMap<Double, String>();
+ map.put(2.3, "a");
+ JsonElement tree = new JsonParser().parse(json);
+ assertEquals(map, gson.fromJson(tree, new TypeToken<Map<Double, String>>() {}.getType()));
+ }
+
+ static class Point {
+ private final int x;
+ private final int y;
+
+ Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Point && x == ((Point) o).x && y == ((Point) o).y;
+ }
+
+ @Override public int hashCode() {
+ return x * 37 + y;
+ }
+
+ @Override public String toString() {
+ return x + "," + y;
+ }
+ }
+
+ static final class MapClass {
+ private final Map<String, TestTypes.Base> bases = new HashMap<String, TestTypes.Base>();
+ private final Map<String, TestTypes.Sub> subs = new HashMap<String, TestTypes.Sub>();
+
+ public final void addBase(String name, TestTypes.Base value) {
+ bases.put(name, value);
+ }
+
+ public final void addSub(String name, TestTypes.Sub value) {
+ subs.put(name, value);
+ }
+ }
+
+ static final class MapWithGeneralMapParameters {
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ final Map<String, Object> map = new LinkedHashMap();
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/MoreSpecificTypeSerializationTest.java b/gson/src/test/java/com/google/gson/functional/MoreSpecificTypeSerializationTest.java
new file mode 100644
index 00000000..7ecbffc8
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/MoreSpecificTypeSerializationTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests for Gson serialization of a sub-class object while encountering a base-class type
+ *
+ * @author Inderjeet Singh
+ */
+@SuppressWarnings("unused")
+public class MoreSpecificTypeSerializationTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testSubclassFields() {
+ ClassWithBaseFields target = new ClassWithBaseFields(new Sub(1, 2));
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"b\":1"));
+ assertTrue(json.contains("\"s\":2"));
+ }
+
+ public void testListOfSubclassFields() {
+ Collection<Base> list = new ArrayList<Base>();
+ list.add(new Base(1));
+ list.add(new Sub(2, 3));
+ ClassWithContainersOfBaseFields target = new ClassWithContainersOfBaseFields(list, null);
+ String json = gson.toJson(target);
+ assertTrue(json, json.contains("{\"b\":1}"));
+ assertTrue(json, json.contains("{\"s\":3,\"b\":2}"));
+ }
+
+ public void testMapOfSubclassFields() {
+ Map<String, Base> map = new HashMap<String, Base>();
+ map.put("base", new Base(1));
+ map.put("sub", new Sub(2, 3));
+ ClassWithContainersOfBaseFields target = new ClassWithContainersOfBaseFields(null, map);
+ JsonObject json = gson.toJsonTree(target).getAsJsonObject().get("map").getAsJsonObject();
+ assertEquals(1, json.get("base").getAsJsonObject().get("b").getAsInt());
+ JsonObject sub = json.get("sub").getAsJsonObject();
+ assertEquals(2, sub.get("b").getAsInt());
+ assertEquals(3, sub.get("s").getAsInt());
+ }
+
+ /**
+ * For parameterized type, Gson ignores the more-specific type and sticks to the declared type
+ */
+ public void testParameterizedSubclassFields() {
+ ClassWithParameterizedBaseFields target = new ClassWithParameterizedBaseFields(
+ new ParameterizedSub<String>("one", "two"));
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"t\":\"one\""));
+ assertFalse(json.contains("\"s\""));
+ }
+
+ /**
+ * For parameterized type in a List, Gson ignores the more-specific type and sticks to
+ * the declared type
+ */
+ public void testListOfParameterizedSubclassFields() {
+ Collection<ParameterizedBase<String>> list = new ArrayList<ParameterizedBase<String>>();
+ list.add(new ParameterizedBase<String>("one"));
+ list.add(new ParameterizedSub<String>("two", "three"));
+ ClassWithContainersOfParameterizedBaseFields target =
+ new ClassWithContainersOfParameterizedBaseFields(list, null);
+ String json = gson.toJson(target);
+ assertTrue(json, json.contains("{\"t\":\"one\"}"));
+ assertFalse(json, json.contains("\"s\":"));
+ }
+
+ /**
+ * For parameterized type in a map, Gson ignores the more-specific type and sticks to the
+ * declared type
+ */
+ public void testMapOfParameterizedSubclassFields() {
+ Map<String, ParameterizedBase<String>> map = new HashMap<String, ParameterizedBase<String>>();
+ map.put("base", new ParameterizedBase<String>("one"));
+ map.put("sub", new ParameterizedSub<String>("two", "three"));
+ ClassWithContainersOfParameterizedBaseFields target =
+ new ClassWithContainersOfParameterizedBaseFields(null, map);
+ JsonObject json = gson.toJsonTree(target).getAsJsonObject().get("map").getAsJsonObject();
+ assertEquals("one", json.get("base").getAsJsonObject().get("t").getAsString());
+ JsonObject sub = json.get("sub").getAsJsonObject();
+ assertEquals("two", sub.get("t").getAsString());
+ assertNull(sub.get("s"));
+ }
+
+ private static class Base {
+ int b;
+ Base(int b) {
+ this.b = b;
+ }
+ }
+
+ private static class Sub extends Base {
+ int s;
+ Sub(int b, int s) {
+ super(b);
+ this.s = s;
+ }
+ }
+
+ private static class ClassWithBaseFields {
+ Base b;
+ ClassWithBaseFields(Base b) {
+ this.b = b;
+ }
+ }
+
+ private static class ClassWithContainersOfBaseFields {
+ Collection<Base> collection;
+ Map<String, Base> map;
+ ClassWithContainersOfBaseFields(Collection<Base> collection, Map<String, Base> map) {
+ this.collection = collection;
+ this.map = map;
+ }
+ }
+
+ private static class ParameterizedBase<T> {
+ T t;
+ ParameterizedBase(T t) {
+ this.t = t;
+ }
+ }
+
+ private static class ParameterizedSub<T> extends ParameterizedBase<T> {
+ T s;
+ ParameterizedSub(T t, T s) {
+ super(t);
+ this.s = s;
+ }
+ }
+
+ private static class ClassWithParameterizedBaseFields {
+ ParameterizedBase<String> b;
+ ClassWithParameterizedBaseFields(ParameterizedBase<String> b) {
+ this.b = b;
+ }
+ }
+
+ private static class ClassWithContainersOfParameterizedBaseFields {
+ Collection<ParameterizedBase<String>> collection;
+ Map<String, ParameterizedBase<String>> map;
+ ClassWithContainersOfParameterizedBaseFields(Collection<ParameterizedBase<String>> collection,
+ Map<String, ParameterizedBase<String>> map) {
+ this.collection = collection;
+ this.map = map;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java
new file mode 100644
index 00000000..8975c155
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/NamingPolicyTest.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.FieldNamingStrategy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.common.TestTypes.ClassWithSerializedNameFields;
+import com.google.gson.common.TestTypes.StringWrapper;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Field;
+
+/**
+ * Functional tests for naming policies.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class NamingPolicyTest extends TestCase {
+ private GsonBuilder builder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ builder = new GsonBuilder();
+ }
+
+ public void testGsonWithNonDefaultFieldNamingPolicySerialization() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
+ StringWrapper target = new StringWrapper("blah");
+ assertEquals("{\"SomeConstantStringInstanceField\":\""
+ + target.someConstantStringInstanceField + "\"}", gson.toJson(target));
+ }
+
+ public void testGsonWithNonDefaultFieldNamingPolicyDeserialiation() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE).create();
+ String target = "{\"SomeConstantStringInstanceField\":\"someValue\"}";
+ StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class);
+ assertEquals("someValue", deserializedObject.someConstantStringInstanceField);
+ }
+
+ public void testGsonWithLowerCaseDashPolicySerialization() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();
+ StringWrapper target = new StringWrapper("blah");
+ assertEquals("{\"some-constant-string-instance-field\":\""
+ + target.someConstantStringInstanceField + "\"}", gson.toJson(target));
+ }
+
+ public void testGsonWithLowerCaseDashPolicyDeserialiation() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_DASHES).create();
+ String target = "{\"some-constant-string-instance-field\":\"someValue\"}";
+ StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class);
+ assertEquals("someValue", deserializedObject.someConstantStringInstanceField);
+ }
+
+ public void testGsonWithLowerCaseUnderscorePolicySerialization() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .create();
+ StringWrapper target = new StringWrapper("blah");
+ assertEquals("{\"some_constant_string_instance_field\":\""
+ + target.someConstantStringInstanceField + "\"}", gson.toJson(target));
+ }
+
+ public void testGsonWithLowerCaseUnderscorePolicyDeserialiation() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .create();
+ String target = "{\"some_constant_string_instance_field\":\"someValue\"}";
+ StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class);
+ assertEquals("someValue", deserializedObject.someConstantStringInstanceField);
+ }
+
+ public void testGsonWithSerializedNameFieldNamingPolicySerialization() {
+ Gson gson = builder.create();
+ ClassWithSerializedNameFields expected = new ClassWithSerializedNameFields(5, 6);
+ String actual = gson.toJson(expected);
+ assertEquals(expected.getExpectedJson(), actual);
+ }
+
+ public void testGsonWithSerializedNameFieldNamingPolicyDeserialization() {
+ Gson gson = builder.create();
+ ClassWithSerializedNameFields expected = new ClassWithSerializedNameFields(5, 7);
+ ClassWithSerializedNameFields actual =
+ gson.fromJson(expected.getExpectedJson(), ClassWithSerializedNameFields.class);
+ assertEquals(expected.f, actual.f);
+ }
+
+ public void testGsonDuplicateNameUsingSerializedNameFieldNamingPolicySerialization() {
+ Gson gson = builder.create();
+ try {
+ ClassWithDuplicateFields target = new ClassWithDuplicateFields(10);
+ gson.toJson(target);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testGsonWithUpperCamelCaseSpacesPolicySerialiation() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES)
+ .create();
+ StringWrapper target = new StringWrapper("blah");
+ assertEquals("{\"Some Constant String Instance Field\":\""
+ + target.someConstantStringInstanceField + "\"}", gson.toJson(target));
+ }
+
+ public void testGsonWithUpperCamelCaseSpacesPolicyDeserialiation() {
+ Gson gson = builder.setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE_WITH_SPACES)
+ .create();
+ String target = "{\"Some Constant String Instance Field\":\"someValue\"}";
+ StringWrapper deserializedObject = gson.fromJson(target, StringWrapper.class);
+ assertEquals("someValue", deserializedObject.someConstantStringInstanceField);
+ }
+
+ public void testDeprecatedNamingStrategy() throws Exception {
+ Gson gson = builder.setFieldNamingStrategy(new UpperCaseNamingStrategy()).create();
+ ClassWithDuplicateFields target = new ClassWithDuplicateFields(10);
+ String actual = gson.toJson(target);
+ assertEquals("{\"A\":10}", actual);
+ }
+
+ public void testComplexFieldNameStrategy() throws Exception {
+ Gson gson = new Gson();
+ String json = gson.toJson(new ClassWithComplexFieldName(10));
+ String escapedFieldName = "@value\\\"_s$\\\\";
+ assertEquals("{\"" + escapedFieldName + "\":10}", json);
+
+ ClassWithComplexFieldName obj = gson.fromJson(json, ClassWithComplexFieldName.class);
+ assertEquals(10, obj.value);
+ }
+
+ /** http://code.google.com/p/google-gson/issues/detail?id=349 */
+ public void testAtSignInSerializedName() {
+ assertEquals("{\"@foo\":\"bar\"}", new Gson().toJson(new AtName()));
+ }
+
+ static class AtName {
+ @SerializedName("@foo") String f = "bar";
+ }
+
+ private static class UpperCaseNamingStrategy implements FieldNamingStrategy {
+ public String translateName(Field f) {
+ return f.getName().toUpperCase();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class ClassWithDuplicateFields {
+ public Integer a;
+ @SerializedName("a") public Double b;
+
+ public ClassWithDuplicateFields(Integer a) {
+ this(a, null);
+ }
+
+ public ClassWithDuplicateFields(Double b) {
+ this(null, b);
+ }
+
+ public ClassWithDuplicateFields(Integer a, Double b) {
+ this.a = a;
+ this.b = b;
+ }
+ }
+
+ private static class ClassWithComplexFieldName {
+ @SerializedName("@value\"_s$\\") public final long value;
+
+ ClassWithComplexFieldName(long value) {
+ this.value = value;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/NullObjectAndFieldTest.java b/gson/src/test/java/com/google/gson/functional/NullObjectAndFieldTest.java
new file mode 100755
index 00000000..742ee221
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/NullObjectAndFieldTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.ClassWithObjects;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+import java.util.Collection;
+
+/**
+ * Functional tests for the different cases for serializing (or ignoring) null fields and object.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class NullObjectAndFieldTest extends TestCase {
+ private GsonBuilder gsonBuilder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gsonBuilder = new GsonBuilder().serializeNulls();
+ }
+
+ public void testTopLevelNullObjectSerialization() {
+ Gson gson = gsonBuilder.create();
+ String actual = gson.toJson(null);
+ assertEquals("null", actual);
+
+ actual = gson.toJson(null, String.class);
+ assertEquals("null", actual);
+ }
+
+ public void testTopLevelNullObjectDeserialization() throws Exception {
+ Gson gson = gsonBuilder.create();
+ String actual = gson.fromJson("null", String.class);
+ assertNull(actual);
+ }
+
+ public void testExplicitSerializationOfNulls() {
+ Gson gson = gsonBuilder.create();
+ ClassWithObjects target = new ClassWithObjects(null);
+ String actual = gson.toJson(target);
+ String expected = "{\"bag\":null}";
+ assertEquals(expected, actual);
+ }
+
+ public void testExplicitDeserializationOfNulls() throws Exception {
+ Gson gson = gsonBuilder.create();
+ ClassWithObjects target = gson.fromJson("{\"bag\":null}", ClassWithObjects.class);
+ assertNull(target.bag);
+ }
+
+ public void testExplicitSerializationOfNullArrayMembers() {
+ Gson gson = gsonBuilder.create();
+ ClassWithMembers target = new ClassWithMembers();
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"array\":null"));
+ }
+
+ /**
+ * Added to verify http://code.google.com/p/google-gson/issues/detail?id=68
+ */
+ public void testNullWrappedPrimitiveMemberSerialization() {
+ Gson gson = gsonBuilder.serializeNulls().create();
+ ClassWithNullWrappedPrimitive target = new ClassWithNullWrappedPrimitive();
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"value\":null"));
+ }
+
+ /**
+ * Added to verify http://code.google.com/p/google-gson/issues/detail?id=68
+ */
+ public void testNullWrappedPrimitiveMemberDeserialization() {
+ Gson gson = gsonBuilder.create();
+ String json = "{'value':null}";
+ ClassWithNullWrappedPrimitive target = gson.fromJson(json, ClassWithNullWrappedPrimitive.class);
+ assertNull(target.value);
+ }
+
+ public void testExplicitSerializationOfNullCollectionMembers() {
+ Gson gson = gsonBuilder.create();
+ ClassWithMembers target = new ClassWithMembers();
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"col\":null"));
+ }
+
+ public void testExplicitSerializationOfNullStringMembers() {
+ Gson gson = gsonBuilder.create();
+ ClassWithMembers target = new ClassWithMembers();
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"str\":null"));
+ }
+
+ public void testCustomSerializationOfNulls() {
+ gsonBuilder.registerTypeAdapter(ClassWithObjects.class, new ClassWithObjectsSerializer());
+ Gson gson = gsonBuilder.create();
+ ClassWithObjects target = new ClassWithObjects(new BagOfPrimitives());
+ String actual = gson.toJson(target);
+ String expected = "{\"bag\":null}";
+ assertEquals(expected, actual);
+ }
+
+ public void testPrintPrintingObjectWithNulls() throws Exception {
+ gsonBuilder = new GsonBuilder();
+ Gson gson = gsonBuilder.create();
+ String result = gson.toJson(new ClassWithMembers());
+ assertEquals("{}", result);
+
+ gson = gsonBuilder.serializeNulls().create();
+ result = gson.toJson(new ClassWithMembers());
+ assertTrue(result.contains("\"str\":null"));
+ }
+
+ public void testPrintPrintingArraysWithNulls() throws Exception {
+ gsonBuilder = new GsonBuilder();
+ Gson gson = gsonBuilder.create();
+ String result = gson.toJson(new String[] { "1", null, "3" });
+ assertEquals("[\"1\",null,\"3\"]", result);
+
+ gson = gsonBuilder.serializeNulls().create();
+ result = gson.toJson(new String[] { "1", null, "3" });
+ assertEquals("[\"1\",null,\"3\"]", result);
+ }
+
+ // test for issue 389
+ public void testAbsentJsonElementsAreSetToNull() {
+ Gson gson = new Gson();
+ ClassWithInitializedMembers target =
+ gson.fromJson("{array:[1,2,3]}", ClassWithInitializedMembers.class);
+ assertTrue(target.array.length == 3 && target.array[1] == 2);
+ assertEquals(ClassWithInitializedMembers.MY_STRING_DEFAULT, target.str1);
+ assertNull(target.str2);
+ assertEquals(ClassWithInitializedMembers.MY_INT_DEFAULT, target.int1);
+ assertEquals(0, target.int2); // test the default value of a primitive int field per JVM spec
+ assertEquals(ClassWithInitializedMembers.MY_BOOLEAN_DEFAULT, target.bool1);
+ assertFalse(target.bool2); // test the default value of a primitive boolean field per JVM spec
+ }
+
+ public static class ClassWithInitializedMembers {
+ // Using a mix of no-args constructor and field initializers
+ // Also, some fields are intialized and some are not (so initialized per JVM spec)
+ public static final String MY_STRING_DEFAULT = "string";
+ private static final int MY_INT_DEFAULT = 2;
+ private static final boolean MY_BOOLEAN_DEFAULT = true;
+ int[] array;
+ String str1, str2;
+ int int1 = MY_INT_DEFAULT;
+ int int2;
+ boolean bool1 = MY_BOOLEAN_DEFAULT;
+ boolean bool2;
+ public ClassWithInitializedMembers() {
+ str1 = MY_STRING_DEFAULT;
+ }
+ }
+
+ private static class ClassWithNullWrappedPrimitive {
+ private Long value;
+ }
+
+ @SuppressWarnings("unused")
+ private static class ClassWithMembers {
+ String str;
+ int[] array;
+ Collection<String> col;
+ }
+
+ private static class ClassWithObjectsSerializer implements JsonSerializer<ClassWithObjects> {
+ public JsonElement serialize(ClassWithObjects src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ JsonObject obj = new JsonObject();
+ obj.add("bag", JsonNull.INSTANCE);
+ return obj;
+ }
+ }
+
+ public void testExplicitNullSetsFieldToNullDuringDeserialization() {
+ Gson gson = new Gson();
+ String json = "{value:null}";
+ ObjectWithField obj = gson.fromJson(json, ObjectWithField.class);
+ assertNull(obj.value);
+ }
+
+ public void testCustomTypeAdapterPassesNullSerialization() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(ObjectWithField.class, new JsonSerializer<ObjectWithField>() {
+ public JsonElement serialize(ObjectWithField src, Type typeOfSrc,
+ JsonSerializationContext context) {
+ return context.serialize(null);
+ }
+ }).create();
+ ObjectWithField target = new ObjectWithField();
+ target.value = "value1";
+ String json = gson.toJson(target);
+ assertFalse(json.contains("value1"));
+ }
+
+ public void testCustomTypeAdapterPassesNullDesrialization() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(ObjectWithField.class, new JsonDeserializer<ObjectWithField>() {
+ public ObjectWithField deserialize(JsonElement json, Type type,
+ JsonDeserializationContext context) {
+ return context.deserialize(null, type);
+ }
+ }).create();
+ String json = "{value:'value1'}";
+ ObjectWithField target = gson.fromJson(json, ObjectWithField.class);
+ assertNull(target);
+ }
+
+ private static class ObjectWithField {
+ String value = "";
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ObjectTest.java b/gson/src/test/java/com/google/gson/functional/ObjectTest.java
new file mode 100644
index 00000000..de1219a6
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ObjectTest.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.InstanceCreator;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.common.TestTypes.ArrayOfObjects;
+import com.google.gson.common.TestTypes.BagOfPrimitiveWrappers;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.ClassWithArray;
+import com.google.gson.common.TestTypes.ClassWithNoFields;
+import com.google.gson.common.TestTypes.ClassWithObjects;
+import com.google.gson.common.TestTypes.ClassWithTransientFields;
+import com.google.gson.common.TestTypes.Nested;
+import com.google.gson.common.TestTypes.PrimitiveArray;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for Json serialization and deserialization of regular classes.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ObjectTest extends TestCase {
+ private Gson gson;
+ private TimeZone oldTimeZone = TimeZone.getDefault();
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+
+ TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
+ Locale.setDefault(Locale.US);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TimeZone.setDefault(oldTimeZone);
+ super.tearDown();
+ }
+ public void testJsonInSingleQuotesDeserialization() {
+ String json = "{'stringValue':'no message','intValue':10,'longValue':20}";
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals("no message", target.stringValue);
+ assertEquals(10, target.intValue);
+ assertEquals(20, target.longValue);
+ }
+
+ public void testJsonInMixedQuotesDeserialization() {
+ String json = "{\"stringValue\":'no message','intValue':10,'longValue':20}";
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals("no message", target.stringValue);
+ assertEquals(10, target.intValue);
+ assertEquals(20, target.longValue);
+ }
+
+ public void testBagOfPrimitivesSerialization() throws Exception {
+ BagOfPrimitives target = new BagOfPrimitives(10, 20, false, "stringValue");
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testBagOfPrimitivesDeserialization() throws Exception {
+ BagOfPrimitives src = new BagOfPrimitives(10, 20, false, "stringValue");
+ String json = src.getExpectedJson();
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ public void testBagOfPrimitiveWrappersSerialization() throws Exception {
+ BagOfPrimitiveWrappers target = new BagOfPrimitiveWrappers(10L, 20, false);
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testBagOfPrimitiveWrappersDeserialization() throws Exception {
+ BagOfPrimitiveWrappers target = new BagOfPrimitiveWrappers(10L, 20, false);
+ String jsonString = target.getExpectedJson();
+ target = gson.fromJson(jsonString, BagOfPrimitiveWrappers.class);
+ assertEquals(jsonString, target.getExpectedJson());
+ }
+
+ public void testClassWithTransientFieldsSerialization() throws Exception {
+ ClassWithTransientFields<Long> target = new ClassWithTransientFields<Long>(1L);
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void testClassWithTransientFieldsDeserialization() throws Exception {
+ String json = "{\"longValue\":[1]}";
+ ClassWithTransientFields target = gson.fromJson(json, ClassWithTransientFields.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void testClassWithTransientFieldsDeserializationTransientFieldsPassedInJsonAreIgnored()
+ throws Exception {
+ String json = "{\"transientLongValue\":1,\"longValue\":[1]}";
+ ClassWithTransientFields target = gson.fromJson(json, ClassWithTransientFields.class);
+ assertFalse(target.transientLongValue != 1);
+ }
+
+ public void testClassWithNoFieldsSerialization() throws Exception {
+ assertEquals("{}", gson.toJson(new ClassWithNoFields()));
+ }
+
+ public void testClassWithNoFieldsDeserialization() throws Exception {
+ String json = "{}";
+ ClassWithNoFields target = gson.fromJson(json, ClassWithNoFields.class);
+ ClassWithNoFields expected = new ClassWithNoFields();
+ assertEquals(expected, target);
+ }
+
+ public void testNestedSerialization() throws Exception {
+ Nested target = new Nested(new BagOfPrimitives(10, 20, false, "stringValue"),
+ new BagOfPrimitives(30, 40, true, "stringValue"));
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testNestedDeserialization() throws Exception {
+ String json = "{\"primitive1\":{\"longValue\":10,\"intValue\":20,\"booleanValue\":false,"
+ + "\"stringValue\":\"stringValue\"},\"primitive2\":{\"longValue\":30,\"intValue\":40,"
+ + "\"booleanValue\":true,\"stringValue\":\"stringValue\"}}";
+ Nested target = gson.fromJson(json, Nested.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+ public void testNullSerialization() throws Exception {
+ assertEquals("null", gson.toJson(null));
+ }
+
+ public void testEmptyStringDeserialization() throws Exception {
+ Object object = gson.fromJson("", Object.class);
+ assertNull(object);
+ }
+
+ public void testTruncatedDeserialization() {
+ try {
+ gson.fromJson("[\"a\", \"b\",", new TypeToken<List<String>>() {}.getType());
+ fail();
+ } catch (JsonParseException expected) {
+ }
+ }
+
+ public void testNullDeserialization() throws Exception {
+ String myNullObject = null;
+ Object object = gson.fromJson(myNullObject, Object.class);
+ assertNull(object);
+ }
+
+ public void testNullFieldsSerialization() throws Exception {
+ Nested target = new Nested(new BagOfPrimitives(10, 20, false, "stringValue"), null);
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testNullFieldsDeserialization() throws Exception {
+ String json = "{\"primitive1\":{\"longValue\":10,\"intValue\":20,\"booleanValue\":false"
+ + ",\"stringValue\":\"stringValue\"}}";
+ Nested target = gson.fromJson(json, Nested.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ public void testArrayOfObjectsSerialization() throws Exception {
+ ArrayOfObjects target = new ArrayOfObjects();
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testArrayOfObjectsDeserialization() throws Exception {
+ String json = new ArrayOfObjects().getExpectedJson();
+ ArrayOfObjects target = gson.fromJson(json, ArrayOfObjects.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ public void testArrayOfArraysSerialization() throws Exception {
+ ArrayOfArrays target = new ArrayOfArrays();
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testArrayOfArraysDeserialization() throws Exception {
+ String json = new ArrayOfArrays().getExpectedJson();
+ ArrayOfArrays target = gson.fromJson(json, ArrayOfArrays.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ public void testArrayOfObjectsAsFields() throws Exception {
+ ClassWithObjects classWithObjects = new ClassWithObjects();
+ BagOfPrimitives bagOfPrimitives = new BagOfPrimitives();
+ String stringValue = "someStringValueInArray";
+ String classWithObjectsJson = gson.toJson(classWithObjects);
+ String bagOfPrimitivesJson = gson.toJson(bagOfPrimitives);
+
+ ClassWithArray classWithArray = new ClassWithArray(
+ new Object[] { stringValue, classWithObjects, bagOfPrimitives });
+ String json = gson.toJson(classWithArray);
+
+ assertTrue(json.contains(classWithObjectsJson));
+ assertTrue(json.contains(bagOfPrimitivesJson));
+ assertTrue(json.contains("\"" + stringValue + "\""));
+ }
+
+ /**
+ * Created in response to Issue 14: http://code.google.com/p/google-gson/issues/detail?id=14
+ */
+ public void testNullArraysDeserialization() throws Exception {
+ String json = "{\"array\": null}";
+ ClassWithArray target = gson.fromJson(json, ClassWithArray.class);
+ assertNull(target.array);
+ }
+
+ /**
+ * Created in response to Issue 14: http://code.google.com/p/google-gson/issues/detail?id=14
+ */
+ public void testNullObjectFieldsDeserialization() throws Exception {
+ String json = "{\"bag\": null}";
+ ClassWithObjects target = gson.fromJson(json, ClassWithObjects.class);
+ assertNull(target.bag);
+ }
+
+ public void testEmptyCollectionInAnObjectDeserialization() throws Exception {
+ String json = "{\"children\":[]}";
+ ClassWithCollectionField target = gson.fromJson(json, ClassWithCollectionField.class);
+ assertNotNull(target);
+ assertTrue(target.children.isEmpty());
+ }
+
+ private static class ClassWithCollectionField {
+ Collection<String> children = new ArrayList<String>();
+ }
+
+ public void testPrimitiveArrayInAnObjectDeserialization() throws Exception {
+ String json = "{\"longArray\":[0,1,2,3,4,5,6,7,8,9]}";
+ PrimitiveArray target = gson.fromJson(json, PrimitiveArray.class);
+ assertEquals(json, target.getExpectedJson());
+ }
+
+ /**
+ * Created in response to Issue 14: http://code.google.com/p/google-gson/issues/detail?id=14
+ */
+ public void testNullPrimitiveFieldsDeserialization() throws Exception {
+ String json = "{\"longValue\":null}";
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(BagOfPrimitives.DEFAULT_VALUE, target.longValue);
+ }
+
+ public void testEmptyCollectionInAnObjectSerialization() throws Exception {
+ ClassWithCollectionField target = new ClassWithCollectionField();
+ assertEquals("{\"children\":[]}", gson.toJson(target));
+ }
+
+ public void testPrivateNoArgConstructorDeserialization() throws Exception {
+ ClassWithPrivateNoArgsConstructor target =
+ gson.fromJson("{\"a\":20}", ClassWithPrivateNoArgsConstructor.class);
+ assertEquals(20, target.a);
+ }
+
+ public void testAnonymousLocalClassesSerialization() throws Exception {
+ assertEquals("null", gson.toJson(new ClassWithNoFields() {
+ // empty anonymous class
+ }));
+ }
+
+ public void testAnonymousLocalClassesCustomSerialization() throws Exception {
+ gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(ClassWithNoFields.class,
+ new JsonSerializer<ClassWithNoFields>() {
+ public JsonElement serialize(
+ ClassWithNoFields src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonObject();
+ }
+ }).create();
+
+ assertEquals("null", gson.toJson(new ClassWithNoFields() {
+ // empty anonymous class
+ }));
+ }
+
+ public void testPrimitiveArrayFieldSerialization() {
+ PrimitiveArray target = new PrimitiveArray(new long[] { 1L, 2L, 3L });
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ /**
+ * Tests that a class field with type Object can be serialized properly.
+ * See issue 54
+ */
+ public void testClassWithObjectFieldSerialization() {
+ ClassWithObjectField obj = new ClassWithObjectField();
+ obj.member = "abc";
+ String json = gson.toJson(obj);
+ assertTrue(json.contains("abc"));
+ }
+
+ private static class ClassWithObjectField {
+ @SuppressWarnings("unused")
+ Object member;
+ }
+
+ public void testInnerClassSerialization() {
+ Parent p = new Parent();
+ Parent.Child c = p.new Child();
+ String json = gson.toJson(c);
+ assertTrue(json.contains("value2"));
+ assertFalse(json.contains("value1"));
+ }
+
+ public void testInnerClassDeserialization() {
+ final Parent p = new Parent();
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ Parent.Child.class, new InstanceCreator<Parent.Child>() {
+ public Parent.Child createInstance(Type type) {
+ return p.new Child();
+ }
+ }).create();
+ String json = "{'value2':3}";
+ Parent.Child c = gson.fromJson(json, Parent.Child.class);
+ assertEquals(3, c.value2);
+ }
+
+ private static class Parent {
+ @SuppressWarnings("unused")
+ int value1 = 1;
+ private class Child {
+ int value2 = 2;
+ }
+ }
+
+ private static class ArrayOfArrays {
+ private final BagOfPrimitives[][] elements;
+ public ArrayOfArrays() {
+ elements = new BagOfPrimitives[3][2];
+ for (int i = 0; i < elements.length; ++i) {
+ BagOfPrimitives[] row = elements[i];
+ for (int j = 0; j < row.length; ++j) {
+ row[j] = new BagOfPrimitives(i+j, i*j, false, i+"_"+j);
+ }
+ }
+ }
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder("{\"elements\":[");
+ boolean first = true;
+ for (BagOfPrimitives[] row : elements) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ boolean firstOfRow = true;
+ sb.append("[");
+ for (BagOfPrimitives element : row) {
+ if (firstOfRow) {
+ firstOfRow = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(element.getExpectedJson());
+ }
+ sb.append("]");
+ }
+ sb.append("]}");
+ return sb.toString();
+ }
+ }
+
+ private static class ClassWithPrivateNoArgsConstructor {
+ public int a;
+ private ClassWithPrivateNoArgsConstructor() {
+ a = 10;
+ }
+ }
+
+ /**
+ * In response to Issue 41 http://code.google.com/p/google-gson/issues/detail?id=41
+ */
+ public void testObjectFieldNamesWithoutQuotesDeserialization() {
+ String json = "{longValue:1,'booleanValue':true,\"stringValue\":'bar'}";
+ BagOfPrimitives bag = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(1, bag.longValue);
+ assertTrue(bag.booleanValue);
+ assertEquals("bar", bag.stringValue);
+ }
+
+ public void testStringFieldWithNumberValueDeserialization() {
+ String json = "{\"stringValue\":1}";
+ BagOfPrimitives bag = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals("1", bag.stringValue);
+
+ json = "{\"stringValue\":1.5E+6}";
+ bag = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals("1.5E+6", bag.stringValue);
+
+ json = "{\"stringValue\":true}";
+ bag = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals("true", bag.stringValue);
+ }
+
+ /**
+ * Created to reproduce issue 140
+ */
+ public void testStringFieldWithEmptyValueSerialization() {
+ ClassWithEmptyStringFields target = new ClassWithEmptyStringFields();
+ target.a = "5794749";
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"a\":\"5794749\""));
+ assertTrue(json.contains("\"b\":\"\""));
+ assertTrue(json.contains("\"c\":\"\""));
+ }
+
+ /**
+ * Created to reproduce issue 140
+ */
+ public void testStringFieldWithEmptyValueDeserialization() {
+ String json = "{a:\"5794749\",b:\"\",c:\"\"}";
+ ClassWithEmptyStringFields target = gson.fromJson(json, ClassWithEmptyStringFields.class);
+ assertEquals("5794749", target.a);
+ assertEquals("", target.b);
+ assertEquals("", target.c);
+ }
+
+ private static class ClassWithEmptyStringFields {
+ String a = "";
+ String b = "";
+ String c = "";
+ }
+
+ public void testJsonObjectSerialization() {
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ JsonObject obj = new JsonObject();
+ String json = gson.toJson(obj);
+ assertEquals("{}", json);
+ }
+
+ /**
+ * Test for issue 215.
+ */
+ public void testSingletonLists() {
+ Gson gson = new Gson();
+ Product product = new Product();
+ assertEquals("{\"attributes\":[],\"departments\":[]}",
+ gson.toJson(product));
+ gson.fromJson(gson.toJson(product), Product.class);
+
+ product.departments.add(new Department());
+ assertEquals("{\"attributes\":[],\"departments\":[{\"name\":\"abc\",\"code\":\"123\"}]}",
+ gson.toJson(product));
+ gson.fromJson(gson.toJson(product), Product.class);
+
+ product.attributes.add("456");
+ assertEquals("{\"attributes\":[\"456\"],\"departments\":[{\"name\":\"abc\",\"code\":\"123\"}]}",
+ gson.toJson(product));
+ gson.fromJson(gson.toJson(product), Product.class);
+ }
+
+ // http://code.google.com/p/google-gson/issues/detail?id=270
+ public void testDateAsMapObjectField() {
+ HasObjectMap a = new HasObjectMap();
+ a.map.put("date", new Date(0));
+ assertEquals("{\"map\":{\"date\":\"Dec 31, 1969 4:00:00 PM\"}}", gson.toJson(a));
+ }
+
+ public class HasObjectMap {
+ Map<String, Object> map = new HashMap<String, Object>();
+ }
+
+ static final class Department {
+ public String name = "abc";
+ public String code = "123";
+ }
+
+ static final class Product {
+ private List<String> attributes = new ArrayList<String>();
+ private List<Department> departments = new ArrayList<Department>();
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java
new file mode 100644
index 00000000..190603de
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ParameterizedTypesTest.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.ParameterizedTypeFixtures.MyParameterizedType;
+import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeAdapter;
+import com.google.gson.ParameterizedTypeFixtures.MyParameterizedTypeInstanceCreator;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.io.Reader;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Functional tests for the serialization and deserialization of parameterized types in Gson.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ParameterizedTypesTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testParameterizedTypesSerialization() throws Exception {
+ MyParameterizedType<Integer> src = new MyParameterizedType<Integer>(10);
+ Type typeOfSrc = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
+ String json = gson.toJson(src, typeOfSrc);
+ assertEquals(src.getExpectedJson(), json);
+ }
+
+ public void testParameterizedTypeDeserialization() throws Exception {
+ BagOfPrimitives bag = new BagOfPrimitives();
+ MyParameterizedType<BagOfPrimitives> expected = new MyParameterizedType<BagOfPrimitives>(bag);
+ Type expectedType = new TypeToken<MyParameterizedType<BagOfPrimitives>>() {}.getType();
+ BagOfPrimitives bagDefaultInstance = new BagOfPrimitives();
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ expectedType, new MyParameterizedTypeInstanceCreator<BagOfPrimitives>(bagDefaultInstance))
+ .create();
+
+ String json = expected.getExpectedJson();
+ MyParameterizedType<BagOfPrimitives> actual = gson.fromJson(json, expectedType);
+ assertEquals(expected, actual);
+ }
+
+ public void testTypesWithMultipleParametersSerialization() throws Exception {
+ MultiParameters<Integer, Float, Double, String, BagOfPrimitives> src =
+ new MultiParameters<Integer, Float, Double, String, BagOfPrimitives>(10, 1.0F, 2.1D,
+ "abc", new BagOfPrimitives());
+ Type typeOfSrc = new TypeToken<MultiParameters<Integer, Float, Double, String,
+ BagOfPrimitives>>() {}.getType();
+ String json = gson.toJson(src, typeOfSrc);
+ String expected = "{\"a\":10,\"b\":1.0,\"c\":2.1,\"d\":\"abc\","
+ + "\"e\":{\"longValue\":0,\"intValue\":0,\"booleanValue\":false,\"stringValue\":\"\"}}";
+ assertEquals(expected, json);
+ }
+
+ public void testTypesWithMultipleParametersDeserialization() throws Exception {
+ Type typeOfTarget = new TypeToken<MultiParameters<Integer, Float, Double, String,
+ BagOfPrimitives>>() {}.getType();
+ String json = "{\"a\":10,\"b\":1.0,\"c\":2.1,\"d\":\"abc\","
+ + "\"e\":{\"longValue\":0,\"intValue\":0,\"booleanValue\":false,\"stringValue\":\"\"}}";
+ MultiParameters<Integer, Float, Double, String, BagOfPrimitives> target =
+ gson.fromJson(json, typeOfTarget);
+ MultiParameters<Integer, Float, Double, String, BagOfPrimitives> expected =
+ new MultiParameters<Integer, Float, Double, String, BagOfPrimitives>(10, 1.0F, 2.1D,
+ "abc", new BagOfPrimitives());
+ assertEquals(expected, target);
+ }
+
+ public void testParameterizedTypeWithCustomSerializer() {
+ Type ptIntegerType = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
+ Type ptStringType = new TypeToken<MyParameterizedType<String>>() {}.getType();
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(ptIntegerType, new MyParameterizedTypeAdapter<Integer>())
+ .registerTypeAdapter(ptStringType, new MyParameterizedTypeAdapter<String>())
+ .create();
+ MyParameterizedType<Integer> intTarget = new MyParameterizedType<Integer>(10);
+ String json = gson.toJson(intTarget, ptIntegerType);
+ assertEquals(MyParameterizedTypeAdapter.<Integer>getExpectedJson(intTarget), json);
+
+ MyParameterizedType<String> stringTarget = new MyParameterizedType<String>("abc");
+ json = gson.toJson(stringTarget, ptStringType);
+ assertEquals(MyParameterizedTypeAdapter.<String>getExpectedJson(stringTarget), json);
+ }
+
+ public void testParameterizedTypesWithCustomDeserializer() {
+ Type ptIntegerType = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
+ Type ptStringType = new TypeToken<MyParameterizedType<String>>() {}.getType();
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ ptIntegerType, new MyParameterizedTypeAdapter<Integer>())
+ .registerTypeAdapter(ptStringType, new MyParameterizedTypeAdapter<String>())
+ .registerTypeAdapter(ptStringType, new MyParameterizedTypeInstanceCreator<String>(""))
+ .registerTypeAdapter(ptIntegerType,
+ new MyParameterizedTypeInstanceCreator<Integer>(new Integer(0)))
+ .create();
+
+ MyParameterizedType<Integer> src = new MyParameterizedType<Integer>(10);
+ String json = MyParameterizedTypeAdapter.<Integer>getExpectedJson(src);
+ MyParameterizedType<Integer> intTarget = gson.fromJson(json, ptIntegerType);
+ assertEquals(10, intTarget.value.intValue());
+
+ MyParameterizedType<String> srcStr = new MyParameterizedType<String>("abc");
+ json = MyParameterizedTypeAdapter.<String>getExpectedJson(srcStr);
+ MyParameterizedType<String> stringTarget = gson.fromJson(json, ptStringType);
+ assertEquals("abc", stringTarget.value);
+ }
+
+ public void testParameterizedTypesWithWriterSerialization() throws Exception {
+ Writer writer = new StringWriter();
+ MyParameterizedType<Integer> src = new MyParameterizedType<Integer>(10);
+ Type typeOfSrc = new TypeToken<MyParameterizedType<Integer>>() {}.getType();
+ gson.toJson(src, typeOfSrc, writer);
+ assertEquals(src.getExpectedJson(), writer.toString());
+ }
+
+ public void testParameterizedTypeWithReaderDeserialization() throws Exception {
+ BagOfPrimitives bag = new BagOfPrimitives();
+ MyParameterizedType<BagOfPrimitives> expected = new MyParameterizedType<BagOfPrimitives>(bag);
+ Type expectedType = new TypeToken<MyParameterizedType<BagOfPrimitives>>() {}.getType();
+ BagOfPrimitives bagDefaultInstance = new BagOfPrimitives();
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ expectedType, new MyParameterizedTypeInstanceCreator<BagOfPrimitives>(bagDefaultInstance))
+ .create();
+
+ Reader json = new StringReader(expected.getExpectedJson());
+ MyParameterizedType<Integer> actual = gson.fromJson(json, expectedType);
+ assertEquals(expected, actual);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testVariableTypeFieldsAndGenericArraysSerialization() throws Exception {
+ Integer obj = 0;
+ Integer[] array = { 1, 2, 3 };
+ List<Integer> list = new ArrayList<Integer>();
+ list.add(4);
+ list.add(5);
+ List<Integer>[] arrayOfLists = new List[] { list, list };
+
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(obj, array, list, arrayOfLists, list, arrayOfLists);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+
+ assertEquals(objToSerialize.getExpectedJson(), json);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testVariableTypeFieldsAndGenericArraysDeserialization() throws Exception {
+ Integer obj = 0;
+ Integer[] array = { 1, 2, 3 };
+ List<Integer> list = new ArrayList<Integer>();
+ list.add(4);
+ list.add(5);
+ List<Integer>[] arrayOfLists = new List[] { list, list };
+
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(obj, array, list, arrayOfLists, list, arrayOfLists);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+ ObjectWithTypeVariables<Integer> objAfterDeserialization = gson.fromJson(json, typeOfSrc);
+
+ assertEquals(objAfterDeserialization.getExpectedJson(), json);
+ }
+
+ public void testVariableTypeDeserialization() throws Exception {
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(0, null, null, null, null, null);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+ ObjectWithTypeVariables<Integer> objAfterDeserialization = gson.fromJson(json, typeOfSrc);
+
+ assertEquals(objAfterDeserialization.getExpectedJson(), json);
+ }
+
+ public void testVariableTypeArrayDeserialization() throws Exception {
+ Integer[] array = { 1, 2, 3 };
+
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(null, array, null, null, null, null);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+ ObjectWithTypeVariables<Integer> objAfterDeserialization = gson.fromJson(json, typeOfSrc);
+
+ assertEquals(objAfterDeserialization.getExpectedJson(), json);
+ }
+
+ public void testParameterizedTypeWithVariableTypeDeserialization() throws Exception {
+ List<Integer> list = new ArrayList<Integer>();
+ list.add(4);
+ list.add(5);
+
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(null, null, list, null, null, null);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+ ObjectWithTypeVariables<Integer> objAfterDeserialization = gson.fromJson(json, typeOfSrc);
+
+ assertEquals(objAfterDeserialization.getExpectedJson(), json);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testParameterizedTypeGenericArraysSerialization() throws Exception {
+ List<Integer> list = new ArrayList<Integer>();
+ list.add(1);
+ list.add(2);
+ List<Integer>[] arrayOfLists = new List[] { list, list };
+
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(null, null, null, arrayOfLists, null, null);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+ assertEquals("{\"arrayOfListOfTypeParameters\":[[1,2],[1,2]]}", json);
+ }
+
+ @SuppressWarnings("unchecked")
+ public void testParameterizedTypeGenericArraysDeserialization() throws Exception {
+ List<Integer> list = new ArrayList<Integer>();
+ list.add(1);
+ list.add(2);
+ List<Integer>[] arrayOfLists = new List[] { list, list };
+
+ Type typeOfSrc = new TypeToken<ObjectWithTypeVariables<Integer>>() {}.getType();
+ ObjectWithTypeVariables<Integer> objToSerialize =
+ new ObjectWithTypeVariables<Integer>(null, null, null, arrayOfLists, null, null);
+ String json = gson.toJson(objToSerialize, typeOfSrc);
+ ObjectWithTypeVariables<Integer> objAfterDeserialization = gson.fromJson(json, typeOfSrc);
+
+ assertEquals(objAfterDeserialization.getExpectedJson(), json);
+ }
+
+ /**
+ * An test object that has fields that are type variables.
+ *
+ * @param <T> Enforce T to be a string to make writing the "toExpectedJson" method easier.
+ */
+ private static class ObjectWithTypeVariables<T extends Number> {
+ private final T typeParameterObj;
+ private final T[] typeParameterArray;
+ private final List<T> listOfTypeParameters;
+ private final List<T>[] arrayOfListOfTypeParameters;
+ private final List<? extends T> listOfWildcardTypeParameters;
+ private final List<? extends T>[] arrayOfListOfWildcardTypeParameters;
+
+ // For use by Gson
+ @SuppressWarnings("unused")
+ private ObjectWithTypeVariables() {
+ this(null, null, null, null, null, null);
+ }
+
+ public ObjectWithTypeVariables(T obj, T[] array, List<T> list, List<T>[] arrayOfList,
+ List<? extends T> wildcardList, List<? extends T>[] arrayOfWildcardList) {
+ this.typeParameterObj = obj;
+ this.typeParameterArray = array;
+ this.listOfTypeParameters = list;
+ this.arrayOfListOfTypeParameters = arrayOfList;
+ this.listOfWildcardTypeParameters = wildcardList;
+ this.arrayOfListOfWildcardTypeParameters = arrayOfWildcardList;
+ }
+
+ public String getExpectedJson() {
+ StringBuilder sb = new StringBuilder().append("{");
+
+ boolean needsComma = false;
+ if (typeParameterObj != null) {
+ sb.append("\"typeParameterObj\":").append(toString(typeParameterObj));
+ needsComma = true;
+ }
+
+ if (typeParameterArray != null) {
+ if (needsComma) {
+ sb.append(',');
+ }
+ sb.append("\"typeParameterArray\":[");
+ appendObjectsToBuilder(sb, Arrays.asList(typeParameterArray));
+ sb.append(']');
+ needsComma = true;
+ }
+
+ if (listOfTypeParameters != null) {
+ if (needsComma) {
+ sb.append(',');
+ }
+ sb.append("\"listOfTypeParameters\":[");
+ appendObjectsToBuilder(sb, listOfTypeParameters);
+ sb.append(']');
+ needsComma = true;
+ }
+
+ if (arrayOfListOfTypeParameters != null) {
+ if (needsComma) {
+ sb.append(',');
+ }
+ sb.append("\"arrayOfListOfTypeParameters\":[");
+ appendObjectsToBuilder(sb, arrayOfListOfTypeParameters);
+ sb.append(']');
+ needsComma = true;
+ }
+
+ if (listOfWildcardTypeParameters != null) {
+ if (needsComma) {
+ sb.append(',');
+ }
+ sb.append("\"listOfWildcardTypeParameters\":[");
+ appendObjectsToBuilder(sb, listOfWildcardTypeParameters);
+ sb.append(']');
+ needsComma = true;
+ }
+
+ if (arrayOfListOfWildcardTypeParameters != null) {
+ if (needsComma) {
+ sb.append(',');
+ }
+ sb.append("\"arrayOfListOfWildcardTypeParameters\":[");
+ appendObjectsToBuilder(sb, arrayOfListOfWildcardTypeParameters);
+ sb.append(']');
+ needsComma = true;
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ private void appendObjectsToBuilder(StringBuilder sb, Iterable<? extends T> iterable) {
+ boolean isFirst = true;
+ for (T obj : iterable) {
+ if (!isFirst) {
+ sb.append(',');
+ }
+ isFirst = false;
+ sb.append(toString(obj));
+ }
+ }
+
+ private void appendObjectsToBuilder(StringBuilder sb, List<? extends T>[] arrayOfList) {
+ boolean isFirst = true;
+ for (List<? extends T> list : arrayOfList) {
+ if (!isFirst) {
+ sb.append(',');
+ }
+ isFirst = false;
+ if (list != null) {
+ sb.append('[');
+ appendObjectsToBuilder(sb, list);
+ sb.append(']');
+ } else {
+ sb.append("null");
+ }
+ }
+ }
+
+ public String toString(T obj) {
+ return obj.toString();
+ }
+ }
+
+ private static class MultiParameters<A, B, C, D, E> {
+ A a;
+ B b;
+ C c;
+ D d;
+ E e;
+ // For use by Gson
+ @SuppressWarnings("unused")
+ private MultiParameters() {
+ }
+ MultiParameters(A a, B b, C c, D d, E e) {
+ super();
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ }
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((a == null) ? 0 : a.hashCode());
+ result = prime * result + ((b == null) ? 0 : b.hashCode());
+ result = prime * result + ((c == null) ? 0 : c.hashCode());
+ result = prime * result + ((d == null) ? 0 : d.hashCode());
+ result = prime * result + ((e == null) ? 0 : e.hashCode());
+ return result;
+ }
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ MultiParameters<A, B, C, D, E> other = (MultiParameters<A, B, C, D, E>) obj;
+ if (a == null) {
+ if (other.a != null) {
+ return false;
+ }
+ } else if (!a.equals(other.a)) {
+ return false;
+ }
+ if (b == null) {
+ if (other.b != null) {
+ return false;
+ }
+ } else if (!b.equals(other.b)) {
+ return false;
+ }
+ if (c == null) {
+ if (other.c != null) {
+ return false;
+ }
+ } else if (!c.equals(other.c)) {
+ return false;
+ }
+ if (d == null) {
+ if (other.d != null) {
+ return false;
+ }
+ } else if (!d.equals(other.d)) {
+ return false;
+ }
+ if (e == null) {
+ if (other.e != null) {
+ return false;
+ }
+ } else if (!e.equals(other.e)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // Begin: tests to reproduce issue 103
+ private static class Quantity {
+ @SuppressWarnings("unused")
+ int q = 10;
+ }
+ private static class MyQuantity extends Quantity {
+ @SuppressWarnings("unused")
+ int q2 = 20;
+ }
+ private interface Measurable<T> {
+ }
+ private interface Field<T> {
+ }
+ private interface Immutable {
+ }
+
+ public static final class Amount<Q extends Quantity>
+ implements Measurable<Q>, Field<Amount<?>>, Serializable, Immutable {
+ private static final long serialVersionUID = -7560491093120970437L;
+
+ int value = 30;
+ }
+
+ public void testDeepParameterizedTypeSerialization() {
+ Amount<MyQuantity> amount = new Amount<MyQuantity>();
+ String json = gson.toJson(amount);
+ assertTrue(json.contains("value"));
+ assertTrue(json.contains("30"));
+ }
+
+ public void testDeepParameterizedTypeDeserialization() {
+ String json = "{value:30}";
+ Type type = new TypeToken<Amount<MyQuantity>>() {}.getType();
+ Amount<MyQuantity> amount = gson.fromJson(json, type);
+ assertEquals(30, amount.value);
+ }
+ // End: tests to reproduce issue 103
+}
diff --git a/gson/src/test/java/com/google/gson/functional/PrettyPrintingTest.java b/gson/src/test/java/com/google/gson/functional/PrettyPrintingTest.java
new file mode 100644
index 00000000..0aacc9e2
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/PrettyPrintingTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.common.TestTypes.ArrayOfObjects;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Functional tests for pretty printing option.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class PrettyPrintingTest extends TestCase {
+
+ private static final boolean DEBUG = false;
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new GsonBuilder().setPrettyPrinting().create();
+ }
+
+ public void testPrettyPrintList() {
+ BagOfPrimitives b = new BagOfPrimitives();
+ List<BagOfPrimitives> listOfB = new LinkedList<BagOfPrimitives>();
+ for (int i = 0; i < 15; ++i) {
+ listOfB.add(b);
+ }
+ Type typeOfSrc = new TypeToken<List<BagOfPrimitives>>() {}.getType();
+ String json = gson.toJson(listOfB, typeOfSrc);
+ print(json);
+ }
+
+ public void testPrettyPrintArrayOfObjects() {
+ ArrayOfObjects target = new ArrayOfObjects();
+ String json = gson.toJson(target);
+ print(json);
+ }
+
+ public void testPrettyPrintArrayOfPrimitives() {
+ int[] ints = new int[] { 1, 2, 3, 4, 5 };
+ String json = gson.toJson(ints);
+ assertEquals("[\n 1,\n 2,\n 3,\n 4,\n 5\n]", json);
+ }
+
+ public void testPrettyPrintArrayOfPrimitiveArrays() {
+ int[][] ints = new int[][] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 },
+ { 9, 0 }, { 10 } };
+ String json = gson.toJson(ints);
+ assertEquals("[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ],\n [\n 5,\n 6\n ],"
+ + "\n [\n 7,\n 8\n ],\n [\n 9,\n 0\n ],\n [\n 10\n ]\n]", json);
+ }
+
+ public void testPrettyPrintListOfPrimitiveArrays() {
+ List<Integer[]> list = Arrays.asList(new Integer[][] { { 1, 2 }, { 3, 4 },
+ { 5, 6 }, { 7, 8 }, { 9, 0 }, { 10 } });
+ String json = gson.toJson(list);
+ assertEquals("[\n [\n 1,\n 2\n ],\n [\n 3,\n 4\n ],\n [\n 5,\n 6\n ],"
+ + "\n [\n 7,\n 8\n ],\n [\n 9,\n 0\n ],\n [\n 10\n ]\n]", json);
+ }
+
+ public void testMap() {
+ Map<String, Integer> map = new LinkedHashMap<String, Integer>();
+ map.put("abc", 1);
+ map.put("def", 5);
+ String json = gson.toJson(map);
+ assertEquals("{\n \"abc\": 1,\n \"def\": 5\n}", json);
+ }
+
+ // In response to bug 153
+ public void testEmptyMapField() {
+ ClassWithMap obj = new ClassWithMap();
+ obj.map = new LinkedHashMap<String, Integer>();
+ String json = gson.toJson(obj);
+ assertTrue(json.contains("{\n \"map\": {},\n \"value\": 2\n}"));
+ }
+
+ @SuppressWarnings("unused")
+ private static class ClassWithMap {
+ Map<String, Integer> map;
+ int value = 2;
+ }
+
+ public void testMultipleArrays() {
+ int[][][] ints = new int[][][] { { { 1 }, { 2 } } };
+ String json = gson.toJson(ints);
+ assertEquals("[\n [\n [\n 1\n ],\n [\n 2\n ]\n ]\n]", json);
+ }
+
+ private void print(String msg) {
+ if (DEBUG) {
+ System.out.println(msg);
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/PrimitiveCharacterTest.java b/gson/src/test/java/com/google/gson/functional/PrimitiveCharacterTest.java
new file mode 100644
index 00000000..69ff1f3f
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/PrimitiveCharacterTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+
+/**
+ * Functional tests for Java Character values.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class PrimitiveCharacterTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testPrimitiveCharacterAutoboxedSerialization() {
+ assertEquals("\"A\"", gson.toJson('A'));
+ assertEquals("\"A\"", gson.toJson('A', char.class));
+ assertEquals("\"A\"", gson.toJson('A', Character.class));
+ }
+
+ public void testPrimitiveCharacterAutoboxedDeserialization() {
+ char expected = 'a';
+ char actual = gson.fromJson("a", char.class);
+ assertEquals(expected, actual);
+
+ actual = gson.fromJson("\"a\"", char.class);
+ assertEquals(expected, actual);
+
+ actual = gson.fromJson("a", Character.class);
+ assertEquals(expected, actual);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java
new file mode 100644
index 00000000..bb28ed1e
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/PrimitiveTest.java
@@ -0,0 +1,821 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.LongSerializationPolicy;
+import com.google.gson.reflect.TypeToken;
+import java.io.Serializable;
+import java.io.StringReader;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for Json primitive values: integers, and floating point numbers.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class PrimitiveTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testPrimitiveIntegerAutoboxedSerialization() {
+ assertEquals("1", gson.toJson(1));
+ }
+
+ public void testPrimitiveIntegerAutoboxedDeserialization() {
+ int expected = 1;
+ int actual = gson.fromJson("1", int.class);
+ assertEquals(expected, actual);
+
+ actual = gson.fromJson("1", Integer.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testByteSerialization() {
+ assertEquals("1", gson.toJson(1, byte.class));
+ assertEquals("1", gson.toJson(1, Byte.class));
+ }
+
+ public void testShortSerialization() {
+ assertEquals("1", gson.toJson(1, short.class));
+ assertEquals("1", gson.toJson(1, Short.class));
+ }
+
+ public void testByteDeserialization() {
+ Byte target = gson.fromJson("1", Byte.class);
+ assertEquals(1, (byte)target);
+ byte primitive = gson.fromJson("1", byte.class);
+ assertEquals(1, primitive);
+ }
+
+ public void testPrimitiveIntegerAutoboxedInASingleElementArraySerialization() {
+ int target[] = {-9332};
+ assertEquals("[-9332]", gson.toJson(target));
+ assertEquals("[-9332]", gson.toJson(target, int[].class));
+ assertEquals("[-9332]", gson.toJson(target, Integer[].class));
+ }
+
+ public void testReallyLongValuesSerialization() {
+ long value = 333961828784581L;
+ assertEquals("333961828784581", gson.toJson(value));
+ }
+
+ public void testReallyLongValuesDeserialization() {
+ String json = "333961828784581";
+ long value = gson.fromJson(json, Long.class);
+ assertEquals(333961828784581L, value);
+ }
+
+ public void testPrimitiveLongAutoboxedSerialization() {
+ assertEquals("1", gson.toJson(1L, long.class));
+ assertEquals("1", gson.toJson(1L, Long.class));
+ }
+
+ public void testPrimitiveLongAutoboxedDeserialization() {
+ long expected = 1L;
+ long actual = gson.fromJson("1", long.class);
+ assertEquals(expected, actual);
+
+ actual = gson.fromJson("1", Long.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testPrimitiveLongAutoboxedInASingleElementArraySerialization() {
+ long[] target = {-23L};
+ assertEquals("[-23]", gson.toJson(target));
+ assertEquals("[-23]", gson.toJson(target, long[].class));
+ assertEquals("[-23]", gson.toJson(target, Long[].class));
+ }
+
+ public void testPrimitiveBooleanAutoboxedSerialization() {
+ assertEquals("true", gson.toJson(true));
+ assertEquals("false", gson.toJson(false));
+ }
+
+ public void testBooleanDeserialization() {
+ boolean value = gson.fromJson("false", boolean.class);
+ assertEquals(false, value);
+ value = gson.fromJson("true", boolean.class);
+ assertEquals(true, value);
+ }
+
+ public void testPrimitiveBooleanAutoboxedInASingleElementArraySerialization() {
+ boolean target[] = {false};
+ assertEquals("[false]", gson.toJson(target));
+ assertEquals("[false]", gson.toJson(target, boolean[].class));
+ assertEquals("[false]", gson.toJson(target, Boolean[].class));
+ }
+
+ public void testNumberSerialization() {
+ Number expected = 1L;
+ String json = gson.toJson(expected);
+ assertEquals(expected.toString(), json);
+
+ json = gson.toJson(expected, Number.class);
+ assertEquals(expected.toString(), json);
+ }
+
+ public void testNumberDeserialization() {
+ String json = "1";
+ Number expected = new Integer(json);
+ Number actual = gson.fromJson(json, Number.class);
+ assertEquals(expected.intValue(), actual.intValue());
+
+ json = String.valueOf(Long.MAX_VALUE);
+ expected = new Long(json);
+ actual = gson.fromJson(json, Number.class);
+ assertEquals(expected.longValue(), actual.longValue());
+
+ json = "1.0";
+ actual = gson.fromJson(json, Number.class);
+ assertEquals(1L, actual.longValue());
+ }
+
+ public void testPrimitiveDoubleAutoboxedSerialization() {
+ assertEquals("-122.08234335", gson.toJson(-122.08234335));
+ assertEquals("122.08112002", gson.toJson(new Double(122.08112002)));
+ }
+
+ public void testPrimitiveDoubleAutoboxedDeserialization() {
+ double actual = gson.fromJson("-122.08858585", double.class);
+ assertEquals(-122.08858585, actual);
+
+ actual = gson.fromJson("122.023900008000", Double.class);
+ assertEquals(122.023900008, actual);
+ }
+
+ public void testPrimitiveDoubleAutoboxedInASingleElementArraySerialization() {
+ double[] target = {-122.08D};
+ assertEquals("[-122.08]", gson.toJson(target));
+ assertEquals("[-122.08]", gson.toJson(target, double[].class));
+ assertEquals("[-122.08]", gson.toJson(target, Double[].class));
+ }
+
+ public void testDoubleAsStringRepresentationDeserialization() {
+ String doubleValue = "1.0043E+5";
+ Double expected = Double.valueOf(doubleValue);
+ Double actual = gson.fromJson(doubleValue, Double.class);
+ assertEquals(expected, actual);
+
+ double actual1 = gson.fromJson(doubleValue, double.class);
+ assertEquals(expected.doubleValue(), actual1);
+ }
+
+ public void testDoubleNoFractAsStringRepresentationDeserialization() {
+ String doubleValue = "1E+5";
+ Double expected = Double.valueOf(doubleValue);
+ Double actual = gson.fromJson(doubleValue, Double.class);
+ assertEquals(expected, actual);
+
+ double actual1 = gson.fromJson(doubleValue, double.class);
+ assertEquals(expected.doubleValue(), actual1);
+ }
+
+ public void testDoubleArrayDeserialization() {
+ String json = "[0.0, 0.004761904761904762, 3.4013606962703525E-4, 7.936508173034305E-4,"
+ + "0.0011904761904761906, 0.0]";
+ double[] values = gson.fromJson(json, double[].class);
+ assertEquals(6, values.length);
+ assertEquals(0.0, values[0]);
+ assertEquals(0.004761904761904762, values[1]);
+ assertEquals(3.4013606962703525E-4, values[2]);
+ assertEquals(7.936508173034305E-4, values[3]);
+ assertEquals(0.0011904761904761906, values[4]);
+ assertEquals(0.0, values[5]);
+ }
+
+ public void testLargeDoubleDeserialization() {
+ String doubleValue = "1.234567899E8";
+ Double expected = Double.valueOf(doubleValue);
+ Double actual = gson.fromJson(doubleValue, Double.class);
+ assertEquals(expected, actual);
+
+ double actual1 = gson.fromJson(doubleValue, double.class);
+ assertEquals(expected.doubleValue(), actual1);
+ }
+
+ public void testBigDecimalSerialization() {
+ BigDecimal target = new BigDecimal("-122.0e-21");
+ String json = gson.toJson(target);
+ assertEquals(target, new BigDecimal(json));
+ }
+
+ public void testBigDecimalDeserialization() {
+ BigDecimal target = new BigDecimal("-122.0e-21");
+ String json = "-122.0e-21";
+ assertEquals(target, gson.fromJson(json, BigDecimal.class));
+ }
+
+ public void testBigDecimalInASingleElementArraySerialization() {
+ BigDecimal[] target = {new BigDecimal("-122.08e-21")};
+ String json = gson.toJson(target);
+ String actual = extractElementFromArray(json);
+ assertEquals(target[0], new BigDecimal(actual));
+
+ json = gson.toJson(target, BigDecimal[].class);
+ actual = extractElementFromArray(json);
+ assertEquals(target[0], new BigDecimal(actual));
+ }
+
+ public void testSmallValueForBigDecimalSerialization() {
+ BigDecimal target = new BigDecimal("1.55");
+ String actual = gson.toJson(target);
+ assertEquals(target.toString(), actual);
+ }
+
+ public void testSmallValueForBigDecimalDeserialization() {
+ BigDecimal expected = new BigDecimal("1.55");
+ BigDecimal actual = gson.fromJson("1.55", BigDecimal.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testBigDecimalPreservePrecisionSerialization() {
+ String expectedValue = "1.000";
+ BigDecimal obj = new BigDecimal(expectedValue);
+ String actualValue = gson.toJson(obj);
+
+ assertEquals(expectedValue, actualValue);
+ }
+
+ public void testBigDecimalPreservePrecisionDeserialization() {
+ String json = "1.000";
+ BigDecimal expected = new BigDecimal(json);
+ BigDecimal actual = gson.fromJson(json, BigDecimal.class);
+
+ assertEquals(expected, actual);
+ }
+
+ public void testBigDecimalAsStringRepresentationDeserialization() {
+ String doubleValue = "0.05E+5";
+ BigDecimal expected = new BigDecimal(doubleValue);
+ BigDecimal actual = gson.fromJson(doubleValue, BigDecimal.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testBigDecimalNoFractAsStringRepresentationDeserialization() {
+ String doubleValue = "5E+5";
+ BigDecimal expected = new BigDecimal(doubleValue);
+ BigDecimal actual = gson.fromJson(doubleValue, BigDecimal.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testBigIntegerSerialization() {
+ BigInteger target = new BigInteger("12121211243123245845384534687435634558945453489543985435");
+ assertEquals(target.toString(), gson.toJson(target));
+ }
+
+ public void testBigIntegerDeserialization() {
+ String json = "12121211243123245845384534687435634558945453489543985435";
+ BigInteger target = new BigInteger(json);
+ assertEquals(target, gson.fromJson(json, BigInteger.class));
+ }
+
+ public void testBigIntegerInASingleElementArraySerialization() {
+ BigInteger[] target = {new BigInteger("1212121243434324323254365345367456456456465464564564")};
+ String json = gson.toJson(target);
+ String actual = extractElementFromArray(json);
+ assertEquals(target[0], new BigInteger(actual));
+
+ json = gson.toJson(target, BigInteger[].class);
+ actual = extractElementFromArray(json);
+ assertEquals(target[0], new BigInteger(actual));
+ }
+
+ public void testSmallValueForBigIntegerSerialization() {
+ BigInteger target = new BigInteger("15");
+ String actual = gson.toJson(target);
+ assertEquals(target.toString(), actual);
+ }
+
+ public void testSmallValueForBigIntegerDeserialization() {
+ BigInteger expected = new BigInteger("15");
+ BigInteger actual = gson.fromJson("15", BigInteger.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testBadValueForBigIntegerDeserialization() {
+ try {
+ gson.fromJson("15.099", BigInteger.class);
+ fail("BigInteger can not be decimal values.");
+ } catch (JsonSyntaxException expected) { }
+ }
+
+ public void testMoreSpecificSerialization() {
+ Gson gson = new Gson();
+ String expected = "This is a string";
+ String expectedJson = gson.toJson(expected);
+
+ Serializable serializableString = expected;
+ String actualJson = gson.toJson(serializableString, Serializable.class);
+ assertFalse(expectedJson.equals(actualJson));
+ }
+
+ private String extractElementFromArray(String json) {
+ return json.substring(json.indexOf('[') + 1, json.indexOf(']'));
+ }
+
+ public void testDoubleNaNSerializationNotSupportedByDefault() {
+ try {
+ double nan = Double.NaN;
+ gson.toJson(nan);
+ fail("Gson should not accept NaN for serialization");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ gson.toJson(Double.NaN);
+ fail("Gson should not accept NaN for serialization");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testDoubleNaNSerialization() {
+ Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ double nan = Double.NaN;
+ assertEquals("NaN", gson.toJson(nan));
+ assertEquals("NaN", gson.toJson(Double.NaN));
+ }
+
+ public void testDoubleNaNDeserialization() {
+ assertTrue(Double.isNaN(gson.fromJson("NaN", Double.class)));
+ assertTrue(Double.isNaN(gson.fromJson("NaN", double.class)));
+ }
+
+ public void testFloatNaNSerializationNotSupportedByDefault() {
+ try {
+ float nan = Float.NaN;
+ gson.toJson(nan);
+ fail("Gson should not accept NaN for serialization");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ gson.toJson(Float.NaN);
+ fail("Gson should not accept NaN for serialization");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testFloatNaNSerialization() {
+ Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ float nan = Float.NaN;
+ assertEquals("NaN", gson.toJson(nan));
+ assertEquals("NaN", gson.toJson(Float.NaN));
+ }
+
+ public void testFloatNaNDeserialization() {
+ assertTrue(Float.isNaN(gson.fromJson("NaN", Float.class)));
+ assertTrue(Float.isNaN(gson.fromJson("NaN", float.class)));
+ }
+
+ public void testBigDecimalNaNDeserializationNotSupported() {
+ try {
+ gson.fromJson("NaN", BigDecimal.class);
+ fail("Gson should not accept NaN for deserialization by default.");
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testDoubleInfinitySerializationNotSupportedByDefault() {
+ try {
+ double infinity = Double.POSITIVE_INFINITY;
+ gson.toJson(infinity);
+ fail("Gson should not accept positive infinity for serialization by default.");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ gson.toJson(Double.POSITIVE_INFINITY);
+ fail("Gson should not accept positive infinity for serialization by default.");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testDoubleInfinitySerialization() {
+ Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ double infinity = Double.POSITIVE_INFINITY;
+ assertEquals("Infinity", gson.toJson(infinity));
+ assertEquals("Infinity", gson.toJson(Double.POSITIVE_INFINITY));
+ }
+
+ public void testDoubleInfinityDeserialization() {
+ assertTrue(Double.isInfinite(gson.fromJson("Infinity", Double.class)));
+ assertTrue(Double.isInfinite(gson.fromJson("Infinity", double.class)));
+ }
+
+ public void testFloatInfinitySerializationNotSupportedByDefault() {
+ try {
+ float infinity = Float.POSITIVE_INFINITY;
+ gson.toJson(infinity);
+ fail("Gson should not accept positive infinity for serialization by default");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ gson.toJson(Float.POSITIVE_INFINITY);
+ fail("Gson should not accept positive infinity for serialization by default");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testFloatInfinitySerialization() {
+ Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ float infinity = Float.POSITIVE_INFINITY;
+ assertEquals("Infinity", gson.toJson(infinity));
+ assertEquals("Infinity", gson.toJson(Float.POSITIVE_INFINITY));
+ }
+
+ public void testFloatInfinityDeserialization() {
+ assertTrue(Float.isInfinite(gson.fromJson("Infinity", Float.class)));
+ assertTrue(Float.isInfinite(gson.fromJson("Infinity", float.class)));
+ }
+
+ public void testBigDecimalInfinityDeserializationNotSupported() {
+ try {
+ gson.fromJson("Infinity", BigDecimal.class);
+ fail("Gson should not accept positive infinity for deserialization with BigDecimal");
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testNegativeInfinitySerializationNotSupportedByDefault() {
+ try {
+ double negativeInfinity = Double.NEGATIVE_INFINITY;
+ gson.toJson(negativeInfinity);
+ fail("Gson should not accept negative infinity for serialization by default");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ gson.toJson(Double.NEGATIVE_INFINITY);
+ fail("Gson should not accept negative infinity for serialization by default");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testNegativeInfinitySerialization() {
+ Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ double negativeInfinity = Double.NEGATIVE_INFINITY;
+ assertEquals("-Infinity", gson.toJson(negativeInfinity));
+ assertEquals("-Infinity", gson.toJson(Double.NEGATIVE_INFINITY));
+ }
+
+ public void testNegativeInfinityDeserialization() {
+ assertTrue(Double.isInfinite(gson.fromJson("-Infinity", double.class)));
+ assertTrue(Double.isInfinite(gson.fromJson("-Infinity", Double.class)));
+ }
+
+ public void testNegativeInfinityFloatSerializationNotSupportedByDefault() {
+ try {
+ float negativeInfinity = Float.NEGATIVE_INFINITY;
+ gson.toJson(negativeInfinity);
+ fail("Gson should not accept negative infinity for serialization by default");
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ gson.toJson(Float.NEGATIVE_INFINITY);
+ fail("Gson should not accept negative infinity for serialization by default");
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testNegativeInfinityFloatSerialization() {
+ Gson gson = new GsonBuilder().serializeSpecialFloatingPointValues().create();
+ float negativeInfinity = Float.NEGATIVE_INFINITY;
+ assertEquals("-Infinity", gson.toJson(negativeInfinity));
+ assertEquals("-Infinity", gson.toJson(Float.NEGATIVE_INFINITY));
+ }
+
+ public void testNegativeInfinityFloatDeserialization() {
+ assertTrue(Float.isInfinite(gson.fromJson("-Infinity", float.class)));
+ assertTrue(Float.isInfinite(gson.fromJson("-Infinity", Float.class)));
+ }
+
+ public void testBigDecimalNegativeInfinityDeserializationNotSupported() {
+ try {
+ gson.fromJson("-Infinity", BigDecimal.class);
+ fail("Gson should not accept positive infinity for deserialization");
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testLongAsStringSerialization() throws Exception {
+ gson = new GsonBuilder().setLongSerializationPolicy(LongSerializationPolicy.STRING).create();
+ String result = gson.toJson(15L);
+ assertEquals("\"15\"", result);
+
+ // Test with an integer and ensure its still a number
+ result = gson.toJson(2);
+ assertEquals("2", result);
+ }
+
+ public void testLongAsStringDeserialization() throws Exception {
+ long value = gson.fromJson("\"15\"", long.class);
+ assertEquals(15, value);
+
+ gson = new GsonBuilder().setLongSerializationPolicy(LongSerializationPolicy.STRING).create();
+ value = gson.fromJson("\"25\"", long.class);
+ assertEquals(25, value);
+ }
+
+ public void testQuotedStringSerializationAndDeserialization() throws Exception {
+ String value = "String Blah Blah Blah...1, 2, 3";
+ String serializedForm = gson.toJson(value);
+ assertEquals("\"" + value + "\"", serializedForm);
+
+ String actual = gson.fromJson(serializedForm, String.class);
+ assertEquals(value, actual);
+ }
+
+ public void testUnquotedStringDeserializationFails() throws Exception {
+ assertEquals("UnquotedSingleWord", gson.fromJson("UnquotedSingleWord", String.class));
+
+ String value = "String Blah Blah Blah...1, 2, 3";
+ try {
+ gson.fromJson(value, String.class);
+ fail();
+ } catch (JsonSyntaxException expected) { }
+ }
+
+ public void testHtmlCharacterSerialization() throws Exception {
+ String target = "<script>var a = 12;</script>";
+ String result = gson.toJson(target);
+ assertFalse(result.equals('"' + target + '"'));
+
+ gson = new GsonBuilder().disableHtmlEscaping().create();
+ result = gson.toJson(target);
+ assertTrue(result.equals('"' + target + '"'));
+ }
+
+ public void testDeserializePrimitiveWrapperAsObjectField() {
+ String json = "{i:10}";
+ ClassWithIntegerField target = gson.fromJson(json, ClassWithIntegerField.class);
+ assertEquals(10, target.i.intValue());
+ }
+
+ private static class ClassWithIntegerField {
+ Integer i;
+ }
+
+ public void testPrimitiveClassLiteral() {
+ assertEquals(1, gson.fromJson("1", int.class).intValue());
+ assertEquals(1, gson.fromJson(new StringReader("1"), int.class).intValue());
+ assertEquals(1, gson.fromJson(new JsonPrimitive(1), int.class).intValue());
+ }
+
+ public void testDeserializeJsonObjectAsLongPrimitive() {
+ try {
+ gson.fromJson("{'abc':1}", long.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsLongWrapper() {
+ try {
+ gson.fromJson("[1,2,3]", Long.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsInt() {
+ try {
+ gson.fromJson("[1, 2, 3, 4]", int.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsInteger() {
+ try {
+ gson.fromJson("{}", Integer.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsShortPrimitive() {
+ try {
+ gson.fromJson("{'abc':1}", short.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsShortWrapper() {
+ try {
+ gson.fromJson("['a','b']", Short.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsDoublePrimitive() {
+ try {
+ gson.fromJson("[1,2]", double.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsDoubleWrapper() {
+ try {
+ gson.fromJson("{'abc':1}", Double.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsFloatPrimitive() {
+ try {
+ gson.fromJson("{'abc':1}", float.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsFloatWrapper() {
+ try {
+ gson.fromJson("[1,2,3]", Float.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsBytePrimitive() {
+ try {
+ gson.fromJson("{'abc':1}", byte.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsByteWrapper() {
+ try {
+ gson.fromJson("[1,2,3,4]", Byte.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsBooleanPrimitive() {
+ try {
+ gson.fromJson("{'abc':1}", boolean.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsBooleanWrapper() {
+ try {
+ gson.fromJson("[1,2,3,4]", Boolean.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsBigDecimal() {
+ try {
+ gson.fromJson("[1,2,3,4]", BigDecimal.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsBigDecimal() {
+ try {
+ gson.fromJson("{'a':1}", BigDecimal.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsBigInteger() {
+ try {
+ gson.fromJson("[1,2,3,4]", BigInteger.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsBigInteger() {
+ try {
+ gson.fromJson("{'c':2}", BigInteger.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonArrayAsNumber() {
+ try {
+ gson.fromJson("[1,2,3,4]", Number.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializeJsonObjectAsNumber() {
+ try {
+ gson.fromJson("{'c':2}", Number.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ }
+
+ public void testDeserializingDecimalPointValueZeroSucceeds() {
+ assertEquals(1, (int) gson.fromJson("1.0", Integer.class));
+ }
+
+ public void testDeserializingNonZeroDecimalPointValuesAsIntegerFails() {
+ try {
+ gson.fromJson("1.02", Byte.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ try {
+ gson.fromJson("1.02", Short.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ try {
+ gson.fromJson("1.02", Integer.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ try {
+ gson.fromJson("1.02", Long.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testDeserializingBigDecimalAsIntegerFails() {
+ try {
+ gson.fromJson("-122.08e-213", Integer.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testDeserializingBigIntegerAsInteger() {
+ try {
+ gson.fromJson("12121211243123245845384534687435634558945453489543985435", Integer.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testDeserializingBigIntegerAsLong() {
+ try {
+ gson.fromJson("12121211243123245845384534687435634558945453489543985435", Long.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testValueVeryCloseToZeroIsZero() {
+ assertEquals(0, (byte) gson.fromJson("-122.08e-2132", byte.class));
+ assertEquals(0, (short) gson.fromJson("-122.08e-2132", short.class));
+ assertEquals(0, (int) gson.fromJson("-122.08e-2132", int.class));
+ assertEquals(0, (long) gson.fromJson("-122.08e-2132", long.class));
+ assertEquals(-0.0f, gson.fromJson("-122.08e-2132", float.class));
+ assertEquals(-0.0, gson.fromJson("-122.08e-2132", double.class));
+ assertEquals(0.0f, gson.fromJson("122.08e-2132", float.class));
+ assertEquals(0.0, gson.fromJson("122.08e-2132", double.class));
+ }
+
+ public void testDeserializingBigDecimalAsFloat() {
+ String json = "-122.08e-2132332";
+ float actual = gson.fromJson(json, float.class);
+ assertEquals(-0.0f, actual);
+ }
+
+ public void testDeserializingBigDecimalAsDouble() {
+ String json = "-122.08e-2132332";
+ double actual = gson.fromJson(json, double.class);
+ assertEquals(-0.0d, actual);
+ }
+
+ public void testDeserializingBigDecimalAsBigIntegerFails() {
+ try {
+ gson.fromJson("-122.08e-213", BigInteger.class);
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testDeserializingBigIntegerAsBigDecimal() {
+ BigDecimal actual =
+ gson.fromJson("12121211243123245845384534687435634558945453489543985435", BigDecimal.class);
+ assertEquals("12121211243123245845384534687435634558945453489543985435", actual.toPlainString());
+ }
+
+ public void testStringsAsBooleans() {
+ String json = "['true', 'false', 'TRUE', 'yes', '1']";
+ assertEquals(Arrays.asList(true, false, true, false, false),
+ gson.<List<Boolean>>fromJson(json, new TypeToken<List<Boolean>>() {}.getType()));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/PrintFormattingTest.java b/gson/src/test/java/com/google/gson/functional/PrintFormattingTest.java
new file mode 100644
index 00000000..7dcbc23c
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/PrintFormattingTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonObject;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.ClassWithTransientFields;
+import com.google.gson.common.TestTypes.Nested;
+import com.google.gson.common.TestTypes.PrimitiveArray;
+
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Functional tests for print formatting.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class PrintFormattingTest extends TestCase {
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public void testCompactFormattingLeavesNoWhiteSpace() {
+ List list = new ArrayList();
+ list.add(new BagOfPrimitives());
+ list.add(new Nested());
+ list.add(new PrimitiveArray());
+ list.add(new ClassWithTransientFields());
+
+ String json = gson.toJson(list);
+ assertContainsNoWhiteSpace(json);
+ }
+
+ public void testJsonObjectWithNullValues() {
+ JsonObject obj = new JsonObject();
+ obj.addProperty("field1", "value1");
+ obj.addProperty("field2", (String) null);
+ String json = gson.toJson(obj);
+ assertTrue(json.contains("field1"));
+ assertFalse(json.contains("field2"));
+ }
+
+ public void testJsonObjectWithNullValuesSerialized() {
+ gson = new GsonBuilder().serializeNulls().create();
+ JsonObject obj = new JsonObject();
+ obj.addProperty("field1", "value1");
+ obj.addProperty("field2", (String) null);
+ String json = gson.toJson(obj);
+ assertTrue(json.contains("field1"));
+ assertTrue(json.contains("field2"));
+ }
+
+ private static void assertContainsNoWhiteSpace(String str) {
+ for (char c : str.toCharArray()) {
+ assertFalse(Character.isWhitespace(c));
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/RawSerializationTest.java b/gson/src/test/java/com/google/gson/functional/RawSerializationTest.java
new file mode 100644
index 00000000..d5e8883e
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/RawSerializationTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.functional;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Unit tests to validate serialization of parameterized types without explicit types
+ *
+ * @author Inderjeet Singh
+ */
+public class RawSerializationTest extends TestCase {
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testCollectionOfPrimitives() {
+ Collection<Integer> ints = Arrays.asList(1, 2, 3, 4, 5);
+ String json = gson.toJson(ints);
+ assertEquals("[1,2,3,4,5]", json);
+ }
+
+ public void testCollectionOfObjects() {
+ Collection<Foo> foos = Arrays.asList(new Foo(1), new Foo(2));
+ String json = gson.toJson(foos);
+ assertEquals("[{\"b\":1},{\"b\":2}]", json);
+ }
+
+ public void testParameterizedObject() {
+ Bar<Foo> bar = new Bar<Foo>(new Foo(1));
+ String expectedJson = "{\"t\":{\"b\":1}}";
+ // Ensure that serialization works without specifying the type explicitly
+ String json = gson.toJson(bar);
+ assertEquals(expectedJson, json);
+ // Ensure that serialization also works when the type is specified explicitly
+ json = gson.toJson(bar, new TypeToken<Bar<Foo>>(){}.getType());
+ assertEquals(expectedJson, json);
+ }
+
+ public void testTwoLevelParameterizedObject() {
+ Bar<Bar<Foo>> bar = new Bar<Bar<Foo>>(new Bar<Foo>(new Foo(1)));
+ String expectedJson = "{\"t\":{\"t\":{\"b\":1}}}";
+ // Ensure that serialization works without specifying the type explicitly
+ String json = gson.toJson(bar);
+ assertEquals(expectedJson, json);
+ // Ensure that serialization also works when the type is specified explicitly
+ json = gson.toJson(bar, new TypeToken<Bar<Bar<Foo>>>(){}.getType());
+ assertEquals(expectedJson, json);
+ }
+
+ public void testThreeLevelParameterizedObject() {
+ Bar<Bar<Bar<Foo>>> bar = new Bar<Bar<Bar<Foo>>>(new Bar<Bar<Foo>>(new Bar<Foo>(new Foo(1))));
+ String expectedJson = "{\"t\":{\"t\":{\"t\":{\"b\":1}}}}";
+ // Ensure that serialization works without specifying the type explicitly
+ String json = gson.toJson(bar);
+ assertEquals(expectedJson, json);
+ // Ensure that serialization also works when the type is specified explicitly
+ json = gson.toJson(bar, new TypeToken<Bar<Bar<Bar<Foo>>>>(){}.getType());
+ assertEquals(expectedJson, json);
+ }
+
+ private static class Foo {
+ @SuppressWarnings("unused")
+ int b;
+ Foo(int b) {
+ this.b = b;
+ }
+ }
+
+ private static class Bar<T> {
+ @SuppressWarnings("unused")
+ T t;
+ Bar(T t) {
+ this.t = t;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ReadersWritersTest.java b/gson/src/test/java/com/google/gson/functional/ReadersWritersTest.java
new file mode 100644
index 00000000..e21fb903
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ReadersWritersTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonStreamParser;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+
+import com.google.gson.reflect.TypeToken;
+import java.util.Map;
+import junit.framework.TestCase;
+
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Functional tests for the support of {@link Reader}s and {@link Writer}s.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class ReadersWritersTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testWriterForSerialization() throws Exception {
+ Writer writer = new StringWriter();
+ BagOfPrimitives src = new BagOfPrimitives();
+ gson.toJson(src, writer);
+ assertEquals(src.getExpectedJson(), writer.toString());
+ }
+
+ public void testReaderForDeserialization() throws Exception {
+ BagOfPrimitives expected = new BagOfPrimitives();
+ Reader json = new StringReader(expected.getExpectedJson());
+ BagOfPrimitives actual = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testTopLevelNullObjectSerializationWithWriter() {
+ StringWriter writer = new StringWriter();
+ gson.toJson(null, writer);
+ assertEquals("null", writer.toString());
+ }
+
+ public void testTopLevelNullObjectDeserializationWithReader() {
+ StringReader reader = new StringReader("null");
+ Integer nullIntObject = gson.fromJson(reader, Integer.class);
+ assertNull(nullIntObject);
+ }
+
+ public void testTopLevelNullObjectSerializationWithWriterAndSerializeNulls() {
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ StringWriter writer = new StringWriter();
+ gson.toJson(null, writer);
+ assertEquals("null", writer.toString());
+ }
+
+ public void testTopLevelNullObjectDeserializationWithReaderAndSerializeNulls() {
+ Gson gson = new GsonBuilder().serializeNulls().create();
+ StringReader reader = new StringReader("null");
+ Integer nullIntObject = gson.fromJson(reader, Integer.class);
+ assertNull(nullIntObject);
+ }
+
+ public void testReadWriteTwoStrings() throws IOException {
+ Gson gson= new Gson();
+ CharArrayWriter writer= new CharArrayWriter();
+ writer.write(gson.toJson("one").toCharArray());
+ writer.write(gson.toJson("two").toCharArray());
+ CharArrayReader reader = new CharArrayReader(writer.toCharArray());
+ JsonStreamParser parser = new JsonStreamParser(reader);
+ String actualOne = gson.fromJson(parser.next(), String.class);
+ assertEquals("one", actualOne);
+ String actualTwo = gson.fromJson(parser.next(), String.class);
+ assertEquals("two", actualTwo);
+ }
+
+ public void testReadWriteTwoObjects() throws IOException {
+ Gson gson= new Gson();
+ CharArrayWriter writer= new CharArrayWriter();
+ BagOfPrimitives expectedOne = new BagOfPrimitives(1, 1, true, "one");
+ writer.write(gson.toJson(expectedOne).toCharArray());
+ BagOfPrimitives expectedTwo = new BagOfPrimitives(2, 2, false, "two");
+ writer.write(gson.toJson(expectedTwo).toCharArray());
+ CharArrayReader reader = new CharArrayReader(writer.toCharArray());
+ JsonStreamParser parser = new JsonStreamParser(reader);
+ BagOfPrimitives actualOne = gson.fromJson(parser.next(), BagOfPrimitives.class);
+ assertEquals("one", actualOne.stringValue);
+ BagOfPrimitives actualTwo = gson.fromJson(parser.next(), BagOfPrimitives.class);
+ assertEquals("two", actualTwo.stringValue);
+ assertFalse(parser.hasNext());
+ }
+
+ public void testTypeMismatchThrowsJsonSyntaxExceptionForStrings() {
+ try {
+ gson.fromJson("true", new TypeToken<Map<String, String>>() {}.getType());
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+
+ public void testTypeMismatchThrowsJsonSyntaxExceptionForReaders() {
+ try {
+ gson.fromJson(new StringReader("true"), new TypeToken<Map<String, String>>() {}.getType());
+ fail();
+ } catch (JsonSyntaxException expected) {
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java
new file mode 100644
index 00000000..c3b0898d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/RuntimeTypeAdapterFactoryFunctionalTest.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.annotations.JsonAdapter;
+import com.google.gson.internal.Streams;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+/**
+ * Functional tests for the RuntimeTypeAdapterFactory feature in extras.
+ */
+public final class RuntimeTypeAdapterFactoryFunctionalTest extends TestCase {
+
+ private final Gson gson = new Gson();
+
+ /**
+ * This test also ensures that {@link TypeAdapterFactory} registered through {@link JsonAdapter}
+ * work correctly for {@link Gson#getDelegateAdapter(TypeAdapterFactory, TypeToken)}.
+ */
+ public void testSubclassesAutomaticallySerialzed() throws Exception {
+ Shape shape = new Circle(25);
+ String json = gson.toJson(shape);
+ shape = gson.fromJson(json, Shape.class);
+ assertEquals(25, ((Circle)shape).radius);
+
+ shape = new Square(15);
+ json = gson.toJson(shape);
+ shape = gson.fromJson(json, Shape.class);
+ assertEquals(15, ((Square)shape).side);
+ assertEquals(ShapeType.SQUARE, shape.type);
+ }
+
+ @JsonAdapter(Shape.JsonAdapterFactory.class)
+ static class Shape {
+ final ShapeType type;
+ Shape(ShapeType type) { this.type = type; }
+ private static final class JsonAdapterFactory extends RuntimeTypeAdapterFactory<Shape> {
+ public JsonAdapterFactory() {
+ super(Shape.class, "type");
+ registerSubtype(Circle.class, ShapeType.CIRCLE.toString());
+ registerSubtype(Square.class, ShapeType.SQUARE.toString());
+ }
+ }
+ }
+
+ public enum ShapeType {
+ SQUARE, CIRCLE
+ }
+
+ private static final class Circle extends Shape {
+ final int radius;
+ Circle(int radius) { super(ShapeType.CIRCLE); this.radius = radius; }
+ }
+
+ private static final class Square extends Shape {
+ final int side;
+ Square(int side) { super(ShapeType.SQUARE); this.side = side; }
+ }
+
+ // Copied from the extras package
+ static class RuntimeTypeAdapterFactory<T> implements TypeAdapterFactory {
+ private final Class<?> baseType;
+ private final String typeFieldName;
+ private final Map<String, Class<?>> labelToSubtype = new LinkedHashMap<String, Class<?>>();
+ private final Map<Class<?>, String> subtypeToLabel = new LinkedHashMap<Class<?>, String>();
+
+ protected RuntimeTypeAdapterFactory(Class<?> baseType, String typeFieldName) {
+ if (typeFieldName == null || baseType == null) {
+ throw new NullPointerException();
+ }
+ this.baseType = baseType;
+ this.typeFieldName = typeFieldName;
+ }
+
+ /**
+ * Creates a new runtime type adapter using for {@code baseType} using {@code
+ * typeFieldName} as the type field name. Type field names are case sensitive.
+ */
+ public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType, String typeFieldName) {
+ return new RuntimeTypeAdapterFactory<T>(baseType, typeFieldName);
+ }
+
+ /**
+ * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as
+ * the type field name.
+ */
+ public static <T> RuntimeTypeAdapterFactory<T> of(Class<T> baseType) {
+ return new RuntimeTypeAdapterFactory<T>(baseType, "type");
+ }
+
+ /**
+ * Registers {@code type} identified by {@code label}. Labels are case
+ * sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or {@code label}
+ * have already been registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type, String label) {
+ if (type == null || label == null) {
+ throw new NullPointerException();
+ }
+ if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) {
+ throw new IllegalArgumentException("types and labels must be unique");
+ }
+ labelToSubtype.put(label, type);
+ subtypeToLabel.put(type, label);
+ return this;
+ }
+
+ /**
+ * Registers {@code type} identified by its {@link Class#getSimpleName simple
+ * name}. Labels are case sensitive.
+ *
+ * @throws IllegalArgumentException if either {@code type} or its simple name
+ * have already been registered on this type adapter.
+ */
+ public RuntimeTypeAdapterFactory<T> registerSubtype(Class<? extends T> type) {
+ return registerSubtype(type, type.getSimpleName());
+ }
+
+ public <R> TypeAdapter<R> create(Gson gson, TypeToken<R> type) {
+ if (type.getRawType() != baseType) {
+ return null;
+ }
+
+ final Map<String, TypeAdapter<?>> labelToDelegate
+ = new LinkedHashMap<String, TypeAdapter<?>>();
+ final Map<Class<?>, TypeAdapter<?>> subtypeToDelegate
+ = new LinkedHashMap<Class<?>, TypeAdapter<?>>();
+ for (Map.Entry<String, Class<?>> entry : labelToSubtype.entrySet()) {
+ TypeAdapter<?> delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue()));
+ labelToDelegate.put(entry.getKey(), delegate);
+ subtypeToDelegate.put(entry.getValue(), delegate);
+ }
+
+ return new TypeAdapter<R>() {
+ @Override public R read(JsonReader in) throws IOException {
+ JsonElement jsonElement = Streams.parse(in);
+ JsonElement labelJsonElement = jsonElement.getAsJsonObject().get(typeFieldName);
+ if (labelJsonElement == null) {
+ throw new JsonParseException("cannot deserialize " + baseType
+ + " because it does not define a field named " + typeFieldName);
+ }
+ String label = labelJsonElement.getAsString();
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter<R> delegate = (TypeAdapter<R>) labelToDelegate.get(label);
+ if (delegate == null) {
+ throw new JsonParseException("cannot deserialize " + baseType + " subtype named "
+ + label + "; did you forget to register a subtype?");
+ }
+ return delegate.fromJsonTree(jsonElement);
+ }
+
+ @Override public void write(JsonWriter out, R value) throws IOException {
+ Class<?> srcType = value.getClass();
+ String label = subtypeToLabel.get(srcType);
+ @SuppressWarnings("unchecked") // registration requires that subtype extends T
+ TypeAdapter<R> delegate = (TypeAdapter<R>) subtypeToDelegate.get(srcType);
+ if (delegate == null) {
+ throw new JsonParseException("cannot serialize " + srcType.getName()
+ + "; did you forget to register a subtype?");
+ }
+ JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject();
+ if (!jsonObject.has(typeFieldName)) {
+ JsonObject clone = new JsonObject();
+ clone.add(typeFieldName, new JsonPrimitive(label));
+ for (Map.Entry<String, JsonElement> e : jsonObject.entrySet()) {
+ clone.add(e.getKey(), e.getValue());
+ }
+ jsonObject = clone;
+ }
+ Streams.write(jsonObject, out);
+ }
+ };
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/SecurityTest.java b/gson/src/test/java/com/google/gson/functional/SecurityTest.java
new file mode 100644
index 00000000..aa1c2d45
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/SecurityTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for security-related aspects of Gson
+ *
+ * @author Inderjeet Singh
+ */
+public class SecurityTest extends TestCase {
+ /**
+ * Keep this in sync with Gson.JSON_NON_EXECUTABLE_PREFIX
+ */
+ private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n";
+
+ private GsonBuilder gsonBuilder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gsonBuilder = new GsonBuilder();
+ }
+
+ public void testNonExecutableJsonSerialization() {
+ Gson gson = gsonBuilder.generateNonExecutableJson().create();
+ String json = gson.toJson(new BagOfPrimitives());
+ assertTrue(json.startsWith(JSON_NON_EXECUTABLE_PREFIX));
+ }
+
+ public void testNonExecutableJsonDeserialization() {
+ String json = JSON_NON_EXECUTABLE_PREFIX + "{longValue:1}";
+ Gson gson = gsonBuilder.create();
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(1, target.longValue);
+ }
+
+ public void testJsonWithNonExectuableTokenSerialization() {
+ Gson gson = gsonBuilder.generateNonExecutableJson().create();
+ String json = gson.toJson(JSON_NON_EXECUTABLE_PREFIX);
+ assertTrue(json.contains(")]}'\n"));
+ }
+
+ /**
+ * Gson should be able to deserialize a stream with non-exectuable token even if it is created
+ * without {@link GsonBuilder#generateNonExecutableJson()}.
+ */
+ public void testJsonWithNonExectuableTokenWithRegularGsonDeserialization() {
+ Gson gson = gsonBuilder.create();
+ String json = JSON_NON_EXECUTABLE_PREFIX + "{stringValue:')]}\\u0027\\n'}";
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(")]}'\n", target.stringValue);
+ }
+
+ /**
+ * Gson should be able to deserialize a stream with non-exectuable token if it is created
+ * with {@link GsonBuilder#generateNonExecutableJson()}.
+ */
+ public void testJsonWithNonExectuableTokenWithConfiguredGsonDeserialization() {
+ // Gson should be able to deserialize a stream with non-exectuable token even if it is created
+ Gson gson = gsonBuilder.generateNonExecutableJson().create();
+ String json = JSON_NON_EXECUTABLE_PREFIX + "{intValue:2,stringValue:')]}\\u0027\\n'}";
+ BagOfPrimitives target = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(")]}'\n", target.stringValue);
+ assertEquals(2, target.intValue);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/SerializedNameTest.java b/gson/src/test/java/com/google/gson/functional/SerializedNameTest.java
new file mode 100644
index 00000000..38ad8242
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/SerializedNameTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+
+import junit.framework.TestCase;
+
+public final class SerializedNameTest extends TestCase {
+ private final Gson gson = new Gson();
+
+ public void testFirstNameIsChosenForSerialization() {
+ MyClass target = new MyClass("v1", "v2");
+ // Ensure name1 occurs exactly once, and name2 and name3 dont appear
+ assertEquals("{\"name\":\"v1\",\"name1\":\"v2\"}", gson.toJson(target));
+ }
+
+ public void testMultipleNamesDeserializedCorrectly() {
+ assertEquals("v1", gson.fromJson("{'name':'v1'}", MyClass.class).a);
+
+ // Both name1 and name2 gets deserialized to b
+ assertEquals("v11", gson.fromJson("{'name1':'v11'}", MyClass.class).b);
+ assertEquals("v2", gson.fromJson("{'name2':'v2'}", MyClass.class).b);
+ assertEquals("v3", gson.fromJson("{'name3':'v3'}", MyClass.class).b);
+ }
+
+ public void testMultipleNamesInTheSameString() {
+ // The last value takes precedence
+ assertEquals("v3", gson.fromJson("{'name1':'v1','name2':'v2','name3':'v3'}", MyClass.class).b);
+ }
+
+ private static final class MyClass {
+ @SerializedName("name") String a;
+ @SerializedName(value="name1", alternate={"name2", "name3"}) String b;
+ MyClass(String a, String b) {
+ this.a = a;
+ this.b = b;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/StreamingTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/StreamingTypeAdaptersTest.java
new file mode 100644
index 00000000..551ceffc
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/StreamingTypeAdaptersTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import junit.framework.TestCase;
+
+public final class StreamingTypeAdaptersTest extends TestCase {
+ private Gson miniGson = new GsonBuilder().create();
+ private TypeAdapter<Truck> truckAdapter = miniGson.getAdapter(Truck.class);
+ private TypeAdapter<Map<String, Double>> mapAdapter
+ = miniGson.getAdapter(new TypeToken<Map<String, Double>>() {});
+
+ public void testSerialize() throws IOException {
+ Truck truck = new Truck();
+ truck.passengers = Arrays.asList(new Person("Jesse", 29), new Person("Jodie", 29));
+ truck.horsePower = 300;
+
+ assertEquals("{'horsePower':300.0,"
+ + "'passengers':[{'age':29,'name':'Jesse'},{'age':29,'name':'Jodie'}]}",
+ toJson(truckAdapter, truck).replace('\"', '\''));
+ }
+
+ public void testDeserialize() throws IOException {
+ String json = "{'horsePower':300.0,"
+ + "'passengers':[{'age':29,'name':'Jesse'},{'age':29,'name':'Jodie'}]}";
+ Truck truck = fromJson(truckAdapter, json);
+ assertEquals(300.0, truck.horsePower);
+ assertEquals(Arrays.asList(new Person("Jesse", 29), new Person("Jodie", 29)), truck.passengers);
+ }
+
+ public void testSerializeNullField() throws IOException {
+ Truck truck = new Truck();
+ truck.passengers = null;
+ assertEquals("{'horsePower':0.0,'passengers':null}",
+ toJson(truckAdapter, truck).replace('\"', '\''));
+ }
+
+ public void testDeserializeNullField() throws IOException {
+ Truck truck = fromJson(truckAdapter, "{'horsePower':0.0,'passengers':null}");
+ assertNull(truck.passengers);
+ }
+
+ public void testSerializeNullObject() throws IOException {
+ Truck truck = new Truck();
+ truck.passengers = Arrays.asList((Person) null);
+ assertEquals("{'horsePower':0.0,'passengers':[null]}",
+ toJson(truckAdapter, truck).replace('\"', '\''));
+ }
+
+ public void testDeserializeNullObject() throws IOException {
+ Truck truck = fromJson(truckAdapter, "{'horsePower':0.0,'passengers':[null]}");
+ assertEquals(Arrays.asList((Person) null), truck.passengers);
+ }
+
+ public void testSerializeWithCustomTypeAdapter() throws IOException {
+ usePersonNameAdapter();
+ Truck truck = new Truck();
+ truck.passengers = Arrays.asList(new Person("Jesse", 29), new Person("Jodie", 29));
+ assertEquals("{'horsePower':0.0,'passengers':['Jesse','Jodie']}",
+ toJson(truckAdapter, truck).replace('\"', '\''));
+ }
+
+ public void testDeserializeWithCustomTypeAdapter() throws IOException {
+ usePersonNameAdapter();
+ Truck truck = fromJson(truckAdapter, "{'horsePower':0.0,'passengers':['Jesse','Jodie']}");
+ assertEquals(Arrays.asList(new Person("Jesse", -1), new Person("Jodie", -1)), truck.passengers);
+ }
+
+ private void usePersonNameAdapter() {
+ TypeAdapter<Person> personNameAdapter = new TypeAdapter<Person>() {
+ @Override public Person read(JsonReader in) throws IOException {
+ String name = in.nextString();
+ return new Person(name, -1);
+ }
+ @Override public void write(JsonWriter out, Person value) throws IOException {
+ out.value(value.name);
+ }
+ };
+ miniGson = new GsonBuilder().registerTypeAdapter(Person.class, personNameAdapter).create();
+ truckAdapter = miniGson.getAdapter(Truck.class);
+ }
+
+ public void testSerializeMap() throws IOException {
+ Map<String, Double> map = new LinkedHashMap<String, Double>();
+ map.put("a", 5.0);
+ map.put("b", 10.0);
+ assertEquals("{'a':5.0,'b':10.0}", toJson(mapAdapter, map).replace('"', '\''));
+ }
+
+ public void testDeserializeMap() throws IOException {
+ Map<String, Double> map = new LinkedHashMap<String, Double>();
+ map.put("a", 5.0);
+ map.put("b", 10.0);
+ assertEquals(map, fromJson(mapAdapter, "{'a':5.0,'b':10.0}"));
+ }
+
+ public void testSerialize1dArray() throws IOException {
+ TypeAdapter<double[]> arrayAdapter = miniGson.getAdapter(new TypeToken<double[]>() {});
+ assertEquals("[1.0,2.0,3.0]", toJson(arrayAdapter, new double[]{1.0, 2.0, 3.0}));
+ }
+
+ public void testDeserialize1dArray() throws IOException {
+ TypeAdapter<double[]> arrayAdapter = miniGson.getAdapter(new TypeToken<double[]>() {});
+ double[] array = fromJson(arrayAdapter, "[1.0,2.0,3.0]");
+ assertTrue(Arrays.toString(array), Arrays.equals(new double[]{1.0, 2.0, 3.0}, array));
+ }
+
+ public void testSerialize2dArray() throws IOException {
+ TypeAdapter<double[][]> arrayAdapter = miniGson.getAdapter(new TypeToken<double[][]>() {});
+ double[][] array = { {1.0, 2.0 }, { 3.0 } };
+ assertEquals("[[1.0,2.0],[3.0]]", toJson(arrayAdapter, array));
+ }
+
+ public void testDeserialize2dArray() throws IOException {
+ TypeAdapter<double[][]> arrayAdapter = miniGson.getAdapter(new TypeToken<double[][]>() {});
+ double[][] array = fromJson(arrayAdapter, "[[1.0,2.0],[3.0]]");
+ double[][] expected = { {1.0, 2.0 }, { 3.0 } };
+ assertTrue(Arrays.toString(array), Arrays.deepEquals(expected, array));
+ }
+
+ public void testNullSafe() {
+ TypeAdapter<Person> typeAdapter = new TypeAdapter<Person>() {
+ @Override public Person read(JsonReader in) throws IOException {
+ String[] values = in.nextString().split(",");
+ return new Person(values[0], Integer.parseInt(values[1]));
+ }
+ public void write(JsonWriter out, Person person) throws IOException {
+ out.value(person.name + "," + person.age);
+ }
+ };
+ Gson gson = new GsonBuilder().registerTypeAdapter(
+ Person.class, typeAdapter).create();
+ Truck truck = new Truck();
+ truck.horsePower = 1.0D;
+ truck.passengers = new ArrayList<Person>();
+ truck.passengers.add(null);
+ truck.passengers.add(new Person("jesse", 30));
+ try {
+ gson.toJson(truck, Truck.class);
+ fail();
+ } catch (NullPointerException expected) {}
+ String json = "{horsePower:1.0,passengers:[null,'jesse,30']}";
+ try {
+ gson.fromJson(json, Truck.class);
+ fail();
+ } catch (JsonSyntaxException expected) {}
+ gson = new GsonBuilder().registerTypeAdapter(Person.class, typeAdapter.nullSafe()).create();
+ assertEquals("{\"horsePower\":1.0,\"passengers\":[null,\"jesse,30\"]}",
+ gson.toJson(truck, Truck.class));
+ truck = gson.fromJson(json, Truck.class);
+ assertEquals(1.0D, truck.horsePower);
+ assertNull(truck.passengers.get(0));
+ assertEquals("jesse", truck.passengers.get(1).name);
+ }
+
+ public void testSerializeRecursive() throws IOException {
+ TypeAdapter<Node> nodeAdapter = miniGson.getAdapter(Node.class);
+ Node root = new Node("root");
+ root.left = new Node("left");
+ root.right = new Node("right");
+ assertEquals("{'label':'root',"
+ + "'left':{'label':'left','left':null,'right':null},"
+ + "'right':{'label':'right','left':null,'right':null}}",
+ toJson(nodeAdapter, root).replace('"', '\''));
+ }
+
+ public void testFromJsonTree() {
+ JsonObject truckObject = new JsonObject();
+ truckObject.add("horsePower", new JsonPrimitive(300));
+ JsonArray passengersArray = new JsonArray();
+ JsonObject jesseObject = new JsonObject();
+ jesseObject.add("age", new JsonPrimitive(30));
+ jesseObject.add("name", new JsonPrimitive("Jesse"));
+ passengersArray.add(jesseObject);
+ truckObject.add("passengers", passengersArray);
+
+ Truck truck = truckAdapter.fromJsonTree(truckObject);
+ assertEquals(300.0, truck.horsePower);
+ assertEquals(Arrays.asList(new Person("Jesse", 30)), truck.passengers);
+ }
+
+ static class Truck {
+ double horsePower;
+ List<Person> passengers = Collections.emptyList();
+ }
+
+ static class Person {
+ int age;
+ String name;
+ Person(String name, int age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof Person
+ && ((Person) o).name.equals(name)
+ && ((Person) o).age == age;
+ }
+ @Override public int hashCode() {
+ return name.hashCode() ^ age;
+ }
+ }
+
+ static class Node {
+ String label;
+ Node left;
+ Node right;
+ Node(String label) {
+ this.label = label;
+ }
+ }
+
+ // TODO: remove this when TypeAdapter.toJson() is public
+ private static <T> String toJson(TypeAdapter<T> typeAdapter, T value) throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ typeAdapter.write(writer, value);
+ return stringWriter.toString();
+ }
+
+ // TODO: remove this when TypeAdapter.fromJson() is public
+ private <T> T fromJson(TypeAdapter<T> typeAdapter, String json) throws IOException {
+ JsonReader reader = new JsonReader(new StringReader(json));
+ reader.setLenient(true); // TODO: non-lenient?
+ return typeAdapter.read(reader);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/StringTest.java b/gson/src/test/java/com/google/gson/functional/StringTest.java
new file mode 100644
index 00000000..7dcf6f0f
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/StringTest.java
@@ -0,0 +1,140 @@
+package com.google.gson.functional;
+
+import com.google.gson.Gson;
+
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for Json serialization and deserialization of strings.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class StringTest extends TestCase {
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testStringValueSerialization() throws Exception {
+ String value = "someRandomStringValue";
+ assertEquals('"' + value + '"', gson.toJson(value));
+ }
+
+ public void testStringValueDeserialization() throws Exception {
+ String value = "someRandomStringValue";
+ String actual = gson.fromJson("\"" + value + "\"", String.class);
+ assertEquals(value, actual);
+ }
+
+ public void testSingleQuoteInStringSerialization() throws Exception {
+ String valueWithQuotes = "beforeQuote'afterQuote";
+ String jsonRepresentation = gson.toJson(valueWithQuotes);
+ assertEquals(valueWithQuotes, gson.fromJson(jsonRepresentation, String.class));
+ }
+
+ public void testEscapedCtrlNInStringSerialization() throws Exception {
+ String value = "a\nb";
+ String json = gson.toJson(value);
+ assertEquals("\"a\\nb\"", json);
+ }
+
+ public void testEscapedCtrlNInStringDeserialization() throws Exception {
+ String json = "'a\\nb'";
+ String actual = gson.fromJson(json, String.class);
+ assertEquals("a\nb", actual);
+ }
+
+ public void testEscapedCtrlRInStringSerialization() throws Exception {
+ String value = "a\rb";
+ String json = gson.toJson(value);
+ assertEquals("\"a\\rb\"", json);
+ }
+
+ public void testEscapedCtrlRInStringDeserialization() throws Exception {
+ String json = "'a\\rb'";
+ String actual = gson.fromJson(json, String.class);
+ assertEquals("a\rb", actual);
+ }
+
+ public void testEscapedBackslashInStringSerialization() throws Exception {
+ String value = "a\\b";
+ String json = gson.toJson(value);
+ assertEquals("\"a\\\\b\"", json);
+ }
+
+ public void testEscapedBackslashInStringDeserialization() throws Exception {
+ String actual = gson.fromJson("'a\\\\b'", String.class);
+ assertEquals("a\\b", actual);
+ }
+
+ public void testSingleQuoteInStringDeserialization() throws Exception {
+ String value = "beforeQuote'afterQuote";
+ String actual = gson.fromJson("\"" + value + "\"", String.class);
+ assertEquals(value, actual);
+ }
+
+ public void testEscapingQuotesInStringSerialization() throws Exception {
+ String valueWithQuotes = "beforeQuote\"afterQuote";
+ String jsonRepresentation = gson.toJson(valueWithQuotes);
+ String target = gson.fromJson(jsonRepresentation, String.class);
+ assertEquals(valueWithQuotes, target);
+ }
+
+ public void testEscapingQuotesInStringDeserialization() throws Exception {
+ String value = "beforeQuote\\\"afterQuote";
+ String actual = gson.fromJson("\"" + value + "\"", String.class);
+ String expected = "beforeQuote\"afterQuote";
+ assertEquals(expected, actual);
+ }
+
+ public void testStringValueAsSingleElementArraySerialization() throws Exception {
+ String[] target = {"abc"};
+ assertEquals("[\"abc\"]", gson.toJson(target));
+ assertEquals("[\"abc\"]", gson.toJson(target, String[].class));
+ }
+
+ public void testStringWithEscapedSlashDeserialization() {
+ String value = "/";
+ String json = "'\\/'";
+ String actual = gson.fromJson(json, String.class);
+ assertEquals(value, actual);
+ }
+
+ /**
+ * Created in response to http://groups.google.com/group/google-gson/browse_thread/thread/2431d4a3d0d6cb23
+ */
+ public void testAssignmentCharSerialization() {
+ String value = "abc=";
+ String json = gson.toJson(value);
+ assertEquals("\"abc\\u003d\"", json);
+ }
+
+ /**
+ * Created in response to http://groups.google.com/group/google-gson/browse_thread/thread/2431d4a3d0d6cb23
+ */
+ public void testAssignmentCharDeserialization() {
+ String json = "\"abc=\"";
+ String value = gson.fromJson(json, String.class);
+ assertEquals("abc=", value);
+
+ json = "'abc\u003d'";
+ value = gson.fromJson(json, String.class);
+ assertEquals("abc=", value);
+ }
+
+ public void testJavascriptKeywordsInStringSerialization() {
+ String value = "null true false function";
+ String json = gson.toJson(value);
+ assertEquals("\"" + value + "\"", json);
+ }
+
+ public void testJavascriptKeywordsInStringDeserialization() {
+ String json = "'null true false function'";
+ String value = gson.fromJson(json, String.class);
+ assertEquals(json.substring(1, json.length() - 1), value);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java b/gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java
new file mode 100644
index 00000000..f6ae748a
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/ThrowableFunctionalTest.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2014 Trymph Inc.
+package com.google.gson.functional;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+
+@SuppressWarnings("serial")
+public final class ThrowableFunctionalTest extends TestCase {
+ private final Gson gson = new Gson();
+
+ public void testExceptionWithoutCause() {
+ RuntimeException e = new RuntimeException("hello");
+ String json = gson.toJson(e);
+ assertTrue(json.contains("hello"));
+
+ e = gson.fromJson("{'detailMessage':'hello'}", RuntimeException.class);
+ assertEquals("hello", e.getMessage());
+ }
+
+ public void testExceptionWithCause() {
+ Exception e = new Exception("top level", new IOException("io error"));
+ String json = gson.toJson(e);
+ assertTrue(json.contains("{\"detailMessage\":\"top level\",\"cause\":{\"detailMessage\":\"io error\""));
+
+ e = gson.fromJson("{'detailMessage':'top level','cause':{'detailMessage':'io error'}}", Exception.class);
+ assertEquals("top level", e.getMessage());
+ assertTrue(e.getCause() instanceof Throwable); // cause is not parameterized so type info is lost
+ assertEquals("io error", e.getCause().getMessage());
+ }
+
+ public void testSerializedNameOnExceptionFields() {
+ MyException e = new MyException();
+ String json = gson.toJson(e);
+ assertTrue(json.contains("{\"my_custom_name\":\"myCustomMessageValue\""));
+ }
+
+ public void testErrorWithoutCause() {
+ OutOfMemoryError e = new OutOfMemoryError("hello");
+ String json = gson.toJson(e);
+ assertTrue(json.contains("hello"));
+
+ e = gson.fromJson("{'detailMessage':'hello'}", OutOfMemoryError.class);
+ assertEquals("hello", e.getMessage());
+ }
+
+ public void testErrornWithCause() {
+ Error e = new Error("top level", new IOException("io error"));
+ String json = gson.toJson(e);
+ assertTrue(json.contains("top level"));
+ assertTrue(json.contains("io error"));
+
+ e = gson.fromJson("{'detailMessage':'top level','cause':{'detailMessage':'io error'}}", Error.class);
+ assertEquals("top level", e.getMessage());
+ assertTrue(e.getCause() instanceof Throwable); // cause is not parameterized so type info is lost
+ assertEquals("io error", e.getCause().getMessage());
+ }
+
+ private static final class MyException extends Throwable {
+ @SerializedName("my_custom_name") String myCustomMessage = "myCustomMessageValue";
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java
new file mode 100644
index 00000000..53d1c5cf
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/TreeTypeAdaptersTest.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.functional;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Collection of functional tests for DOM tree based type adapters.
+ */
+public class TreeTypeAdaptersTest extends TestCase {
+ private static final Id<Student> STUDENT1_ID = new Id<Student>("5", Student.class);
+ private static final Id<Student> STUDENT2_ID = new Id<Student>("6", Student.class);
+ private static final Student STUDENT1 = new Student(STUDENT1_ID, "first");
+ private static final Student STUDENT2 = new Student(STUDENT2_ID, "second");
+ private static final Type TYPE_COURSE_HISTORY =
+ new TypeToken<Course<HistoryCourse>>(){}.getType();
+ private static final Id<Course<HistoryCourse>> COURSE_ID =
+ new Id<Course<HistoryCourse>>("10", TYPE_COURSE_HISTORY);
+
+ private Gson gson;
+ private Course<HistoryCourse> course;
+
+ @Override
+ protected void setUp() {
+ gson = new GsonBuilder()
+ .registerTypeAdapter(Id.class, new IdTreeTypeAdapter())
+ .create();
+ course = new Course<HistoryCourse>(COURSE_ID, 4,
+ new Assignment<HistoryCourse>(null, null), createList(STUDENT1, STUDENT2));
+ }
+
+ public void testSerializeId() {
+ String json = gson.toJson(course, TYPE_COURSE_HISTORY);
+ assertTrue(json.contains(String.valueOf(COURSE_ID.getValue())));
+ assertTrue(json.contains(String.valueOf(STUDENT1_ID.getValue())));
+ assertTrue(json.contains(String.valueOf(STUDENT2_ID.getValue())));
+ }
+
+ public void testDeserializeId() {
+ String json = "{courseId:1,students:[{id:1,name:'first'},{id:6,name:'second'}],"
+ + "numAssignments:4,assignment:{}}";
+ Course<HistoryCourse> target = gson.fromJson(json, TYPE_COURSE_HISTORY);
+ assertEquals("1", target.getStudents().get(0).id.getValue());
+ assertEquals("6", target.getStudents().get(1).id.getValue());
+ assertEquals("1", target.getId().getValue());
+ }
+
+ private static final class Id<R> {
+ final String value;
+ @SuppressWarnings("unused")
+ final Type typeOfId;
+
+ private Id(String value, Type typeOfId) {
+ this.value = value;
+ this.typeOfId = typeOfId;
+ }
+ public String getValue() {
+ return value;
+ }
+ }
+
+ private static final class IdTreeTypeAdapter implements JsonSerializer<Id<?>>,
+ JsonDeserializer<Id<?>> {
+
+ @SuppressWarnings("rawtypes")
+ public Id<?> deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!(typeOfT instanceof ParameterizedType)) {
+ throw new JsonParseException("Id of unknown type: " + typeOfT);
+ }
+ ParameterizedType parameterizedType = (ParameterizedType) typeOfT;
+ // Since Id takes only one TypeVariable, the actual type corresponding to the first
+ // TypeVariable is the Type we are looking for
+ Type typeOfId = parameterizedType.getActualTypeArguments()[0];
+ return new Id(json.getAsString(), typeOfId);
+ }
+
+ public JsonElement serialize(Id<?> src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.getValue());
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class Student {
+ Id<Student> id;
+ String name;
+
+ private Student() {
+ this(null, null);
+ }
+ public Student(Id<Student> id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class Course<T> {
+ final List<Student> students;
+ private final Id<Course<T>> courseId;
+ private final int numAssignments;
+ private final Assignment<T> assignment;
+
+ private Course() {
+ this(null, 0, null, new ArrayList<Student>());
+ }
+
+ public Course(Id<Course<T>> courseId, int numAssignments,
+ Assignment<T> assignment, List<Student> players) {
+ this.courseId = courseId;
+ this.numAssignments = numAssignments;
+ this.assignment = assignment;
+ this.students = players;
+ }
+ public Id<Course<T>> getId() {
+ return courseId;
+ }
+ List<Student> getStudents() {
+ return students;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class Assignment<T> {
+ private final Id<Assignment<T>> id;
+ private final T data;
+
+ private Assignment() {
+ this(null, null);
+ }
+ public Assignment(Id<Assignment<T>> id, T data) {
+ this.id = id;
+ this.data = data;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class HistoryCourse {
+ int numClasses;
+ }
+
+ private static <T> List<T> createList(T ...items) {
+ return Arrays.asList(items);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/TypeAdapterPrecedenceTest.java b/gson/src/test/java/com/google/gson/functional/TypeAdapterPrecedenceTest.java
new file mode 100644
index 00000000..2f13f664
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/TypeAdapterPrecedenceTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import junit.framework.TestCase;
+
+public final class TypeAdapterPrecedenceTest extends TestCase {
+ public void testNonstreamingFollowedByNonstreaming() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Foo.class, newSerializer("serializer 1"))
+ .registerTypeAdapter(Foo.class, newSerializer("serializer 2"))
+ .registerTypeAdapter(Foo.class, newDeserializer("deserializer 1"))
+ .registerTypeAdapter(Foo.class, newDeserializer("deserializer 2"))
+ .create();
+ assertEquals("\"foo via serializer 2\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via deserializer 2", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testStreamingFollowedByStreaming() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Foo.class, newTypeAdapter("type adapter 1"))
+ .registerTypeAdapter(Foo.class, newTypeAdapter("type adapter 2"))
+ .create();
+ assertEquals("\"foo via type adapter 2\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via type adapter 2", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testSerializeNonstreamingTypeAdapterFollowedByStreamingTypeAdapter() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Foo.class, newSerializer("serializer"))
+ .registerTypeAdapter(Foo.class, newDeserializer("deserializer"))
+ .registerTypeAdapter(Foo.class, newTypeAdapter("type adapter"))
+ .create();
+ assertEquals("\"foo via type adapter\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via type adapter", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testStreamingFollowedByNonstreaming() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Foo.class, newTypeAdapter("type adapter"))
+ .registerTypeAdapter(Foo.class, newSerializer("serializer"))
+ .registerTypeAdapter(Foo.class, newDeserializer("deserializer"))
+ .create();
+ assertEquals("\"foo via serializer\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via deserializer", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testStreamingHierarchicalFollowedByNonstreaming() {
+ Gson gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Foo.class, newTypeAdapter("type adapter"))
+ .registerTypeAdapter(Foo.class, newSerializer("serializer"))
+ .registerTypeAdapter(Foo.class, newDeserializer("deserializer"))
+ .create();
+ assertEquals("\"foo via serializer\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via deserializer", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testStreamingFollowedByNonstreamingHierarchical() {
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Foo.class, newTypeAdapter("type adapter"))
+ .registerTypeHierarchyAdapter(Foo.class, newSerializer("serializer"))
+ .registerTypeHierarchyAdapter(Foo.class, newDeserializer("deserializer"))
+ .create();
+ assertEquals("\"foo via type adapter\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via type adapter", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testStreamingHierarchicalFollowedByNonstreamingHierarchical() {
+ Gson gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Foo.class, newSerializer("serializer"))
+ .registerTypeHierarchyAdapter(Foo.class, newDeserializer("deserializer"))
+ .registerTypeHierarchyAdapter(Foo.class, newTypeAdapter("type adapter"))
+ .create();
+ assertEquals("\"foo via type adapter\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via type adapter", gson.fromJson("foo", Foo.class).name);
+ }
+
+ public void testNonstreamingHierarchicalFollowedByNonstreaming() {
+ Gson gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Foo.class, newSerializer("hierarchical"))
+ .registerTypeHierarchyAdapter(Foo.class, newDeserializer("hierarchical"))
+ .registerTypeAdapter(Foo.class, newSerializer("non hierarchical"))
+ .registerTypeAdapter(Foo.class, newDeserializer("non hierarchical"))
+ .create();
+ assertEquals("\"foo via non hierarchical\"", gson.toJson(new Foo("foo")));
+ assertEquals("foo via non hierarchical", gson.fromJson("foo", Foo.class).name);
+ }
+
+ private static class Foo {
+ final String name;
+ private Foo(String name) {
+ this.name = name;
+ }
+ }
+
+ private JsonSerializer<Foo> newSerializer(final String name) {
+ return new JsonSerializer<Foo>() {
+ public JsonElement serialize(Foo src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.name + " via " + name);
+ }
+ };
+ }
+
+ private JsonDeserializer<Foo> newDeserializer(final String name) {
+ return new JsonDeserializer<Foo>() {
+ public Foo deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ return new Foo(json.getAsString() + " via " + name);
+ }
+ };
+ }
+
+ private TypeAdapter<Foo> newTypeAdapter(final String name) {
+ return new TypeAdapter<Foo>() {
+ @Override public Foo read(JsonReader in) throws IOException {
+ return new Foo(in.nextString() + " via " + name);
+ }
+ @Override public void write(JsonWriter out, Foo value) throws IOException {
+ out.value(value.name + " via " + name);
+ }
+ };
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java b/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java
new file mode 100644
index 00000000..aa2f8f83
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/TypeHierarchyAdapterTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
+import junit.framework.TestCase;
+
+/**
+ * Test that the hierarchy adapter works when subtypes are used.
+ */
+public final class TypeHierarchyAdapterTest extends TestCase {
+
+ public void testTypeHierarchy() {
+ Manager andy = new Manager();
+ andy.userid = "andy";
+ andy.startDate = 2005;
+ andy.minions = new Employee[] {
+ new Employee("inder", 2007),
+ new Employee("joel", 2006),
+ new Employee("jesse", 2006),
+ };
+
+ CEO eric = new CEO();
+ eric.userid = "eric";
+ eric.startDate = 2001;
+ eric.assistant = new Employee("jerome", 2006);
+
+ eric.minions = new Employee[] {
+ new Employee("larry", 1998),
+ new Employee("sergey", 1998),
+ andy,
+ };
+
+ Gson gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
+ .setPrettyPrinting()
+ .create();
+
+ Company company = new Company();
+ company.ceo = eric;
+
+ String json = gson.toJson(company, Company.class);
+ assertEquals("{\n" +
+ " \"ceo\": {\n" +
+ " \"userid\": \"eric\",\n" +
+ " \"startDate\": 2001,\n" +
+ " \"minions\": [\n" +
+ " {\n" +
+ " \"userid\": \"larry\",\n" +
+ " \"startDate\": 1998\n" +
+ " },\n" +
+ " {\n" +
+ " \"userid\": \"sergey\",\n" +
+ " \"startDate\": 1998\n" +
+ " },\n" +
+ " {\n" +
+ " \"userid\": \"andy\",\n" +
+ " \"startDate\": 2005,\n" +
+ " \"minions\": [\n" +
+ " {\n" +
+ " \"userid\": \"inder\",\n" +
+ " \"startDate\": 2007\n" +
+ " },\n" +
+ " {\n" +
+ " \"userid\": \"joel\",\n" +
+ " \"startDate\": 2006\n" +
+ " },\n" +
+ " {\n" +
+ " \"userid\": \"jesse\",\n" +
+ " \"startDate\": 2006\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ],\n" +
+ " \"assistant\": {\n" +
+ " \"userid\": \"jerome\",\n" +
+ " \"startDate\": 2006\n" +
+ " }\n" +
+ " }\n" +
+ "}", json);
+
+ Company copied = gson.fromJson(json, Company.class);
+ assertEquals(json, gson.toJson(copied, Company.class));
+ assertEquals(copied.ceo.userid, company.ceo.userid);
+ assertEquals(copied.ceo.assistant.userid, company.ceo.assistant.userid);
+ assertEquals(copied.ceo.minions[0].userid, company.ceo.minions[0].userid);
+ assertEquals(copied.ceo.minions[1].userid, company.ceo.minions[1].userid);
+ assertEquals(copied.ceo.minions[2].userid, company.ceo.minions[2].userid);
+ assertEquals(((Manager) copied.ceo.minions[2]).minions[0].userid,
+ ((Manager) company.ceo.minions[2]).minions[0].userid);
+ assertEquals(((Manager) copied.ceo.minions[2]).minions[1].userid,
+ ((Manager) company.ceo.minions[2]).minions[1].userid);
+ }
+
+ public void testRegisterSuperTypeFirst() {
+ Gson gson = new GsonBuilder()
+ .registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
+ .registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
+ .create();
+
+ Manager manager = new Manager();
+ manager.userid = "inder";
+
+ String json = gson.toJson(manager, Manager.class);
+ assertEquals("\"inder\"", json);
+ Manager copied = gson.fromJson(json, Manager.class);
+ assertEquals(manager.userid, copied.userid);
+ }
+
+ /** This behaviour changed in Gson 2.1; it used to throw. */
+ public void testRegisterSubTypeFirstAllowed() {
+ new GsonBuilder()
+ .registerTypeHierarchyAdapter(Manager.class, new ManagerAdapter())
+ .registerTypeHierarchyAdapter(Employee.class, new EmployeeAdapter())
+ .create();
+ }
+
+ static class ManagerAdapter implements JsonSerializer<Manager>, JsonDeserializer<Manager> {
+ public Manager deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ Manager result = new Manager();
+ result.userid = json.getAsString();
+ return result;
+ }
+ public JsonElement serialize(Manager src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(src.userid);
+ }
+ }
+
+ static class EmployeeAdapter implements JsonSerializer<Employee>, JsonDeserializer<Employee> {
+ public JsonElement serialize(Employee employee, Type typeOfSrc,
+ JsonSerializationContext context) {
+ JsonObject result = new JsonObject();
+ result.add("userid", context.serialize(employee.userid, String.class));
+ result.add("startDate", context.serialize(employee.startDate, long.class));
+ if (employee instanceof Manager) {
+ result.add("minions", context.serialize(((Manager) employee).minions, Employee[].class));
+ if (employee instanceof CEO) {
+ result.add("assistant", context.serialize(((CEO) employee).assistant, Employee.class));
+ }
+ }
+ return result;
+ }
+
+ public Employee deserialize(JsonElement json, Type typeOfT,
+ JsonDeserializationContext context) throws JsonParseException {
+ JsonObject object = json.getAsJsonObject();
+ Employee result = null;
+
+ // if the employee has an assistant, she must be the CEO
+ JsonElement assistant = object.get("assistant");
+ if (assistant != null) {
+ result = new CEO();
+ ((CEO) result).assistant = context.deserialize(assistant, Employee.class);
+ }
+
+ // only managers have minions
+ JsonElement minons = object.get("minions");
+ if (minons != null) {
+ if (result == null) {
+ result = new Manager();
+ }
+ ((Manager) result).minions = context.deserialize(minons, Employee[].class);
+ }
+
+ if (result == null) {
+ result = new Employee();
+ }
+ result.userid = context.deserialize(object.get("userid"), String.class);
+ result.startDate = context.<Long>deserialize(object.get("startDate"), long.class);
+ return result;
+ }
+ }
+
+ static class Employee {
+ String userid;
+ long startDate;
+
+ Employee(String userid, long startDate) {
+ this.userid = userid;
+ this.startDate = startDate;
+ }
+
+ Employee() {}
+ }
+
+ static class Manager extends Employee {
+ Employee[] minions;
+ }
+
+ static class CEO extends Manager {
+ Employee assistant;
+ }
+
+ static class Company {
+ CEO ceo;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/TypeVariableTest.java b/gson/src/test/java/com/google/gson/functional/TypeVariableTest.java
new file mode 100644
index 00000000..2d7503eb
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/TypeVariableTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Functional test for Gson serialization and deserialization of
+ * classes with type variables.
+ *
+ * @author Joel Leitch
+ */
+public class TypeVariableTest extends TestCase {
+
+ public void testAdvancedTypeVariables() throws Exception {
+ Gson gson = new Gson();
+ Bar bar1 = new Bar("someString", 1, true);
+ ArrayList<Integer> arrayList = new ArrayList<Integer>();
+ arrayList.add(1);
+ arrayList.add(2);
+ arrayList.add(3);
+ bar1.map.put("key1", arrayList);
+ bar1.map.put("key2", new ArrayList<Integer>());
+ String json = gson.toJson(bar1);
+
+ Bar bar2 = gson.fromJson(json, Bar.class);
+ assertEquals(bar1, bar2);
+ }
+
+ public void testTypeVariablesViaTypeParameter() throws Exception {
+ Gson gson = new Gson();
+ Foo<String, Integer> original = new Foo<String, Integer>("e", 5, false);
+ original.map.put("f", Arrays.asList(6, 7));
+ Type type = new TypeToken<Foo<String, Integer>>() {}.getType();
+ String json = gson.toJson(original, type);
+ assertEquals("{\"someSField\":\"e\",\"someTField\":5,\"map\":{\"f\":[6,7]},\"redField\":false}",
+ json);
+ assertEquals(original, gson.<Foo<String, Integer>>fromJson(json, type));
+ }
+
+ public void testBasicTypeVariables() throws Exception {
+ Gson gson = new Gson();
+ Blue blue1 = new Blue(true);
+ String json = gson.toJson(blue1);
+
+ Blue blue2 = gson.fromJson(json, Blue.class);
+ assertEquals(blue1, blue2);
+ }
+
+ public static class Blue extends Red<Boolean> {
+ public Blue() {
+ super(false);
+ }
+
+ public Blue(boolean value) {
+ super(value);
+ }
+
+ // Technically, we should implement hashcode too
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Blue)) {
+ return false;
+ }
+ Blue blue = (Blue) o;
+ return redField.equals(blue.redField);
+ }
+ }
+
+ public static class Red<S> {
+ protected S redField;
+
+ public Red() {}
+
+ public Red(S redField) {
+ this.redField = redField;
+ }
+ }
+
+ public static class Foo<S, T> extends Red<Boolean> {
+ private S someSField;
+ private T someTField;
+ public final Map<S, List<T>> map = new HashMap<S, List<T>>();
+
+ public Foo() {}
+
+ public Foo(S sValue, T tValue, Boolean redField) {
+ super(redField);
+ this.someSField = sValue;
+ this.someTField = tValue;
+ }
+
+ // Technically, we should implement hashcode too
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (!(o instanceof Foo<?, ?>)) {
+ return false;
+ }
+ Foo<S, T> realFoo = (Foo<S, T>) o;
+ return redField.equals(realFoo.redField)
+ && someTField.equals(realFoo.someTField)
+ && someSField.equals(realFoo.someSField)
+ && map.equals(realFoo.map);
+ }
+ }
+
+ public static class Bar extends Foo<String, Integer> {
+ public Bar() {
+ this("", 0, false);
+ }
+
+ public Bar(String s, Integer i, boolean b) {
+ super(s, i, b);
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/UncategorizedTest.java b/gson/src/test/java/com/google/gson/functional/UncategorizedTest.java
new file mode 100644
index 00000000..62c7fa09
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/UncategorizedTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+import com.google.gson.common.TestTypes.ClassOverridingEquals;
+
+import com.google.gson.reflect.TypeToken;
+import java.util.Arrays;
+import java.util.List;
+import junit.framework.TestCase;
+
+import java.lang.reflect.Type;
+
+/**
+ * Functional tests that do not fall neatly into any of the existing classification.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class UncategorizedTest extends TestCase {
+
+ private Gson gson = null;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testInvalidJsonDeserializationFails() throws Exception {
+ try {
+ gson.fromJson("adfasdf1112,,,\":", BagOfPrimitives.class);
+ fail("Bad JSON should throw a ParseException");
+ } catch (JsonParseException expected) { }
+
+ try {
+ gson.fromJson("{adfasdf1112,,,\":}", BagOfPrimitives.class);
+ fail("Bad JSON should throw a ParseException");
+ } catch (JsonParseException expected) { }
+ }
+
+ public void testObjectEqualButNotSameSerialization() throws Exception {
+ ClassOverridingEquals objA = new ClassOverridingEquals();
+ ClassOverridingEquals objB = new ClassOverridingEquals();
+ objB.ref = objA;
+ String json = gson.toJson(objB);
+ assertEquals(objB.getExpectedJson(), json);
+ }
+
+ public void testStaticFieldsAreNotSerialized() {
+ BagOfPrimitives target = new BagOfPrimitives();
+ assertFalse(gson.toJson(target).contains("DEFAULT_VALUE"));
+ }
+
+ public void testGsonInstanceReusableForSerializationAndDeserialization() {
+ BagOfPrimitives bag = new BagOfPrimitives();
+ String json = gson.toJson(bag);
+ BagOfPrimitives deserialized = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(bag, deserialized);
+ }
+
+ /**
+ * This test ensures that a custom deserializer is able to return a derived class instance for a
+ * base class object. For a motivation for this test, see Issue 37 and
+ * http://groups.google.com/group/google-gson/browse_thread/thread/677d56e9976d7761
+ */
+ public void testReturningDerivedClassesDuringDeserialization() {
+ Gson gson = new GsonBuilder().registerTypeAdapter(Base.class, new BaseTypeAdapter()).create();
+ String json = "{\"opType\":\"OP1\"}";
+ Base base = gson.fromJson(json, Base.class);
+ assertTrue(base instanceof Derived1);
+ assertEquals(OperationType.OP1, base.opType);
+
+ json = "{\"opType\":\"OP2\"}";
+ base = gson.fromJson(json, Base.class);
+ assertTrue(base instanceof Derived2);
+ assertEquals(OperationType.OP2, base.opType);
+ }
+
+ /**
+ * Test that trailing whitespace is ignored.
+ * http://code.google.com/p/google-gson/issues/detail?id=302
+ */
+ public void testTrailingWhitespace() throws Exception {
+ List<Integer> integers = gson.fromJson("[1,2,3] \n\n ",
+ new TypeToken<List<Integer>>() {}.getType());
+ assertEquals(Arrays.asList(1, 2, 3), integers);
+ }
+
+ private enum OperationType { OP1, OP2 }
+ private static class Base {
+ OperationType opType;
+ }
+ private static class Derived1 extends Base {
+ Derived1() { opType = OperationType.OP1; }
+ }
+ private static class Derived2 extends Base {
+ Derived2() { opType = OperationType.OP2; }
+ }
+ private static class BaseTypeAdapter implements JsonDeserializer<Base> {
+ public Base deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ String opTypeStr = json.getAsJsonObject().get("opType").getAsString();
+ OperationType opType = OperationType.valueOf(opTypeStr);
+ switch (opType) {
+ case OP1:
+ return new Derived1();
+ case OP2:
+ return new Derived2();
+ }
+ throw new JsonParseException("unknown type: " + json);
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/functional/VersioningTest.java b/gson/src/test/java/com/google/gson/functional/VersioningTest.java
new file mode 100644
index 00000000..bc526de0
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/functional/VersioningTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.functional;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.Since;
+import com.google.gson.annotations.Until;
+import com.google.gson.common.TestTypes.BagOfPrimitives;
+
+import junit.framework.TestCase;
+
+/**
+ * Functional tests for versioning support in Gson.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class VersioningTest extends TestCase {
+ private static final int A = 0;
+ private static final int B = 1;
+ private static final int C = 2;
+ private static final int D = 3;
+
+ private GsonBuilder builder;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ builder = new GsonBuilder();
+ }
+
+ public void testVersionedUntilSerialization() {
+ Version1 target = new Version1();
+ Gson gson = builder.setVersion(1.29).create();
+ String json = gson.toJson(target);
+ assertTrue(json.contains("\"a\":" + A));
+
+ gson = builder.setVersion(1.3).create();
+ json = gson.toJson(target);
+ assertFalse(json.contains("\"a\":" + A));
+ }
+
+ public void testVersionedUntilDeserialization() {
+ Gson gson = builder.setVersion(1.3).create();
+ String json = "{\"a\":3,\"b\":4,\"c\":5}";
+ Version1 version1 = gson.fromJson(json, Version1.class);
+ assertEquals(A, version1.a);
+ }
+
+ public void testVersionedClassesSerialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ String json1 = gson.toJson(new Version1());
+ String json2 = gson.toJson(new Version1_1());
+ assertEquals(json1, json2);
+ }
+
+ public void testVersionedClassesDeserialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ String json = "{\"a\":3,\"b\":4,\"c\":5}";
+ Version1 version1 = gson.fromJson(json, Version1.class);
+ assertEquals(3, version1.a);
+ assertEquals(4, version1.b);
+ Version1_1 version1_1 = gson.fromJson(json, Version1_1.class);
+ assertEquals(3, version1_1.a);
+ assertEquals(4, version1_1.b);
+ assertEquals(C, version1_1.c);
+ }
+
+ public void testIgnoreLaterVersionClassSerialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ assertEquals("null", gson.toJson(new Version1_2()));
+ }
+
+ public void testIgnoreLaterVersionClassDeserialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ String json = "{\"a\":3,\"b\":4,\"c\":5,\"d\":6}";
+ Version1_2 version1_2 = gson.fromJson(json, Version1_2.class);
+ // Since the class is versioned to be after 1.0, we expect null
+ // This is the new behavior in Gson 2.0
+ assertNull(version1_2);
+ }
+
+ public void testVersionedGsonWithUnversionedClassesSerialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ BagOfPrimitives target = new BagOfPrimitives(10, 20, false, "stringValue");
+ assertEquals(target.getExpectedJson(), gson.toJson(target));
+ }
+
+ public void testVersionedGsonWithUnversionedClassesDeserialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ String json = "{\"longValue\":10,\"intValue\":20,\"booleanValue\":false}";
+
+ BagOfPrimitives expected = new BagOfPrimitives();
+ expected.longValue = 10;
+ expected.intValue = 20;
+ expected.booleanValue = false;
+ BagOfPrimitives actual = gson.fromJson(json, BagOfPrimitives.class);
+ assertEquals(expected, actual);
+ }
+
+ public void testVersionedGsonMixingSinceAndUntilSerialization() {
+ Gson gson = builder.setVersion(1.0).create();
+ SinceUntilMixing target = new SinceUntilMixing();
+ String json = gson.toJson(target);
+ assertFalse(json.contains("\"b\":" + B));
+
+ gson = builder.setVersion(1.2).create();
+ json = gson.toJson(target);
+ assertTrue(json.contains("\"b\":" + B));
+
+ gson = builder.setVersion(1.3).create();
+ json = gson.toJson(target);
+ assertFalse(json.contains("\"b\":" + B));
+ }
+
+ public void testVersionedGsonMixingSinceAndUntilDeserialization() {
+ String json = "{\"a\":5,\"b\":6}";
+ Gson gson = builder.setVersion(1.0).create();
+ SinceUntilMixing result = gson.fromJson(json, SinceUntilMixing.class);
+ assertEquals(5, result.a);
+ assertEquals(B, result.b);
+
+ gson = builder.setVersion(1.2).create();
+ result = gson.fromJson(json, SinceUntilMixing.class);
+ assertEquals(5, result.a);
+ assertEquals(6, result.b);
+
+ gson = builder.setVersion(1.3).create();
+ result = gson.fromJson(json, SinceUntilMixing.class);
+ assertEquals(5, result.a);
+ assertEquals(B, result.b);
+ }
+
+ private static class Version1 {
+ @Until(1.3) int a = A;
+ @Since(1.0) int b = B;
+ }
+
+ private static class Version1_1 extends Version1 {
+ @Since(1.1) int c = C;
+ }
+
+ @Since(1.2)
+ private static class Version1_2 extends Version1_1 {
+ @SuppressWarnings("unused")
+ int d = D;
+ }
+
+ private static class SinceUntilMixing {
+ int a = A;
+
+ @Since(1.1)
+ @Until(1.3)
+ int b = B;
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/GsonTypesTest.java b/gson/src/test/java/com/google/gson/internal/GsonTypesTest.java
new file mode 100644
index 00000000..c80700bd
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/GsonTypesTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 Google 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 com.google.gson.internal;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+public final class GsonTypesTest extends TestCase {
+
+ public void testNewParameterizedTypeWithoutOwner() throws Exception {
+ // List<A>. List is a top-level class
+ Type type = $Gson$Types.newParameterizedTypeWithOwner(null, List.class, A.class);
+ assertEquals(A.class, getFirstTypeArgument(type));
+
+ // A<B>. A is a static inner class.
+ type = $Gson$Types.newParameterizedTypeWithOwner(null, A.class, B.class);
+ assertEquals(B.class, getFirstTypeArgument(type));
+
+ final class D {
+ }
+ try {
+ // D<A> is not allowed since D is not a static inner class
+ $Gson$Types.newParameterizedTypeWithOwner(null, D.class, A.class);
+ fail();
+ } catch (IllegalArgumentException expected) {}
+
+ // A<D> is allowed.
+ type = $Gson$Types.newParameterizedTypeWithOwner(null, A.class, D.class);
+ assertEquals(D.class, getFirstTypeArgument(type));
+ }
+
+ public void testGetFirstTypeArgument() throws Exception {
+ assertNull(getFirstTypeArgument(A.class));
+
+ Type type = $Gson$Types.newParameterizedTypeWithOwner(null, A.class, B.class, C.class);
+ assertEquals(B.class, getFirstTypeArgument(type));
+ }
+
+ private static final class A {
+ }
+ private static final class B {
+ }
+ private static final class C {
+ }
+
+ /**
+ * Given a parameterized type A&lt;B,C&gt;, returns B. If the specified type is not
+ * a generic type, returns null.
+ */
+ public static Type getFirstTypeArgument(Type type) throws Exception {
+ if (!(type instanceof ParameterizedType)) return null;
+ ParameterizedType ptype = (ParameterizedType) type;
+ Type[] actualTypeArguments = ptype.getActualTypeArguments();
+ if (actualTypeArguments.length == 0) return null;
+ return $Gson$Types.canonicalize(actualTypeArguments[0]);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java b/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java
new file mode 100644
index 00000000..f108fa0d
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/LazilyParsedNumberTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2015 Google 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 com.google.gson.internal;
+
+import junit.framework.TestCase;
+
+public class LazilyParsedNumberTest extends TestCase {
+ public void testHashCode() {
+ LazilyParsedNumber n1 = new LazilyParsedNumber("1");
+ LazilyParsedNumber n1Another = new LazilyParsedNumber("1");
+ assertEquals(n1.hashCode(), n1Another.hashCode());
+ }
+
+ public void testEquals() {
+ LazilyParsedNumber n1 = new LazilyParsedNumber("1");
+ LazilyParsedNumber n1Another = new LazilyParsedNumber("1");
+ assertTrue(n1.equals(n1Another));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java
new file mode 100644
index 00000000..2aeeeb76
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2012 Google 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 com.google.gson.internal;
+
+import com.google.gson.common.MoreAsserts;
+import com.google.gson.internal.LinkedHashTreeMap.AvlBuilder;
+import com.google.gson.internal.LinkedHashTreeMap.AvlIterator;
+import com.google.gson.internal.LinkedHashTreeMap.Node;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+import junit.framework.TestCase;
+
+public final class LinkedHashTreeMapTest extends TestCase {
+ public void testIterationOrder() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ map.put("a", "android");
+ map.put("c", "cola");
+ map.put("b", "bbq");
+ assertIterationOrder(map.keySet(), "a", "c", "b");
+ assertIterationOrder(map.values(), "android", "cola", "bbq");
+ }
+
+ public void testRemoveRootDoesNotDoubleUnlink() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ map.put("a", "android");
+ map.put("c", "cola");
+ map.put("b", "bbq");
+ Iterator<Map.Entry<String,String>> it = map.entrySet().iterator();
+ it.next();
+ it.next();
+ it.next();
+ it.remove();
+ assertIterationOrder(map.keySet(), "a", "c");
+ }
+
+ public void testPutNullKeyFails() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ try {
+ map.put(null, "android");
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testPutNonComparableKeyFails() {
+ LinkedHashTreeMap<Object, String> map = new LinkedHashTreeMap<Object, String>();
+ try {
+ map.put(new Object(), "android");
+ fail();
+ } catch (ClassCastException expected) {}
+ }
+
+ public void testContainsNonComparableKeyReturnsFalse() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ map.put("a", "android");
+ assertFalse(map.containsKey(new Object()));
+ }
+
+ public void testContainsNullKeyIsAlwaysFalse() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ map.put("a", "android");
+ assertFalse(map.containsKey(null));
+ }
+
+ public void testPutOverrides() throws Exception {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ assertNull(map.put("d", "donut"));
+ assertNull(map.put("e", "eclair"));
+ assertNull(map.put("f", "froyo"));
+ assertEquals(3, map.size());
+
+ assertEquals("donut", map.get("d"));
+ assertEquals("donut", map.put("d", "done"));
+ assertEquals(3, map.size());
+ }
+
+ public void testEmptyStringValues() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ map.put("a", "");
+ assertTrue(map.containsKey("a"));
+ assertEquals("", map.get("a"));
+ }
+
+ // NOTE that this does not happen every time, but given the below predictable random,
+ // this test will consistently fail (assuming the initial size is 16 and rehashing
+ // size remains at 3/4)
+ public void testForceDoublingAndRehash() throws Exception {
+ Random random = new Random(1367593214724L);
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ String[] keys = new String[1000];
+ for (int i = 0; i < keys.length; i++) {
+ keys[i] = Integer.toString(Math.abs(random.nextInt()), 36) + "-" + i;
+ map.put(keys[i], "" + i);
+ }
+
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ assertTrue(map.containsKey(key));
+ assertEquals("" + i, map.get(key));
+ }
+ }
+
+ public void testClear() {
+ LinkedHashTreeMap<String, String> map = new LinkedHashTreeMap<String, String>();
+ map.put("a", "android");
+ map.put("c", "cola");
+ map.put("b", "bbq");
+ map.clear();
+ assertIterationOrder(map.keySet());
+ assertEquals(0, map.size());
+ }
+
+ public void testEqualsAndHashCode() throws Exception {
+ LinkedHashTreeMap<String, Integer> map1 = new LinkedHashTreeMap<String, Integer>();
+ map1.put("A", 1);
+ map1.put("B", 2);
+ map1.put("C", 3);
+ map1.put("D", 4);
+
+ LinkedHashTreeMap<String, Integer> map2 = new LinkedHashTreeMap<String, Integer>();
+ map2.put("C", 3);
+ map2.put("B", 2);
+ map2.put("D", 4);
+ map2.put("A", 1);
+
+ MoreAsserts.assertEqualsAndHashCode(map1, map2);
+ }
+
+ public void testAvlWalker() {
+ assertAvlWalker(node(node("a"), "b", node("c")),
+ "a", "b", "c");
+ assertAvlWalker(node(node(node("a"), "b", node("c")), "d", node(node("e"), "f", node("g"))),
+ "a", "b", "c", "d", "e", "f", "g");
+ assertAvlWalker(node(node(null, "a", node("b")), "c", node(node("d"), "e", null)),
+ "a", "b", "c", "d", "e");
+ assertAvlWalker(node(null, "a", node(null, "b", node(null, "c", node("d")))),
+ "a", "b", "c", "d");
+ assertAvlWalker(node(node(node(node("a"), "b", null), "c", null), "d", null),
+ "a", "b", "c", "d");
+ }
+
+ private void assertAvlWalker(Node<String, String> root, String... values) {
+ AvlIterator<String, String> iterator = new AvlIterator<String, String>();
+ iterator.reset(root);
+ for (String value : values) {
+ assertEquals(value, iterator.next().getKey());
+ }
+ assertNull(iterator.next());
+ }
+
+ public void testAvlBuilder() {
+ assertAvlBuilder(1, "a");
+ assertAvlBuilder(2, "(. a b)");
+ assertAvlBuilder(3, "(a b c)");
+ assertAvlBuilder(4, "(a b (. c d))");
+ assertAvlBuilder(5, "(a b (c d e))");
+ assertAvlBuilder(6, "((. a b) c (d e f))");
+ assertAvlBuilder(7, "((a b c) d (e f g))");
+ assertAvlBuilder(8, "((a b c) d (e f (. g h)))");
+ assertAvlBuilder(9, "((a b c) d (e f (g h i)))");
+ assertAvlBuilder(10, "((a b c) d ((. e f) g (h i j)))");
+ assertAvlBuilder(11, "((a b c) d ((e f g) h (i j k)))");
+ assertAvlBuilder(12, "((a b (. c d)) e ((f g h) i (j k l)))");
+ assertAvlBuilder(13, "((a b (c d e)) f ((g h i) j (k l m)))");
+ assertAvlBuilder(14, "(((. a b) c (d e f)) g ((h i j) k (l m n)))");
+ assertAvlBuilder(15, "(((a b c) d (e f g)) h ((i j k) l (m n o)))");
+ assertAvlBuilder(16, "(((a b c) d (e f g)) h ((i j k) l (m n (. o p))))");
+ assertAvlBuilder(30, "((((. a b) c (d e f)) g ((h i j) k (l m n))) o "
+ + "(((p q r) s (t u v)) w ((x y z) A (B C D))))");
+ assertAvlBuilder(31, "((((a b c) d (e f g)) h ((i j k) l (m n o))) p "
+ + "(((q r s) t (u v w)) x ((y z A) B (C D E))))");
+ }
+
+ private void assertAvlBuilder(int size, String expected) {
+ char[] values = "abcdefghijklmnopqrstuvwxyzABCDE".toCharArray();
+ AvlBuilder<String, String> avlBuilder = new AvlBuilder<String, String>();
+ avlBuilder.reset(size);
+ for (int i = 0; i < size; i++) {
+ avlBuilder.add(node(Character.toString(values[i])));
+ }
+ assertTree(expected, avlBuilder.root());
+ }
+
+ public void testDoubleCapacity() {
+ @SuppressWarnings("unchecked") // Arrays and generics don't get along.
+ Node<String, String>[] oldTable = new Node[1];
+ oldTable[0] = node(node(node("a"), "b", node("c")), "d", node(node("e"), "f", node("g")));
+
+ Node<String, String>[] newTable = LinkedHashTreeMap.doubleCapacity(oldTable);
+ assertTree("(b d f)", newTable[0]); // Even hash codes!
+ assertTree("(a c (. e g))", newTable[1]); // Odd hash codes!
+ }
+
+ public void testDoubleCapacityAllNodesOnLeft() {
+ @SuppressWarnings("unchecked") // Arrays and generics don't get along.
+ Node<String, String>[] oldTable = new Node[1];
+ oldTable[0] = node(node("b"), "d", node("f"));
+
+ Node<String, String>[] newTable = LinkedHashTreeMap.doubleCapacity(oldTable);
+ assertTree("(b d f)", newTable[0]); // Even hash codes!
+ assertNull(newTable[1]); // Odd hash codes!
+
+ for (Node<?, ?> node : newTable) {
+ if (node != null) {
+ assertConsistent(node);
+ }
+ }
+ }
+
+ private static final Node<String, String> head = new Node<String, String>();
+
+ private Node<String, String> node(String value) {
+ return new Node<String, String>(null, value, value.hashCode(), head, head);
+ }
+
+ private Node<String, String> node(Node<String, String> left, String value,
+ Node<String, String> right) {
+ Node<String, String> result = node(value);
+ if (left != null) {
+ result.left = left;
+ left.parent = result;
+ }
+ if (right != null) {
+ result.right = right;
+ right.parent = result;
+ }
+ return result;
+ }
+
+ private void assertTree(String expected, Node<?, ?> root) {
+ assertEquals(expected, toString(root));
+ assertConsistent(root);
+ }
+
+ private void assertConsistent(Node<?, ?> node) {
+ int leftHeight = 0;
+ if (node.left != null) {
+ assertConsistent(node.left);
+ assertSame(node, node.left.parent);
+ leftHeight = node.left.height;
+ }
+ int rightHeight = 0;
+ if (node.right != null) {
+ assertConsistent(node.right);
+ assertSame(node, node.right.parent);
+ rightHeight = node.right.height;
+ }
+ if (node.parent != null) {
+ assertTrue(node.parent.left == node || node.parent.right == node);
+ }
+ if (Math.max(leftHeight, rightHeight) + 1 != node.height) {
+ fail();
+ }
+ }
+
+ private String toString(Node<?, ?> root) {
+ if (root == null) {
+ return ".";
+ } else if (root.left == null && root.right == null) {
+ return String.valueOf(root.key);
+ } else {
+ return String.format("(%s %s %s)", toString(root.left), root.key, toString(root.right));
+ }
+ }
+
+ private <T> void assertIterationOrder(Iterable<T> actual, T... expected) {
+ ArrayList<T> actualList = new ArrayList<T>();
+ for (T t : actual) {
+ actualList.add(t);
+ }
+ assertEquals(Arrays.asList(expected), actualList);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java
new file mode 100644
index 00000000..580d25a5
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2012 Google 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 com.google.gson.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+import com.google.gson.common.MoreAsserts;
+
+public final class LinkedTreeMapTest extends TestCase {
+
+ public void testIterationOrder() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ map.put("a", "android");
+ map.put("c", "cola");
+ map.put("b", "bbq");
+ assertIterationOrder(map.keySet(), "a", "c", "b");
+ assertIterationOrder(map.values(), "android", "cola", "bbq");
+ }
+
+ public void testRemoveRootDoesNotDoubleUnlink() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ map.put("a", "android");
+ map.put("c", "cola");
+ map.put("b", "bbq");
+ Iterator<Map.Entry<String,String>> it = map.entrySet().iterator();
+ it.next();
+ it.next();
+ it.next();
+ it.remove();
+ assertIterationOrder(map.keySet(), "a", "c");
+ }
+
+ public void testPutNullKeyFails() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ try {
+ map.put(null, "android");
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testPutNonComparableKeyFails() {
+ LinkedTreeMap<Object, String> map = new LinkedTreeMap<Object, String>();
+ try {
+ map.put(new Object(), "android");
+ fail();
+ } catch (ClassCastException expected) {}
+ }
+
+ public void testContainsNonComparableKeyReturnsFalse() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ map.put("a", "android");
+ assertFalse(map.containsKey(new Object()));
+ }
+
+ public void testContainsNullKeyIsAlwaysFalse() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ map.put("a", "android");
+ assertFalse(map.containsKey(null));
+ }
+
+ public void testPutOverrides() throws Exception {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ assertNull(map.put("d", "donut"));
+ assertNull(map.put("e", "eclair"));
+ assertNull(map.put("f", "froyo"));
+ assertEquals(3, map.size());
+
+ assertEquals("donut", map.get("d"));
+ assertEquals("donut", map.put("d", "done"));
+ assertEquals(3, map.size());
+ }
+
+ public void testEmptyStringValues() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ map.put("a", "");
+ assertTrue(map.containsKey("a"));
+ assertEquals("", map.get("a"));
+ }
+
+ public void testLargeSetOfRandomKeys() throws Exception {
+ Random random = new Random(1367593214724L);
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ String[] keys = new String[1000];
+ for (int i = 0; i < keys.length; i++) {
+ keys[i] = Integer.toString(Math.abs(random.nextInt()), 36) + "-" + i;
+ map.put(keys[i], "" + i);
+ }
+
+ for (int i = 0; i < keys.length; i++) {
+ String key = keys[i];
+ assertTrue(map.containsKey(key));
+ assertEquals("" + i, map.get(key));
+ }
+ }
+
+ public void testClear() {
+ LinkedTreeMap<String, String> map = new LinkedTreeMap<String, String>();
+ map.put("a", "android");
+ map.put("c", "cola");
+ map.put("b", "bbq");
+ map.clear();
+ assertIterationOrder(map.keySet());
+ assertEquals(0, map.size());
+ }
+
+ public void testEqualsAndHashCode() throws Exception {
+ LinkedTreeMap<String, Integer> map1 = new LinkedTreeMap<String, Integer>();
+ map1.put("A", 1);
+ map1.put("B", 2);
+ map1.put("C", 3);
+ map1.put("D", 4);
+
+ LinkedTreeMap<String, Integer> map2 = new LinkedTreeMap<String, Integer>();
+ map2.put("C", 3);
+ map2.put("B", 2);
+ map2.put("D", 4);
+ map2.put("A", 1);
+
+ MoreAsserts.assertEqualsAndHashCode(map1, map2);
+ }
+
+ private <T> void assertIterationOrder(Iterable<T> actual, T... expected) {
+ ArrayList<T> actualList = new ArrayList<T>();
+ for (T t : actual) {
+ actualList.add(t);
+ }
+ assertEquals(Arrays.asList(expected), actualList);
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java
new file mode 100644
index 00000000..10624711
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/bind/JsonElementReaderTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonToken;
+import java.io.IOException;
+import junit.framework.TestCase;
+
+@SuppressWarnings("resource")
+public final class JsonElementReaderTest extends TestCase {
+
+ public void testNumbers() throws IOException {
+ JsonElement element = new JsonParser().parse("[1, 2, 3]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ assertEquals(1, reader.nextInt());
+ assertEquals(2L, reader.nextLong());
+ assertEquals(3.0, reader.nextDouble());
+ reader.endArray();
+ }
+
+ public void testLenientNansAndInfinities() throws IOException {
+ JsonElement element = new JsonParser().parse("[NaN, -Infinity, Infinity]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.setLenient(true);
+ reader.beginArray();
+ assertTrue(Double.isNaN(reader.nextDouble()));
+ assertEquals(Double.NEGATIVE_INFINITY, reader.nextDouble());
+ assertEquals(Double.POSITIVE_INFINITY, reader.nextDouble());
+ reader.endArray();
+ }
+
+ public void testStrictNansAndInfinities() throws IOException {
+ JsonElement element = new JsonParser().parse("[NaN, -Infinity, Infinity]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.setLenient(false);
+ reader.beginArray();
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException e) {
+ }
+ assertEquals("NaN", reader.nextString());
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException e) {
+ }
+ assertEquals("-Infinity", reader.nextString());
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException e) {
+ }
+ assertEquals("Infinity", reader.nextString());
+ reader.endArray();
+ }
+
+ public void testNumbersFromStrings() throws IOException {
+ JsonElement element = new JsonParser().parse("[\"1\", \"2\", \"3\"]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ assertEquals(1, reader.nextInt());
+ assertEquals(2L, reader.nextLong());
+ assertEquals(3.0, reader.nextDouble());
+ reader.endArray();
+ }
+
+ public void testStringsFromNumbers() throws IOException {
+ JsonElement element = new JsonParser().parse("[1]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ assertEquals("1", reader.nextString());
+ reader.endArray();
+ }
+
+ public void testBooleans() throws IOException {
+ JsonElement element = new JsonParser().parse("[true, false]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.endArray();
+ }
+
+ public void testNulls() throws IOException {
+ JsonElement element = new JsonParser().parse("[null,null]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ reader.nextNull();
+ reader.nextNull();
+ reader.endArray();
+ }
+
+ public void testStrings() throws IOException {
+ JsonElement element = new JsonParser().parse("[\"A\",\"B\"]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ assertEquals("A", reader.nextString());
+ assertEquals("B", reader.nextString());
+ reader.endArray();
+ }
+
+ public void testArray() throws IOException {
+ JsonElement element = new JsonParser().parse("[1, 2, 3]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ assertEquals(JsonToken.BEGIN_ARRAY, reader.peek());
+ reader.beginArray();
+ assertEquals(JsonToken.NUMBER, reader.peek());
+ assertEquals(1, reader.nextInt());
+ assertEquals(JsonToken.NUMBER, reader.peek());
+ assertEquals(2, reader.nextInt());
+ assertEquals(JsonToken.NUMBER, reader.peek());
+ assertEquals(3, reader.nextInt());
+ assertEquals(JsonToken.END_ARRAY, reader.peek());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testObject() throws IOException {
+ JsonElement element = new JsonParser().parse("{\"A\": 1, \"B\": 2}");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ assertEquals(JsonToken.BEGIN_OBJECT, reader.peek());
+ reader.beginObject();
+ assertEquals(JsonToken.NAME, reader.peek());
+ assertEquals("A", reader.nextName());
+ assertEquals(JsonToken.NUMBER, reader.peek());
+ assertEquals(1, reader.nextInt());
+ assertEquals(JsonToken.NAME, reader.peek());
+ assertEquals("B", reader.nextName());
+ assertEquals(JsonToken.NUMBER, reader.peek());
+ assertEquals(2, reader.nextInt());
+ assertEquals(JsonToken.END_OBJECT, reader.peek());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testEmptyArray() throws IOException {
+ JsonElement element = new JsonParser().parse("[]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ reader.endArray();
+ }
+
+ public void testNestedArrays() throws IOException {
+ JsonElement element = new JsonParser().parse("[[],[[]]]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ reader.beginArray();
+ reader.endArray();
+ reader.beginArray();
+ reader.beginArray();
+ reader.endArray();
+ reader.endArray();
+ reader.endArray();
+ }
+
+ public void testNestedObjects() throws IOException {
+ JsonElement element = new JsonParser().parse("{\"A\":{},\"B\":{\"C\":{}}}");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginObject();
+ assertEquals("A", reader.nextName());
+ reader.beginObject();
+ reader.endObject();
+ assertEquals("B", reader.nextName());
+ reader.beginObject();
+ assertEquals("C", reader.nextName());
+ reader.beginObject();
+ reader.endObject();
+ reader.endObject();
+ reader.endObject();
+ }
+
+ public void testEmptyObject() throws IOException {
+ JsonElement element = new JsonParser().parse("{}");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginObject();
+ reader.endObject();
+ }
+
+ public void testSkipValue() throws IOException {
+ JsonElement element = new JsonParser().parse("[\"A\",{\"B\":[[]]},\"C\",[[]],\"D\",null]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ assertEquals("A", reader.nextString());
+ reader.skipValue();
+ assertEquals("C", reader.nextString());
+ reader.skipValue();
+ assertEquals("D", reader.nextString());
+ reader.skipValue();
+ reader.endArray();
+ }
+
+ public void testWrongType() throws IOException {
+ JsonElement element = new JsonParser().parse("[[],\"A\"]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextInt();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextLong();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ reader.beginArray();
+ reader.endArray();
+
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals("A", reader.nextString());
+ reader.endArray();
+ }
+
+ public void testEarlyClose() throws IOException {
+ JsonElement element = new JsonParser().parse("[1, 2, 3]");
+ JsonTreeReader reader = new JsonTreeReader(element);
+ reader.beginArray();
+ reader.close();
+ try {
+ reader.peek();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
new file mode 100644
index 00000000..e07014d3
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/internal/bind/JsonTreeWriterTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2011 Google 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 com.google.gson.internal.bind;
+
+import com.google.gson.JsonNull;
+import java.io.IOException;
+import junit.framework.TestCase;
+
+@SuppressWarnings("resource")
+public final class JsonTreeWriterTest extends TestCase {
+ public void testArray() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.beginArray();
+ writer.value(1);
+ writer.value(2);
+ writer.value(3);
+ writer.endArray();
+ assertEquals("[1,2,3]", writer.get().toString());
+ }
+
+ public void testNestedArray() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.beginArray();
+ writer.beginArray();
+ writer.endArray();
+ writer.beginArray();
+ writer.beginArray();
+ writer.endArray();
+ writer.endArray();
+ writer.endArray();
+ assertEquals("[[],[[]]]", writer.get().toString());
+ }
+
+ public void testObject() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.beginObject();
+ writer.name("A").value(1);
+ writer.name("B").value(2);
+ writer.endObject();
+ assertEquals("{\"A\":1,\"B\":2}", writer.get().toString());
+ }
+
+ public void testNestedObject() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.beginObject();
+ writer.name("A");
+ writer.beginObject();
+ writer.name("B");
+ writer.beginObject();
+ writer.endObject();
+ writer.endObject();
+ writer.name("C");
+ writer.beginObject();
+ writer.endObject();
+ writer.endObject();
+ assertEquals("{\"A\":{\"B\":{}},\"C\":{}}", writer.get().toString());
+ }
+
+ public void testWriteAfterClose() throws Exception {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setLenient(true);
+ writer.beginArray();
+ writer.value("A");
+ writer.endArray();
+ writer.close();
+ try {
+ writer.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testPrematureClose() throws Exception {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setLenient(true);
+ writer.beginArray();
+ try {
+ writer.close();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testSerializeNullsFalse() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setSerializeNulls(false);
+ writer.beginObject();
+ writer.name("A");
+ writer.nullValue();
+ writer.endObject();
+ assertEquals("{}", writer.get().toString());
+ }
+
+ public void testSerializeNullsTrue() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setSerializeNulls(true);
+ writer.beginObject();
+ writer.name("A");
+ writer.nullValue();
+ writer.endObject();
+ assertEquals("{\"A\":null}", writer.get().toString());
+ }
+
+ public void testEmptyWriter() {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ assertEquals(JsonNull.INSTANCE, writer.get());
+ }
+
+ public void testLenientNansAndInfinities() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setLenient(true);
+ writer.beginArray();
+ writer.value(Double.NaN);
+ writer.value(Double.NEGATIVE_INFINITY);
+ writer.value(Double.POSITIVE_INFINITY);
+ writer.endArray();
+ assertEquals("[NaN,-Infinity,Infinity]", writer.get().toString());
+ }
+
+ public void testStrictNansAndInfinities() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setLenient(false);
+ writer.beginArray();
+ try {
+ writer.value(Double.NaN);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ writer.value(Double.NEGATIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ writer.value(Double.POSITIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testStrictBoxedNansAndInfinities() throws IOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ writer.setLenient(false);
+ writer.beginArray();
+ try {
+ writer.value(new Double(Double.NaN));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ writer.value(new Double(Double.NEGATIVE_INFINITY));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ writer.value(new Double(Double.POSITIVE_INFINITY));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/metrics/PerformanceTest.java b/gson/src/test/java/com/google/gson/metrics/PerformanceTest.java
new file mode 100644
index 00000000..cf444eee
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/metrics/PerformanceTest.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2008 Google 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 com.google.gson.metrics;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonParseException;
+import com.google.gson.annotations.Expose;
+import com.google.gson.reflect.TypeToken;
+
+import junit.framework.TestCase;
+
+import java.io.StringWriter;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests to measure performance for Gson. All tests in this file will be disabled in code. To run
+ * them remove disabled_ prefix from the tests and run them.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public class PerformanceTest extends TestCase {
+ private static final int COLLECTION_SIZE = 5000;
+
+ private static final int NUM_ITERATIONS = 100;
+
+ private Gson gson;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ gson = new Gson();
+ }
+
+ public void testDummy() {
+ // This is here to prevent Junit for complaining when we disable all tests.
+ }
+
+ public void disabled_testStringDeserialization() {
+ StringBuilder sb = new StringBuilder(8096);
+ sb.append("Error Yippie");
+
+ while (true) {
+ try {
+ String stackTrace = sb.toString();
+ sb.append(stackTrace);
+ String json = "{\"message\":\"Error message.\"," + "\"stackTrace\":\"" + stackTrace + "\"}";
+ parseLongJson(json);
+ System.out.println("Gson could handle a string of size: " + stackTrace.length());
+ } catch (JsonParseException expected) {
+ break;
+ }
+ }
+ }
+
+ private void parseLongJson(String json) throws JsonParseException {
+ ExceptionHolder target = gson.fromJson(json, ExceptionHolder.class);
+ assertTrue(target.message.contains("Error"));
+ assertTrue(target.stackTrace.contains("Yippie"));
+ }
+
+ private static class ExceptionHolder {
+ public final String message;
+ public final String stackTrace;
+
+ // For use by Gson
+ @SuppressWarnings("unused")
+ private ExceptionHolder() {
+ this("", "");
+ }
+ public ExceptionHolder(String message, String stackTrace) {
+ this.message = message;
+ this.stackTrace = stackTrace;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class CollectionEntry {
+ final String name;
+ final String value;
+
+ // For use by Gson
+ private CollectionEntry() {
+ this(null, null);
+ }
+
+ CollectionEntry(String name, String value) {
+ this.name = name;
+ this.value = value;
+ }
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=96
+ */
+ public void disabled_testLargeCollectionSerialization() {
+ int count = 1400000;
+ List<CollectionEntry> list = new ArrayList<CollectionEntry>(count);
+ for (int i = 0; i < count; ++i) {
+ list.add(new CollectionEntry("name"+i,"value"+i));
+ }
+ gson.toJson(list);
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=96
+ */
+ public void disabled_testLargeCollectionDeserialization() {
+ StringBuilder sb = new StringBuilder();
+ int count = 87000;
+ boolean first = true;
+ sb.append('[');
+ for (int i = 0; i < count; ++i) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(',');
+ }
+ sb.append("{name:'name").append(i).append("',value:'value").append(i).append("'}");
+ }
+ sb.append(']');
+ String json = sb.toString();
+ Type collectionType = new TypeToken<ArrayList<CollectionEntry>>(){}.getType();
+ List<CollectionEntry> list = gson.fromJson(json, collectionType);
+ assertEquals(count, list.size());
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=96
+ */
+ // Last I tested, Gson was able to serialize upto 14MB byte array
+ public void disabled_testByteArraySerialization() {
+ for (int size = 4145152; true; size += 1036288) {
+ byte[] ba = new byte[size];
+ for (int i = 0; i < size; ++i) {
+ ba[i] = 0x05;
+ }
+ gson.toJson(ba);
+ System.out.printf("Gson could serialize a byte array of size: %d\n", size);
+ }
+ }
+
+ /**
+ * Created in response to http://code.google.com/p/google-gson/issues/detail?id=96
+ */
+ // Last I tested, Gson was able to deserialize a byte array of 11MB
+ public void disable_testByteArrayDeserialization() {
+ for (int numElements = 10639296; true; numElements += 16384) {
+ StringBuilder sb = new StringBuilder(numElements*2);
+ sb.append("[");
+ boolean first = true;
+ for (int i = 0; i < numElements; ++i) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append("5");
+ }
+ sb.append("]");
+ String json = sb.toString();
+ byte[] ba = gson.fromJson(json, byte[].class);
+ System.out.printf("Gson could deserialize a byte array of size: %d\n", ba.length);
+ }
+ }
+
+// The tests to measure serialization and deserialization performance of Gson
+// Based on the discussion at
+// http://groups.google.com/group/google-gson/browse_thread/thread/7a50b17a390dfaeb
+// Test results: 10/19/2009
+// Serialize classes avg time: 60 ms
+// Deserialized classes avg time: 70 ms
+// Serialize exposed classes avg time: 159 ms
+// Deserialized exposed classes avg time: 173 ms
+
+ public void disabled_testSerializeClasses() {
+ ClassWithList c = new ClassWithList("str");
+ for (int i = 0; i < COLLECTION_SIZE; ++i) {
+ c.list.add(new ClassWithField("element-" + i));
+ }
+ StringWriter w = new StringWriter();
+ long t1 = System.currentTimeMillis();
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ gson.toJson(c, w);
+ }
+ long t2 = System.currentTimeMillis();
+ long avg = (t2 - t1) / NUM_ITERATIONS;
+ System.out.printf("Serialize classes avg time: %d ms\n", avg);
+ }
+
+ public void disabled_testDeserializeClasses() {
+ String json = buildJsonForClassWithList();
+ ClassWithList[] target = new ClassWithList[NUM_ITERATIONS];
+ long t1 = System.currentTimeMillis();
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ target[i] = gson.fromJson(json, ClassWithList.class);
+ }
+ long t2 = System.currentTimeMillis();
+ long avg = (t2 - t1) / NUM_ITERATIONS;
+ System.out.printf("Deserialize classes avg time: %d ms\n", avg);
+ }
+
+ public void disable_testLargeObjectSerializationAndDeserialization() {
+ Map<String, Long> largeObject = new HashMap<String, Long>();
+ for (long l = 0; l < 100000; l++) {
+ largeObject.put("field" + l, l);
+ }
+
+ long t1 = System.currentTimeMillis();
+ String json = gson.toJson(largeObject);
+ long t2 = System.currentTimeMillis();
+ System.out.printf("Large object serialized in: %d ms\n", (t2 - t1));
+
+ t1 = System.currentTimeMillis();
+ gson.fromJson(json, new TypeToken<Map<String, Long>>() {}.getType());
+ t2 = System.currentTimeMillis();
+ System.out.printf("Large object deserialized in: %d ms\n", (t2 - t1));
+
+ }
+
+ public void disabled_testSerializeExposedClasses() {
+ ClassWithListOfObjects c1 = new ClassWithListOfObjects("str");
+ for (int i1 = 0; i1 < COLLECTION_SIZE; ++i1) {
+ c1.list.add(new ClassWithExposedField("element-" + i1));
+ }
+ ClassWithListOfObjects c = c1;
+ StringWriter w = new StringWriter();
+ long t1 = System.currentTimeMillis();
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ gson.toJson(c, w);
+ }
+ long t2 = System.currentTimeMillis();
+ long avg = (t2 - t1) / NUM_ITERATIONS;
+ System.out.printf("Serialize exposed classes avg time: %d ms\n", avg);
+ }
+
+ public void disabled_testDeserializeExposedClasses() {
+ String json = buildJsonForClassWithList();
+ ClassWithListOfObjects[] target = new ClassWithListOfObjects[NUM_ITERATIONS];
+ long t1 = System.currentTimeMillis();
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ target[i] = gson.fromJson(json, ClassWithListOfObjects.class);
+ }
+ long t2 = System.currentTimeMillis();
+ long avg = (t2 - t1) / NUM_ITERATIONS;
+ System.out.printf("Deserialize exposed classes avg time: %d ms\n", avg);
+ }
+
+ public void disabled_testLargeGsonMapRoundTrip() throws Exception {
+ Map<Long, Long> original = new HashMap<Long, Long>();
+ for (long i = 0; i < 1000000; i++) {
+ original.put(i, i + 1);
+ }
+
+ Gson gson = new Gson();
+ String json = gson.toJson(original);
+ Type longToLong = new TypeToken<Map<Long, Long>>(){}.getType();
+ gson.fromJson(json, longToLong);
+ }
+
+ private String buildJsonForClassWithList() {
+ StringBuilder sb = new StringBuilder("{");
+ sb.append("field:").append("'str',");
+ sb.append("list:[");
+ boolean first = true;
+ for (int i = 0; i < COLLECTION_SIZE; ++i) {
+ if (first) {
+ first = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append("{field:'element-" + i + "'}");
+ }
+ sb.append("]");
+ sb.append("}");
+ String json = sb.toString();
+ return json;
+ }
+
+ @SuppressWarnings("unused")
+ private static final class ClassWithList {
+ final String field;
+ final List<ClassWithField> list = new ArrayList<ClassWithField>(COLLECTION_SIZE);
+ ClassWithList() {
+ this(null);
+ }
+ ClassWithList(String field) {
+ this.field = field;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static final class ClassWithField {
+ final String field;
+ ClassWithField() {
+ this("");
+ }
+ public ClassWithField(String field) {
+ this.field = field;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static final class ClassWithListOfObjects {
+ @Expose
+ final String field;
+ @Expose
+ final List<ClassWithExposedField> list = new ArrayList<ClassWithExposedField>(COLLECTION_SIZE);
+ ClassWithListOfObjects() {
+ this(null);
+ }
+ ClassWithListOfObjects(String field) {
+ this.field = field;
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static final class ClassWithExposedField {
+ @Expose
+ final String field;
+ ClassWithExposedField() {
+ this("");
+ }
+ ClassWithExposedField(String field) {
+ this.field = field;
+ }
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java
new file mode 100644
index 00000000..7dda9d47
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/reflect/TypeTokenTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.reflect;
+
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.RandomAccess;
+import java.util.Set;
+import junit.framework.TestCase;
+
+/**
+ * @author Jesse Wilson
+ */
+@SuppressWarnings({"deprecation"})
+public final class TypeTokenTest extends TestCase {
+
+ List<Integer> listOfInteger = null;
+ List<Number> listOfNumber = null;
+ List<String> listOfString = null;
+ List<?> listOfUnknown = null;
+ List<Set<String>> listOfSetOfString = null;
+ List<Set<?>> listOfSetOfUnknown = null;
+
+ public void testIsAssignableFromRawTypes() {
+ assertTrue(TypeToken.get(Object.class).isAssignableFrom(String.class));
+ assertFalse(TypeToken.get(String.class).isAssignableFrom(Object.class));
+ assertTrue(TypeToken.get(RandomAccess.class).isAssignableFrom(ArrayList.class));
+ assertFalse(TypeToken.get(ArrayList.class).isAssignableFrom(RandomAccess.class));
+ }
+
+ public void testIsAssignableFromWithTypeParameters() throws Exception {
+ Type a = getClass().getDeclaredField("listOfInteger").getGenericType();
+ Type b = getClass().getDeclaredField("listOfNumber").getGenericType();
+ assertTrue(TypeToken.get(a).isAssignableFrom(a));
+ assertTrue(TypeToken.get(b).isAssignableFrom(b));
+
+ // listOfInteger = listOfNumber; // doesn't compile; must be false
+ assertFalse(TypeToken.get(a).isAssignableFrom(b));
+ // listOfNumber = listOfInteger; // doesn't compile; must be false
+ assertFalse(TypeToken.get(b).isAssignableFrom(a));
+ }
+
+ public void testIsAssignableFromWithBasicWildcards() throws Exception {
+ Type a = getClass().getDeclaredField("listOfString").getGenericType();
+ Type b = getClass().getDeclaredField("listOfUnknown").getGenericType();
+ assertTrue(TypeToken.get(a).isAssignableFrom(a));
+ assertTrue(TypeToken.get(b).isAssignableFrom(b));
+
+ // listOfString = listOfUnknown // doesn't compile; must be false
+ assertFalse(TypeToken.get(a).isAssignableFrom(b));
+ listOfUnknown = listOfString; // compiles; must be true
+ // The following assertion is too difficult to support reliably, so disabling
+ // assertTrue(TypeToken.get(b).isAssignableFrom(a));
+ }
+
+ public void testIsAssignableFromWithNestedWildcards() throws Exception {
+ Type a = getClass().getDeclaredField("listOfSetOfString").getGenericType();
+ Type b = getClass().getDeclaredField("listOfSetOfUnknown").getGenericType();
+ assertTrue(TypeToken.get(a).isAssignableFrom(a));
+ assertTrue(TypeToken.get(b).isAssignableFrom(b));
+
+ // listOfSetOfString = listOfSetOfUnknown; // doesn't compile; must be false
+ assertFalse(TypeToken.get(a).isAssignableFrom(b));
+ // listOfSetOfUnknown = listOfSetOfString; // doesn't compile; must be false
+ assertFalse(TypeToken.get(b).isAssignableFrom(a));
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java
new file mode 100644
index 00000000..50661664
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/stream/JsonReaderPathTest.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2014 Google 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 com.google.gson.stream;
+
+import java.io.IOException;
+import java.io.StringReader;
+import junit.framework.TestCase;
+
+@SuppressWarnings("resource")
+public class JsonReaderPathTest extends TestCase {
+ public void testPath() throws IOException {
+ JsonReader reader = new JsonReader(
+ new StringReader("{\"a\":[2,true,false,null,\"b\",{\"c\":\"d\"},[3]]}"));
+ assertEquals("$", reader.getPath());
+ reader.beginObject();
+ assertEquals("$.", reader.getPath());
+ reader.nextName();
+ assertEquals("$.a", reader.getPath());
+ reader.beginArray();
+ assertEquals("$.a[0]", reader.getPath());
+ reader.nextInt();
+ assertEquals("$.a[1]", reader.getPath());
+ reader.nextBoolean();
+ assertEquals("$.a[2]", reader.getPath());
+ reader.nextBoolean();
+ assertEquals("$.a[3]", reader.getPath());
+ reader.nextNull();
+ assertEquals("$.a[4]", reader.getPath());
+ reader.nextString();
+ assertEquals("$.a[5]", reader.getPath());
+ reader.beginObject();
+ assertEquals("$.a[5].", reader.getPath());
+ reader.nextName();
+ assertEquals("$.a[5].c", reader.getPath());
+ reader.nextString();
+ assertEquals("$.a[5].c", reader.getPath());
+ reader.endObject();
+ assertEquals("$.a[6]", reader.getPath());
+ reader.beginArray();
+ assertEquals("$.a[6][0]", reader.getPath());
+ reader.nextInt();
+ assertEquals("$.a[6][1]", reader.getPath());
+ reader.endArray();
+ assertEquals("$.a[7]", reader.getPath());
+ reader.endArray();
+ assertEquals("$.a", reader.getPath());
+ reader.endObject();
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testObjectPath() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":1,\"b\":2}"));
+ assertEquals("$", reader.getPath());
+
+ reader.peek();
+ assertEquals("$", reader.getPath());
+ reader.beginObject();
+ assertEquals("$.", reader.getPath());
+
+ reader.peek();
+ assertEquals("$.", reader.getPath());
+ reader.nextName();
+ assertEquals("$.a", reader.getPath());
+
+ reader.peek();
+ assertEquals("$.a", reader.getPath());
+ reader.nextInt();
+ assertEquals("$.a", reader.getPath());
+
+ reader.peek();
+ assertEquals("$.a", reader.getPath());
+ reader.nextName();
+ assertEquals("$.b", reader.getPath());
+
+ reader.peek();
+ assertEquals("$.b", reader.getPath());
+ reader.nextInt();
+ assertEquals("$.b", reader.getPath());
+
+ reader.peek();
+ assertEquals("$.b", reader.getPath());
+ reader.endObject();
+ assertEquals("$", reader.getPath());
+
+ reader.peek();
+ assertEquals("$", reader.getPath());
+ reader.close();
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testArrayPath() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[1,2]"));
+ assertEquals("$", reader.getPath());
+
+ reader.peek();
+ assertEquals("$", reader.getPath());
+ reader.beginArray();
+ assertEquals("$[0]", reader.getPath());
+
+ reader.peek();
+ assertEquals("$[0]", reader.getPath());
+ reader.nextInt();
+ assertEquals("$[1]", reader.getPath());
+
+ reader.peek();
+ assertEquals("$[1]", reader.getPath());
+ reader.nextInt();
+ assertEquals("$[2]", reader.getPath());
+
+ reader.peek();
+ assertEquals("$[2]", reader.getPath());
+ reader.endArray();
+ assertEquals("$", reader.getPath());
+
+ reader.peek();
+ assertEquals("$", reader.getPath());
+ reader.close();
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testMultipleTopLevelValuesInOneDocument() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[][]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.endArray();
+ assertEquals("$", reader.getPath());
+ reader.beginArray();
+ reader.endArray();
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testSkipArrayElements() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[1,2,3]"));
+ reader.beginArray();
+ reader.skipValue();
+ reader.skipValue();
+ assertEquals("$[2]", reader.getPath());
+ }
+
+ public void testSkipObjectNames() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":1}"));
+ reader.beginObject();
+ reader.skipValue();
+ assertEquals("$.null", reader.getPath());
+ }
+
+ public void testSkipObjectValues() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("{\"a\":1,\"b\":2}"));
+ reader.beginObject();
+ reader.nextName();
+ reader.skipValue();
+ assertEquals("$.null", reader.getPath());
+ reader.nextName();
+ assertEquals("$.b", reader.getPath());
+ }
+
+ public void testSkipNestedStructures() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[[1,2,3],4]"));
+ reader.beginArray();
+ reader.skipValue();
+ assertEquals("$[1]", reader.getPath());
+ }
+
+ public void testArrayOfObjects() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[{},{},{}]"));
+ reader.beginArray();
+ assertEquals("$[0]", reader.getPath());
+ reader.beginObject();
+ assertEquals("$[0].", reader.getPath());
+ reader.endObject();
+ assertEquals("$[1]", reader.getPath());
+ reader.beginObject();
+ assertEquals("$[1].", reader.getPath());
+ reader.endObject();
+ assertEquals("$[2]", reader.getPath());
+ reader.beginObject();
+ assertEquals("$[2].", reader.getPath());
+ reader.endObject();
+ assertEquals("$[3]", reader.getPath());
+ reader.endArray();
+ assertEquals("$", reader.getPath());
+ }
+
+ public void testArrayOfArrays() throws IOException {
+ JsonReader reader = new JsonReader(new StringReader("[[],[],[]]"));
+ reader.beginArray();
+ assertEquals("$[0]", reader.getPath());
+ reader.beginArray();
+ assertEquals("$[0][0]", reader.getPath());
+ reader.endArray();
+ assertEquals("$[1]", reader.getPath());
+ reader.beginArray();
+ assertEquals("$[1][0]", reader.getPath());
+ reader.endArray();
+ assertEquals("$[2]", reader.getPath());
+ reader.beginArray();
+ assertEquals("$[2][0]", reader.getPath());
+ reader.endArray();
+ assertEquals("$[3]", reader.getPath());
+ reader.endArray();
+ assertEquals("$", reader.getPath());
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
new file mode 100644
index 00000000..72c9aa4c
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/stream/JsonReaderTest.java
@@ -0,0 +1,1775 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+import static com.google.gson.stream.JsonToken.BEGIN_ARRAY;
+import static com.google.gson.stream.JsonToken.BEGIN_OBJECT;
+import static com.google.gson.stream.JsonToken.BOOLEAN;
+import static com.google.gson.stream.JsonToken.END_ARRAY;
+import static com.google.gson.stream.JsonToken.END_OBJECT;
+import static com.google.gson.stream.JsonToken.NAME;
+import static com.google.gson.stream.JsonToken.NULL;
+import static com.google.gson.stream.JsonToken.NUMBER;
+import static com.google.gson.stream.JsonToken.STRING;
+
+@SuppressWarnings("resource")
+public final class JsonReaderTest extends TestCase {
+ public void testReadArray() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true, true]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadEmptyArray() throws IOException {
+ JsonReader reader = new JsonReader(reader("[]"));
+ reader.beginArray();
+ assertFalse(reader.hasNext());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadObject() throws IOException {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\": \"android\", \"b\": \"banana\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("android", reader.nextString());
+ assertEquals("b", reader.nextName());
+ assertEquals("banana", reader.nextString());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testReadEmptyObject() throws IOException {
+ JsonReader reader = new JsonReader(reader("{}"));
+ reader.beginObject();
+ assertFalse(reader.hasNext());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipArray() throws IOException {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ assertEquals(123, reader.nextInt());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipArrayAfterPeek() throws Exception {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(BEGIN_ARRAY, reader.peek());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ assertEquals(123, reader.nextInt());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipTopLevelObject() throws Exception {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\": [\"one\", \"two\", \"three\"], \"b\": 123}"));
+ reader.skipValue();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipObject() throws IOException {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\": { \"c\": [], \"d\": [true, true, {}] }, \"b\": \"banana\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ reader.skipValue();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipObjectAfterPeek() throws Exception {
+ String json = "{" + " \"one\": { \"num\": 1 }"
+ + ", \"two\": { \"num\": 2 }" + ", \"three\": { \"num\": 3 }" + "}";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginObject();
+ assertEquals("one", reader.nextName());
+ assertEquals(BEGIN_OBJECT, reader.peek());
+ reader.skipValue();
+ assertEquals("two", reader.nextName());
+ assertEquals(BEGIN_OBJECT, reader.peek());
+ reader.skipValue();
+ assertEquals("three", reader.nextName());
+ reader.skipValue();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipInteger() throws IOException {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\":123456789,\"b\":-123456789}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ reader.skipValue();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipDouble() throws IOException {
+ JsonReader reader = new JsonReader(reader(
+ "{\"a\":-123.456e-789,\"b\":123456789.0}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ reader.skipValue();
+ assertEquals("b", reader.nextName());
+ reader.skipValue();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testHelloWorld() throws IOException {
+ String json = "{\n" +
+ " \"hello\": true,\n" +
+ " \"foo\": [\"world\"]\n" +
+ "}";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginObject();
+ assertEquals("hello", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals("foo", reader.nextName());
+ reader.beginArray();
+ assertEquals("world", reader.nextString());
+ reader.endArray();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testNulls() {
+ try {
+ new JsonReader(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testEmptyString() {
+ try {
+ new JsonReader(reader("")).beginArray();
+ fail();
+ } catch (IOException expected) {
+ }
+ try {
+ new JsonReader(reader("")).beginObject();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testNoTopLevelObject() {
+ try {
+ new JsonReader(reader("true")).nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testCharacterUnescaping() throws IOException {
+ String json = "[\"a\","
+ + "\"a\\\"\","
+ + "\"\\\"\","
+ + "\":\","
+ + "\",\","
+ + "\"\\b\","
+ + "\"\\f\","
+ + "\"\\n\","
+ + "\"\\r\","
+ + "\"\\t\","
+ + "\" \","
+ + "\"\\\\\","
+ + "\"{\","
+ + "\"}\","
+ + "\"[\","
+ + "\"]\","
+ + "\"\\u0000\","
+ + "\"\\u0019\","
+ + "\"\\u20AC\""
+ + "]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ assertEquals("a\"", reader.nextString());
+ assertEquals("\"", reader.nextString());
+ assertEquals(":", reader.nextString());
+ assertEquals(",", reader.nextString());
+ assertEquals("\b", reader.nextString());
+ assertEquals("\f", reader.nextString());
+ assertEquals("\n", reader.nextString());
+ assertEquals("\r", reader.nextString());
+ assertEquals("\t", reader.nextString());
+ assertEquals(" ", reader.nextString());
+ assertEquals("\\", reader.nextString());
+ assertEquals("{", reader.nextString());
+ assertEquals("}", reader.nextString());
+ assertEquals("[", reader.nextString());
+ assertEquals("]", reader.nextString());
+ assertEquals("\0", reader.nextString());
+ assertEquals("\u0019", reader.nextString());
+ assertEquals("\u20AC", reader.nextString());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testUnescapingInvalidCharacters() throws IOException {
+ String json = "[\"\\u000g\"]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ }
+
+ public void testUnescapingTruncatedCharacters() throws IOException {
+ String json = "[\"\\u000";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testUnescapingTruncatedSequence() throws IOException {
+ String json = "[\"\\";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testIntegersWithFractionalPartSpecified() throws IOException {
+ JsonReader reader = new JsonReader(reader("[1.0,1.0,1.0]"));
+ reader.beginArray();
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(1, reader.nextInt());
+ assertEquals(1L, reader.nextLong());
+ }
+
+ public void testDoubles() throws IOException {
+ String json = "[-0.0,"
+ + "1.0,"
+ + "1.7976931348623157E308,"
+ + "4.9E-324,"
+ + "0.0,"
+ + "-0.5,"
+ + "2.2250738585072014E-308,"
+ + "3.141592653589793,"
+ + "2.718281828459045]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ assertEquals(-0.0, reader.nextDouble());
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(1.7976931348623157E308, reader.nextDouble());
+ assertEquals(4.9E-324, reader.nextDouble());
+ assertEquals(0.0, reader.nextDouble());
+ assertEquals(-0.5, reader.nextDouble());
+ assertEquals(2.2250738585072014E-308, reader.nextDouble());
+ assertEquals(3.141592653589793, reader.nextDouble());
+ assertEquals(2.718281828459045, reader.nextDouble());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testStrictNonFiniteDoubles() throws IOException {
+ String json = "[NaN]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testStrictQuotedNonFiniteDoubles() throws IOException {
+ String json = "[\"NaN\"]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testLenientNonFiniteDoubles() throws IOException {
+ String json = "[NaN, -Infinity, Infinity]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertTrue(Double.isNaN(reader.nextDouble()));
+ assertEquals(Double.NEGATIVE_INFINITY, reader.nextDouble());
+ assertEquals(Double.POSITIVE_INFINITY, reader.nextDouble());
+ reader.endArray();
+ }
+
+ public void testLenientQuotedNonFiniteDoubles() throws IOException {
+ String json = "[\"NaN\", \"-Infinity\", \"Infinity\"]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertTrue(Double.isNaN(reader.nextDouble()));
+ assertEquals(Double.NEGATIVE_INFINITY, reader.nextDouble());
+ assertEquals(Double.POSITIVE_INFINITY, reader.nextDouble());
+ reader.endArray();
+ }
+
+ public void testStrictNonFiniteDoublesWithSkipValue() throws IOException {
+ String json = "[NaN]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testLongs() throws IOException {
+ String json = "[0,0,0,"
+ + "1,1,1,"
+ + "-1,-1,-1,"
+ + "-9223372036854775808,"
+ + "9223372036854775807]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ assertEquals(0L, reader.nextLong());
+ assertEquals(0, reader.nextInt());
+ assertEquals(0.0, reader.nextDouble());
+ assertEquals(1L, reader.nextLong());
+ assertEquals(1, reader.nextInt());
+ assertEquals(1.0, reader.nextDouble());
+ assertEquals(-1L, reader.nextLong());
+ assertEquals(-1, reader.nextInt());
+ assertEquals(-1.0, reader.nextDouble());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(Long.MIN_VALUE, reader.nextLong());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(Long.MAX_VALUE, reader.nextLong());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void disabled_testNumberWithOctalPrefix() throws IOException {
+ String json = "[01]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ try {
+ reader.peek();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ try {
+ reader.nextInt();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ try {
+ reader.nextLong();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ try {
+ reader.nextDouble();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ assertEquals("01", reader.nextString());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testBooleans() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true,false]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testPeekingUnquotedStringsPrefixedWithBooleans() throws IOException {
+ JsonReader reader = new JsonReader(reader("[truey]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(STRING, reader.peek());
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals("truey", reader.nextString());
+ reader.endArray();
+ }
+
+ public void testMalformedNumbers() throws IOException {
+ assertNotANumber("-");
+ assertNotANumber(".");
+
+ // exponent lacks digit
+ assertNotANumber("e");
+ assertNotANumber("0e");
+ assertNotANumber(".e");
+ assertNotANumber("0.e");
+ assertNotANumber("-.0e");
+
+ // no integer
+ assertNotANumber("e1");
+ assertNotANumber(".e1");
+ assertNotANumber("-e1");
+
+ // trailing characters
+ assertNotANumber("1x");
+ assertNotANumber("1.1x");
+ assertNotANumber("1e1x");
+ assertNotANumber("1ex");
+ assertNotANumber("1.1ex");
+ assertNotANumber("1.1e1x");
+
+ // fraction has no digit
+ assertNotANumber("0.");
+ assertNotANumber("-0.");
+ assertNotANumber("0.e1");
+ assertNotANumber("-0.e1");
+
+ // no leading digit
+ assertNotANumber(".0");
+ assertNotANumber("-.0");
+ assertNotANumber(".0e1");
+ assertNotANumber("-.0e1");
+ }
+
+ private void assertNotANumber(String s) throws IOException {
+ JsonReader reader = new JsonReader(reader("[" + s + "]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(JsonToken.STRING, reader.peek());
+ assertEquals(s, reader.nextString());
+ reader.endArray();
+ }
+
+ public void testPeekingUnquotedStringsPrefixedWithIntegers() throws IOException {
+ JsonReader reader = new JsonReader(reader("[12.34e5x]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(STRING, reader.peek());
+ try {
+ reader.nextInt();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals("12.34e5x", reader.nextString());
+ }
+
+ public void testPeekLongMinValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[-9223372036854775808]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ assertEquals(-9223372036854775808L, reader.nextLong());
+ }
+
+ public void testPeekLongMaxValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[9223372036854775807]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ assertEquals(9223372036854775807L, reader.nextLong());
+ }
+
+ public void testLongLargerThanMaxLongThatWrapsAround() throws IOException {
+ JsonReader reader = new JsonReader(reader("[22233720368547758070]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ }
+
+ public void testLongLargerThanMinLongThatWrapsAround() throws IOException {
+ JsonReader reader = new JsonReader(reader("[-22233720368547758070]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ }
+
+ /**
+ * This test fails because there's no double for 9223372036854775808, and our
+ * long parsing uses Double.parseDouble() for fractional values.
+ */
+ public void disabled_testPeekLargerThanLongMaxValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[9223372036854775808]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException e) {
+ }
+ }
+
+ /**
+ * This test fails because there's no double for -9223372036854775809, and our
+ * long parsing uses Double.parseDouble() for fractional values.
+ */
+ public void disabled_testPeekLargerThanLongMinValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[-9223372036854775809]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(-9223372036854775809d, reader.nextDouble());
+ }
+
+ /**
+ * This test fails because there's no double for 9223372036854775806, and
+ * our long parsing uses Double.parseDouble() for fractional values.
+ */
+ public void disabled_testHighPrecisionLong() throws IOException {
+ String json = "[9223372036854775806.000]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ assertEquals(9223372036854775806L, reader.nextLong());
+ reader.endArray();
+ }
+
+ public void testPeekMuchLargerThanLongMinValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[-92233720368547758080]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(NUMBER, reader.peek());
+ try {
+ reader.nextLong();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(-92233720368547758080d, reader.nextDouble());
+ }
+
+ public void testQuotedNumberWithEscape() throws IOException {
+ JsonReader reader = new JsonReader(reader("[\"12\u00334\"]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(STRING, reader.peek());
+ assertEquals(1234, reader.nextInt());
+ }
+
+ public void testMixedCaseLiterals() throws IOException {
+ JsonReader reader = new JsonReader(reader("[True,TruE,False,FALSE,NULL,nulL]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ assertEquals(false, reader.nextBoolean());
+ reader.nextNull();
+ reader.nextNull();
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testMissingValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testPrematureEndOfInput() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":true,"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testPrematurelyClosed() throws IOException {
+ try {
+ JsonReader reader = new JsonReader(reader("{\"a\":[]}"));
+ reader.beginObject();
+ reader.close();
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ JsonReader reader = new JsonReader(reader("{\"a\":[]}"));
+ reader.close();
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ JsonReader reader = new JsonReader(reader("{\"a\":true}"));
+ reader.beginObject();
+ reader.nextName();
+ reader.peek();
+ reader.close();
+ reader.nextBoolean();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNextFailuresDoNotAdvance() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":true}"));
+ reader.beginObject();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.nextName();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ reader.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ reader.close();
+ }
+
+ public void testIntegerMismatchFailuresDoNotAdvance() throws IOException {
+ JsonReader reader = new JsonReader(reader("[1.5]"));
+ reader.beginArray();
+ try {
+ reader.nextInt();
+ fail();
+ } catch (NumberFormatException expected) {
+ }
+ assertEquals(1.5d, reader.nextDouble());
+ reader.endArray();
+ }
+
+ public void testStringNullIsNotNull() throws IOException {
+ JsonReader reader = new JsonReader(reader("[\"null\"]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNullLiteralIsNotAString() throws IOException {
+ JsonReader reader = new JsonReader(reader("[null]"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testStrictNameValueSeparator() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\"=true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("{\"a\"=>true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientNameValueSeparator() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\"=true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+
+ reader = new JsonReader(reader("{\"a\"=>true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictNameValueSeparatorWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\"=true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("{\"a\"=>true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testCommentsInStringValue() throws Exception {
+ JsonReader reader = new JsonReader(reader("[\"// comment\"]"));
+ reader.beginArray();
+ assertEquals("// comment", reader.nextString());
+ reader.endArray();
+
+ reader = new JsonReader(reader("{\"a\":\"#someComment\"}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("#someComment", reader.nextString());
+ reader.endObject();
+
+ reader = new JsonReader(reader("{\"#//a\":\"#some //Comment\"}"));
+ reader.beginObject();
+ assertEquals("#//a", reader.nextName());
+ assertEquals("#some //Comment", reader.nextString());
+ reader.endObject();
+ }
+
+ public void testStrictComments() throws IOException {
+ JsonReader reader = new JsonReader(reader("[// comment \n true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[# comment \n true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[/* comment */ true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientComments() throws IOException {
+ JsonReader reader = new JsonReader(reader("[// comment \n true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+
+ reader = new JsonReader(reader("[# comment \n true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+
+ reader = new JsonReader(reader("[/* comment */ true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictCommentsWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[// comment \n true]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[# comment \n true]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[/* comment */ true]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictUnquotedNames() throws IOException {
+ JsonReader reader = new JsonReader(reader("{a:true}"));
+ reader.beginObject();
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientUnquotedNames() throws IOException {
+ JsonReader reader = new JsonReader(reader("{a:true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ }
+
+ public void testStrictUnquotedNamesWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("{a:true}"));
+ reader.beginObject();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictSingleQuotedNames() throws IOException {
+ JsonReader reader = new JsonReader(reader("{'a':true}"));
+ reader.beginObject();
+ try {
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSingleQuotedNames() throws IOException {
+ JsonReader reader = new JsonReader(reader("{'a':true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ }
+
+ public void testStrictSingleQuotedNamesWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("{'a':true}"));
+ reader.beginObject();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictUnquotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(reader("[a]"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testStrictUnquotedStringsWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[a]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testLenientUnquotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(reader("[a]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ }
+
+ public void testStrictSingleQuotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(reader("['a']"));
+ reader.beginArray();
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSingleQuotedStrings() throws IOException {
+ JsonReader reader = new JsonReader(reader("['a']"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals("a", reader.nextString());
+ }
+
+ public void testStrictSingleQuotedStringsWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("['a']"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictSemicolonDelimitedArray() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true;true]"));
+ reader.beginArray();
+ try {
+ reader.nextBoolean();
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSemicolonDelimitedArray() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true;true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictSemicolonDelimitedArrayWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true;true]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictSemicolonDelimitedNameValuePair() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":true;\"b\":true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.nextBoolean();
+ reader.nextName();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientSemicolonDelimitedNameValuePair() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":true;\"b\":true}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals("b", reader.nextName());
+ }
+
+ public void testStrictSemicolonDelimitedNameValuePairWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":true;\"b\":true}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ try {
+ reader.skipValue();
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictUnnecessaryArraySeparators() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true,,true]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[,true]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[true,]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[,]"));
+ reader.beginArray();
+ try {
+ reader.nextNull();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientUnnecessaryArraySeparators() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true,,true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ reader.nextNull();
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+
+ reader = new JsonReader(reader("[,true]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.nextNull();
+ assertEquals(true, reader.nextBoolean());
+ reader.endArray();
+
+ reader = new JsonReader(reader("[true,]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ reader.nextNull();
+ reader.endArray();
+
+ reader = new JsonReader(reader("[,]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.nextNull();
+ reader.nextNull();
+ reader.endArray();
+ }
+
+ public void testStrictUnnecessaryArraySeparatorsWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[true,,true]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[,true]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[true,]"));
+ reader.beginArray();
+ assertEquals(true, reader.nextBoolean());
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+
+ reader = new JsonReader(reader("[,]"));
+ reader.beginArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictMultipleTopLevelValues() throws IOException {
+ JsonReader reader = new JsonReader(reader("[] []"));
+ reader.beginArray();
+ reader.endArray();
+ try {
+ reader.peek();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientMultipleTopLevelValues() throws IOException {
+ JsonReader reader = new JsonReader(reader("[] true {}"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.endArray();
+ assertEquals(true, reader.nextBoolean());
+ reader.beginObject();
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testStrictMultipleTopLevelValuesWithSkipValue() throws IOException {
+ JsonReader reader = new JsonReader(reader("[] []"));
+ reader.beginArray();
+ reader.endArray();
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictTopLevelString() {
+ JsonReader reader = new JsonReader(reader("\"a\""));
+ try {
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientTopLevelString() throws IOException {
+ JsonReader reader = new JsonReader(reader("\"a\""));
+ reader.setLenient(true);
+ assertEquals("a", reader.nextString());
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testStrictTopLevelValueType() {
+ JsonReader reader = new JsonReader(reader("true"));
+ try {
+ reader.nextBoolean();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientTopLevelValueType() throws IOException {
+ JsonReader reader = new JsonReader(reader("true"));
+ reader.setLenient(true);
+ assertEquals(true, reader.nextBoolean());
+ }
+
+ public void testStrictTopLevelValueTypeWithSkipValue() {
+ JsonReader reader = new JsonReader(reader("true"));
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictNonExecutePrefix() {
+ JsonReader reader = new JsonReader(reader(")]}'\n []"));
+ try {
+ reader.beginArray();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testStrictNonExecutePrefixWithSkipValue() {
+ JsonReader reader = new JsonReader(reader(")]}'\n []"));
+ try {
+ reader.skipValue();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientNonExecutePrefix() throws IOException {
+ JsonReader reader = new JsonReader(reader(")]}'\n []"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testLenientNonExecutePrefixWithLeadingWhitespace() throws IOException {
+ JsonReader reader = new JsonReader(reader("\r\n \t)]}'\n []"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testLenientPartialNonExecutePrefix() {
+ JsonReader reader = new JsonReader(reader(")]}' []"));
+ reader.setLenient(true);
+ try {
+ assertEquals(")", reader.nextString());
+ reader.nextString();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testBomIgnoredAsFirstCharacterOfDocument() throws IOException {
+ JsonReader reader = new JsonReader(reader("\ufeff[]"));
+ reader.beginArray();
+ reader.endArray();
+ }
+
+ public void testBomForbiddenAsOtherCharacterInDocument() throws IOException {
+ JsonReader reader = new JsonReader(reader("[\ufeff]"));
+ reader.beginArray();
+ try {
+ reader.endArray();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testFailWithPosition() throws IOException {
+ testFailWithPosition("Expected value at line 6 column 5 path $[1]",
+ "[\n\n\n\n\n\"a\",}]");
+ }
+
+ public void testFailWithPositionGreaterThanBufferSize() throws IOException {
+ String spaces = repeat(' ', 8192);
+ testFailWithPosition("Expected value at line 6 column 5 path $[1]",
+ "[\n\n" + spaces + "\n\n\n\"a\",}]");
+ }
+
+ public void testFailWithPositionOverSlashSlashEndOfLineComment() throws IOException {
+ testFailWithPosition("Expected value at line 5 column 6 path $[1]",
+ "\n// foo\n\n//bar\r\n[\"a\",}");
+ }
+
+ public void testFailWithPositionOverHashEndOfLineComment() throws IOException {
+ testFailWithPosition("Expected value at line 5 column 6 path $[1]",
+ "\n# foo\n\n#bar\r\n[\"a\",}");
+ }
+
+ public void testFailWithPositionOverCStyleComment() throws IOException {
+ testFailWithPosition("Expected value at line 6 column 12 path $[1]",
+ "\n\n/* foo\n*\n*\r\nbar */[\"a\",}");
+ }
+
+ public void testFailWithPositionOverQuotedString() throws IOException {
+ testFailWithPosition("Expected value at line 5 column 3 path $[1]",
+ "[\"foo\nbar\r\nbaz\n\",\n }");
+ }
+
+ public void testFailWithPositionOverUnquotedString() throws IOException {
+ testFailWithPosition("Expected value at line 5 column 2 path $[1]", "[\n\nabcd\n\n,}");
+ }
+
+ public void testFailWithEscapedNewlineCharacter() throws IOException {
+ testFailWithPosition("Expected value at line 5 column 3 path $[1]", "[\n\n\"\\\n\n\",}");
+ }
+
+ public void testFailWithPositionIsOffsetByBom() throws IOException {
+ testFailWithPosition("Expected value at line 1 column 6 path $[1]",
+ "\ufeff[\"a\",}]");
+ }
+
+ private void testFailWithPosition(String message, String json) throws IOException {
+ // Validate that it works reading the string normally.
+ JsonReader reader1 = new JsonReader(reader(json));
+ reader1.setLenient(true);
+ reader1.beginArray();
+ reader1.nextString();
+ try {
+ reader1.peek();
+ fail();
+ } catch (IOException expected) {
+ assertEquals(message, expected.getMessage());
+ }
+
+ // Also validate that it works when skipping.
+ JsonReader reader2 = new JsonReader(reader(json));
+ reader2.setLenient(true);
+ reader2.beginArray();
+ reader2.skipValue();
+ try {
+ reader2.peek();
+ fail();
+ } catch (IOException expected) {
+ assertEquals(message, expected.getMessage());
+ }
+ }
+
+ public void testFailWithPositionDeepPath() throws IOException {
+ JsonReader reader = new JsonReader(reader("[1,{\"a\":[2,3,}"));
+ reader.beginArray();
+ reader.nextInt();
+ reader.beginObject();
+ reader.nextName();
+ reader.beginArray();
+ reader.nextInt();
+ reader.nextInt();
+ try {
+ reader.peek();
+ fail();
+ } catch (IOException expected) {
+ assertEquals("Expected value at line 1 column 14 path $[1].a[2]", expected.getMessage());
+ }
+ }
+
+ public void testStrictVeryLongNumber() throws IOException {
+ JsonReader reader = new JsonReader(reader("[0." + repeat('9', 8192) + "]"));
+ reader.beginArray();
+ try {
+ assertEquals(1d, reader.nextDouble());
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testLenientVeryLongNumber() throws IOException {
+ JsonReader reader = new JsonReader(reader("[0." + repeat('9', 8192) + "]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(JsonToken.STRING, reader.peek());
+ assertEquals(1d, reader.nextDouble());
+ reader.endArray();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testVeryLongUnquotedLiteral() throws IOException {
+ String literal = "a" + repeat('b', 8192) + "c";
+ JsonReader reader = new JsonReader(reader("[" + literal + "]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(literal, reader.nextString());
+ reader.endArray();
+ }
+
+ public void testDeeplyNestedArrays() throws IOException {
+ // this is nested 40 levels deep; Gson is tuned for nesting is 30 levels deep or fewer
+ JsonReader reader = new JsonReader(reader(
+ "[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]"));
+ for (int i = 0; i < 40; i++) {
+ reader.beginArray();
+ }
+ assertEquals("$[0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0][0]"
+ + "[0][0][0][0][0][0][0][0][0][0][0][0][0][0]", reader.getPath());
+ for (int i = 0; i < 40; i++) {
+ reader.endArray();
+ }
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testDeeplyNestedObjects() throws IOException {
+ // Build a JSON document structured like {"a":{"a":{"a":{"a":true}}}}, but 40 levels deep
+ String array = "{\"a\":%s}";
+ String json = "true";
+ for (int i = 0; i < 40; i++) {
+ json = String.format(array, json);
+ }
+
+ JsonReader reader = new JsonReader(reader(json));
+ for (int i = 0; i < 40; i++) {
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ }
+ assertEquals("$.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a"
+ + ".a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a", reader.getPath());
+ assertEquals(true, reader.nextBoolean());
+ for (int i = 0; i < 40; i++) {
+ reader.endObject();
+ }
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ // http://code.google.com/p/google-gson/issues/detail?id=409
+ public void testStringEndingInSlash() throws IOException {
+ JsonReader reader = new JsonReader(reader("/"));
+ reader.setLenient(true);
+ try {
+ reader.peek();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testDocumentWithCommentEndingInSlash() throws IOException {
+ JsonReader reader = new JsonReader(reader("/* foo *//"));
+ reader.setLenient(true);
+ try {
+ reader.peek();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testStringWithLeadingSlash() throws IOException {
+ JsonReader reader = new JsonReader(reader("/x"));
+ reader.setLenient(true);
+ try {
+ reader.peek();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testUnterminatedObject() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":\"android\"x"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("android", reader.nextString());
+ try {
+ reader.peek();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ public void testVeryLongQuotedString() throws IOException {
+ char[] stringChars = new char[1024 * 16];
+ Arrays.fill(stringChars, 'x');
+ String string = new String(stringChars);
+ String json = "[\"" + string + "\"]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.beginArray();
+ assertEquals(string, reader.nextString());
+ reader.endArray();
+ }
+
+ public void testVeryLongUnquotedString() throws IOException {
+ char[] stringChars = new char[1024 * 16];
+ Arrays.fill(stringChars, 'x');
+ String string = new String(stringChars);
+ String json = "[" + string + "]";
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(string, reader.nextString());
+ reader.endArray();
+ }
+
+ public void testVeryLongUnterminatedString() throws IOException {
+ char[] stringChars = new char[1024 * 16];
+ Arrays.fill(stringChars, 'x');
+ String string = new String(stringChars);
+ String json = "[" + string;
+ JsonReader reader = new JsonReader(reader(json));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(string, reader.nextString());
+ try {
+ reader.peek();
+ fail();
+ } catch (EOFException expected) {
+ }
+ }
+
+ public void testSkipVeryLongUnquotedString() throws IOException {
+ JsonReader reader = new JsonReader(reader("[" + repeat('x', 8192) + "]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ reader.skipValue();
+ reader.endArray();
+ }
+
+ public void testSkipTopLevelUnquotedString() throws IOException {
+ JsonReader reader = new JsonReader(reader(repeat('x', 8192)));
+ reader.setLenient(true);
+ reader.skipValue();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testSkipVeryLongQuotedString() throws IOException {
+ JsonReader reader = new JsonReader(reader("[\"" + repeat('x', 8192) + "\"]"));
+ reader.beginArray();
+ reader.skipValue();
+ reader.endArray();
+ }
+
+ public void testSkipTopLevelQuotedString() throws IOException {
+ JsonReader reader = new JsonReader(reader("\"" + repeat('x', 8192) + "\""));
+ reader.setLenient(true);
+ reader.skipValue();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testStringAsNumberWithTruncatedExponent() throws IOException {
+ JsonReader reader = new JsonReader(reader("[123e]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(STRING, reader.peek());
+ }
+
+ public void testStringAsNumberWithDigitAndNonDigitExponent() throws IOException {
+ JsonReader reader = new JsonReader(reader("[123e4b]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(STRING, reader.peek());
+ }
+
+ public void testStringAsNumberWithNonDigitExponent() throws IOException {
+ JsonReader reader = new JsonReader(reader("[123eb]"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(STRING, reader.peek());
+ }
+
+ public void testEmptyStringName() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"\":true}"));
+ reader.setLenient(true);
+ assertEquals(BEGIN_OBJECT, reader.peek());
+ reader.beginObject();
+ assertEquals(NAME, reader.peek());
+ assertEquals("", reader.nextName());
+ assertEquals(JsonToken.BOOLEAN, reader.peek());
+ assertEquals(true, reader.nextBoolean());
+ assertEquals(JsonToken.END_OBJECT, reader.peek());
+ reader.endObject();
+ assertEquals(JsonToken.END_DOCUMENT, reader.peek());
+ }
+
+ public void testStrictExtraCommasInMaps() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":\"b\",}"));
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("b", reader.nextString());
+ try {
+ reader.peek();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ public void testLenientExtraCommasInMaps() throws IOException {
+ JsonReader reader = new JsonReader(reader("{\"a\":\"b\",}"));
+ reader.setLenient(true);
+ reader.beginObject();
+ assertEquals("a", reader.nextName());
+ assertEquals("b", reader.nextString());
+ try {
+ reader.peek();
+ fail();
+ } catch (IOException expected) {
+ }
+ }
+
+ private String repeat(char c, int count) {
+ char[] array = new char[count];
+ Arrays.fill(array, c);
+ return new String(array);
+ }
+
+ public void testMalformedDocuments() throws IOException {
+ assertDocument("{]", BEGIN_OBJECT, IOException.class);
+ assertDocument("{,", BEGIN_OBJECT, IOException.class);
+ assertDocument("{{", BEGIN_OBJECT, IOException.class);
+ assertDocument("{[", BEGIN_OBJECT, IOException.class);
+ assertDocument("{:", BEGIN_OBJECT, IOException.class);
+ assertDocument("{\"name\",", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\",", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\":}", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\"::", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\":,", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\"=}", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\"=>}", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\"=>\"string\":", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\"=>\"string\"=", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\"=>\"string\"=>", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\"=>\"string\",", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\"=>\"string\",\"name\"", BEGIN_OBJECT, NAME, STRING, NAME);
+ assertDocument("[}", BEGIN_ARRAY, IOException.class);
+ assertDocument("[,]", BEGIN_ARRAY, NULL, NULL, END_ARRAY);
+ assertDocument("{", BEGIN_OBJECT, IOException.class);
+ assertDocument("{\"name\"", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{\"name\",", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{'name'", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{'name',", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("{name", BEGIN_OBJECT, NAME, IOException.class);
+ assertDocument("[", BEGIN_ARRAY, IOException.class);
+ assertDocument("[string", BEGIN_ARRAY, STRING, IOException.class);
+ assertDocument("[\"string\"", BEGIN_ARRAY, STRING, IOException.class);
+ assertDocument("['string'", BEGIN_ARRAY, STRING, IOException.class);
+ assertDocument("[123", BEGIN_ARRAY, NUMBER, IOException.class);
+ assertDocument("[123,", BEGIN_ARRAY, NUMBER, IOException.class);
+ assertDocument("{\"name\":123", BEGIN_OBJECT, NAME, NUMBER, IOException.class);
+ assertDocument("{\"name\":123,", BEGIN_OBJECT, NAME, NUMBER, IOException.class);
+ assertDocument("{\"name\":\"string\"", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\":\"string\",", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\":'string'", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\":'string',", BEGIN_OBJECT, NAME, STRING, IOException.class);
+ assertDocument("{\"name\":false", BEGIN_OBJECT, NAME, BOOLEAN, IOException.class);
+ assertDocument("{\"name\":false,,", BEGIN_OBJECT, NAME, BOOLEAN, IOException.class);
+ }
+
+ /**
+ * This test behave slightly differently in Gson 2.2 and earlier. It fails
+ * during peek rather than during nextString().
+ */
+ public void testUnterminatedStringFailure() throws IOException {
+ JsonReader reader = new JsonReader(reader("[\"string"));
+ reader.setLenient(true);
+ reader.beginArray();
+ assertEquals(JsonToken.STRING, reader.peek());
+ try {
+ reader.nextString();
+ fail();
+ } catch (MalformedJsonException expected) {
+ }
+ }
+
+ private void assertDocument(String document, Object... expectations) throws IOException {
+ JsonReader reader = new JsonReader(reader(document));
+ reader.setLenient(true);
+ for (Object expectation : expectations) {
+ if (expectation == BEGIN_OBJECT) {
+ reader.beginObject();
+ } else if (expectation == BEGIN_ARRAY) {
+ reader.beginArray();
+ } else if (expectation == END_OBJECT) {
+ reader.endObject();
+ } else if (expectation == END_ARRAY) {
+ reader.endArray();
+ } else if (expectation == NAME) {
+ assertEquals("name", reader.nextName());
+ } else if (expectation == BOOLEAN) {
+ assertEquals(false, reader.nextBoolean());
+ } else if (expectation == STRING) {
+ assertEquals("string", reader.nextString());
+ } else if (expectation == NUMBER) {
+ assertEquals(123, reader.nextInt());
+ } else if (expectation == NULL) {
+ reader.nextNull();
+ } else if (expectation == IOException.class) {
+ try {
+ reader.peek();
+ fail();
+ } catch (IOException expected) {
+ }
+ } else {
+ throw new AssertionError();
+ }
+ }
+ }
+
+ /**
+ * Returns a reader that returns one character at a time.
+ */
+ private Reader reader(final String s) {
+ /* if (true) */ return new StringReader(s);
+ /* return new Reader() {
+ int position = 0;
+ @Override public int read(char[] buffer, int offset, int count) throws IOException {
+ if (position == s.length()) {
+ return -1;
+ } else if (count > 0) {
+ buffer[offset] = s.charAt(position++);
+ return 1;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ }
+ @Override public void close() throws IOException {
+ }
+ }; */
+ }
+}
diff --git a/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java b/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java
new file mode 100644
index 00000000..4cfd55a7
--- /dev/null
+++ b/gson/src/test/java/com/google/gson/stream/JsonWriterTest.java
@@ -0,0 +1,579 @@
+/*
+ * Copyright (C) 2010 Google 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 com.google.gson.stream;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import junit.framework.TestCase;
+
+@SuppressWarnings("resource")
+public final class JsonWriterTest extends TestCase {
+
+ public void testWrongTopLevelType() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ try {
+ jsonWriter.value("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testTwoNames() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ try {
+ jsonWriter.name("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNameWithoutValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ try {
+ jsonWriter.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testValueWithoutName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.value(true);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testMultipleTopLevelValues() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray().endArray();
+ try {
+ jsonWriter.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testBadNestingObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testBadNestingArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testNullName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ try {
+ jsonWriter.name(null);
+ fail();
+ } catch (NullPointerException expected) {
+ }
+ }
+
+ public void testNullStringValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.value((String) null);
+ jsonWriter.endObject();
+ assertEquals("{\"a\":null}", stringWriter.toString());
+ }
+
+ public void testJsonValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.jsonValue("{\"b\":true}");
+ jsonWriter.name("c");
+ jsonWriter.value(1);
+ jsonWriter.endObject();
+ assertEquals("{\"a\":{\"b\":true},\"c\":1}", stringWriter.toString());
+ }
+
+ public void testNonFiniteDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.value(Double.NaN);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(Double.NEGATIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(Double.POSITIVE_INFINITY);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testNonFiniteBoxedDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ try {
+ jsonWriter.value(new Double(Double.NaN));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(new Double(Double.NEGATIVE_INFINITY));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ try {
+ jsonWriter.value(new Double(Double.POSITIVE_INFINITY));
+ fail();
+ } catch (IllegalArgumentException expected) {
+ }
+ }
+
+ public void testDoubles() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(-0.0);
+ jsonWriter.value(1.0);
+ jsonWriter.value(Double.MAX_VALUE);
+ jsonWriter.value(Double.MIN_VALUE);
+ jsonWriter.value(0.0);
+ jsonWriter.value(-0.5);
+ jsonWriter.value(2.2250738585072014E-308);
+ jsonWriter.value(Math.PI);
+ jsonWriter.value(Math.E);
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[-0.0,"
+ + "1.0,"
+ + "1.7976931348623157E308,"
+ + "4.9E-324,"
+ + "0.0,"
+ + "-0.5,"
+ + "2.2250738585072014E-308,"
+ + "3.141592653589793,"
+ + "2.718281828459045]", stringWriter.toString());
+ }
+
+ public void testLongs() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(0);
+ jsonWriter.value(1);
+ jsonWriter.value(-1);
+ jsonWriter.value(Long.MIN_VALUE);
+ jsonWriter.value(Long.MAX_VALUE);
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[0,"
+ + "1,"
+ + "-1,"
+ + "-9223372036854775808,"
+ + "9223372036854775807]", stringWriter.toString());
+ }
+
+ public void testNumbers() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(new BigInteger("0"));
+ jsonWriter.value(new BigInteger("9223372036854775808"));
+ jsonWriter.value(new BigInteger("-9223372036854775809"));
+ jsonWriter.value(new BigDecimal("3.141592653589793238462643383"));
+ jsonWriter.endArray();
+ jsonWriter.close();
+ assertEquals("[0,"
+ + "9223372036854775808,"
+ + "-9223372036854775809,"
+ + "3.141592653589793238462643383]", stringWriter.toString());
+ }
+
+ public void testBooleans() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value(true);
+ jsonWriter.value(false);
+ jsonWriter.endArray();
+ assertEquals("[true,false]", stringWriter.toString());
+ }
+
+ public void testNulls() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.nullValue();
+ jsonWriter.endArray();
+ assertEquals("[null]", stringWriter.toString());
+ }
+
+ public void testStrings() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value("a");
+ jsonWriter.value("a\"");
+ jsonWriter.value("\"");
+ jsonWriter.value(":");
+ jsonWriter.value(",");
+ jsonWriter.value("\b");
+ jsonWriter.value("\f");
+ jsonWriter.value("\n");
+ jsonWriter.value("\r");
+ jsonWriter.value("\t");
+ jsonWriter.value(" ");
+ jsonWriter.value("\\");
+ jsonWriter.value("{");
+ jsonWriter.value("}");
+ jsonWriter.value("[");
+ jsonWriter.value("]");
+ jsonWriter.value("\0");
+ jsonWriter.value("\u0019");
+ jsonWriter.endArray();
+ assertEquals("[\"a\","
+ + "\"a\\\"\","
+ + "\"\\\"\","
+ + "\":\","
+ + "\",\","
+ + "\"\\b\","
+ + "\"\\f\","
+ + "\"\\n\","
+ + "\"\\r\","
+ + "\"\\t\","
+ + "\" \","
+ + "\"\\\\\","
+ + "\"{\","
+ + "\"}\","
+ + "\"[\","
+ + "\"]\","
+ + "\"\\u0000\","
+ + "\"\\u0019\"]", stringWriter.toString());
+ }
+
+ public void testUnicodeLineBreaksEscaped() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.value("\u2028 \u2029");
+ jsonWriter.endArray();
+ assertEquals("[\"\\u2028 \\u2029\"]", stringWriter.toString());
+ }
+
+ public void testEmptyArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.endArray();
+ assertEquals("[]", stringWriter.toString());
+ }
+
+ public void testEmptyObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.endObject();
+ assertEquals("{}", stringWriter.toString());
+ }
+
+ public void testObjectsInArrays() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginArray();
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(5);
+ jsonWriter.name("b").value(false);
+ jsonWriter.endObject();
+ jsonWriter.beginObject();
+ jsonWriter.name("c").value(6);
+ jsonWriter.name("d").value(true);
+ jsonWriter.endObject();
+ jsonWriter.endArray();
+ assertEquals("[{\"a\":5,\"b\":false},"
+ + "{\"c\":6,\"d\":true}]", stringWriter.toString());
+ }
+
+ public void testArraysInObjects() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a");
+ jsonWriter.beginArray();
+ jsonWriter.value(5);
+ jsonWriter.value(false);
+ jsonWriter.endArray();
+ jsonWriter.name("b");
+ jsonWriter.beginArray();
+ jsonWriter.value(6);
+ jsonWriter.value(true);
+ jsonWriter.endArray();
+ jsonWriter.endObject();
+ assertEquals("{\"a\":[5,false],"
+ + "\"b\":[6,true]}", stringWriter.toString());
+ }
+
+ public void testDeepNestingArrays() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.beginArray();
+ }
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.endArray();
+ }
+ assertEquals("[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]", stringWriter.toString());
+ }
+
+ public void testDeepNestingObjects() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.name("a");
+ jsonWriter.beginObject();
+ }
+ for (int i = 0; i < 20; i++) {
+ jsonWriter.endObject();
+ }
+ jsonWriter.endObject();
+ assertEquals("{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":"
+ + "{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{\"a\":{"
+ + "}}}}}}}}}}}}}}}}}}}}}", stringWriter.toString());
+ }
+
+ public void testRepeatedName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(true);
+ jsonWriter.name("a").value(false);
+ jsonWriter.endObject();
+ // JsonWriter doesn't attempt to detect duplicate names
+ assertEquals("{\"a\":true,\"a\":false}", stringWriter.toString());
+ }
+
+ public void testPrettyPrintObject() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndent(" ");
+
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(true);
+ jsonWriter.name("b").value(false);
+ jsonWriter.name("c").value(5.0);
+ jsonWriter.name("e").nullValue();
+ jsonWriter.name("f").beginArray();
+ jsonWriter.value(6.0);
+ jsonWriter.value(7.0);
+ jsonWriter.endArray();
+ jsonWriter.name("g").beginObject();
+ jsonWriter.name("h").value(8.0);
+ jsonWriter.name("i").value(9.0);
+ jsonWriter.endObject();
+ jsonWriter.endObject();
+
+ String expected = "{\n"
+ + " \"a\": true,\n"
+ + " \"b\": false,\n"
+ + " \"c\": 5.0,\n"
+ + " \"e\": null,\n"
+ + " \"f\": [\n"
+ + " 6.0,\n"
+ + " 7.0\n"
+ + " ],\n"
+ + " \"g\": {\n"
+ + " \"h\": 8.0,\n"
+ + " \"i\": 9.0\n"
+ + " }\n"
+ + "}";
+ assertEquals(expected, stringWriter.toString());
+ }
+
+ public void testPrettyPrintArray() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter jsonWriter = new JsonWriter(stringWriter);
+ jsonWriter.setIndent(" ");
+
+ jsonWriter.beginArray();
+ jsonWriter.value(true);
+ jsonWriter.value(false);
+ jsonWriter.value(5.0);
+ jsonWriter.nullValue();
+ jsonWriter.beginObject();
+ jsonWriter.name("a").value(6.0);
+ jsonWriter.name("b").value(7.0);
+ jsonWriter.endObject();
+ jsonWriter.beginArray();
+ jsonWriter.value(8.0);
+ jsonWriter.value(9.0);
+ jsonWriter.endArray();
+ jsonWriter.endArray();
+
+ String expected = "[\n"
+ + " true,\n"
+ + " false,\n"
+ + " 5.0,\n"
+ + " null,\n"
+ + " {\n"
+ + " \"a\": 6.0,\n"
+ + " \"b\": 7.0\n"
+ + " },\n"
+ + " [\n"
+ + " 8.0,\n"
+ + " 9.0\n"
+ + " ]\n"
+ + "]";
+ assertEquals(expected, stringWriter.toString());
+ }
+
+ public void testLenientWriterPermitsMultipleTopLevelValues() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.setLenient(true);
+ writer.beginArray();
+ writer.endArray();
+ writer.beginArray();
+ writer.endArray();
+ writer.close();
+ assertEquals("[][]", stringWriter.toString());
+ }
+
+ public void testStrictWriterDoesNotPermitMultipleTopLevelValues() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginArray();
+ writer.endArray();
+ try {
+ writer.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testClosedWriterThrowsOnStructure() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginArray();
+ writer.endArray();
+ writer.close();
+ try {
+ writer.beginArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ writer.endArray();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ writer.beginObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ writer.endObject();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testClosedWriterThrowsOnName() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginArray();
+ writer.endArray();
+ writer.close();
+ try {
+ writer.name("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testClosedWriterThrowsOnValue() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginArray();
+ writer.endArray();
+ writer.close();
+ try {
+ writer.value("a");
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testClosedWriterThrowsOnFlush() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginArray();
+ writer.endArray();
+ writer.close();
+ try {
+ writer.flush();
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ }
+
+ public void testWriterCloseIsIdempotent() throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ JsonWriter writer = new JsonWriter(stringWriter);
+ writer.beginArray();
+ writer.endArray();
+ writer.close();
+ writer.close();
+ }
+}