();
+ 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
+ * Gson Issue 42
+ * 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 serialized 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:
+ *
+ * Maps as JSON objects
+ * 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}.
+ *
+ * Below is an example:
+ *
{@code
+ * Gson gson = new GsonBuilder()
+ * .register(Point.class, new MyPointTypeAdapter())
+ * .enableComplexMapKeySerialization()
+ * .create();
+ *
+ * Map original = new LinkedHashMap();
+ * original.put(new Point(5, 6), "a");
+ * original.put(new Point(8, 8), "b");
+ * System.out.println(gson.toJson(original, type));
+ * }
+ * The above code prints this JSON object: {@code
+ * {
+ * "(5,6)": "a",
+ * "(8,8)": "b"
+ * }
+ * }
+ *
+ * Maps as JSON arrays
+ * 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}}.
+ *
+ * Given the assumption above, a {@code Map} will be
+ * serialize as an array of arrays (can be viewed as an entry set of pairs).
+ *
+ * Below is an example of serializing complex types as JSON arrays:
+ *
{@code
+ * Gson gson = new GsonBuilder()
+ * .enableComplexMapKeySerialization()
+ * .create();
+ *
+ * Map original = new LinkedHashMap();
+ * 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:
+ * {@code
+ * [
+ * [
+ * {
+ * "x": 5,
+ * "y": 6
+ * },
+ * "a"
+ * ],
+ * [
+ * {
+ * "x": 8,
+ * "y": 8
+ * },
+ * "b"
+ * ]
+ * ]
+ * }
+ *
+ * @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 < > 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.
+ *
+ * The date format will be used to serialize and deserialize {@link java.util.Date}, {@link
+ * java.sql.Timestamp} and {@link java.sql.Date}.
+ *
+ *
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.
+ *
+ * @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.
+ *
+ * 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.
+ *
+ * @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.
+ *
+ * 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.
+ *
+ * @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.
+ *
+ * @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 (Primitives.isPrimitive(type) || Primitives.isWrapperType(type)) {
+ throw new IllegalArgumentException(
+ "Cannot register type adapters for " + type);
+ }
+ 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 JSON specification disallows
+ * special double values (NaN, Infinity, -Infinity). However,
+ * Javascript
+ * specification (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.
+ *
+ * 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 factories = new ArrayList();
+ 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 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/src/com/google/gson/InstanceCreator.java b/src/com/google/gson/InstanceCreator.java
new file mode 100644
index 0000000..d5096a0
--- /dev/null
+++ b/src/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.
+ * 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.
+ *
+ *
+ * public class Id<T> {
+ * private final Class<T> clazz;
+ * private final long value;
+ * public Id(Class<T> clazz, long value) {
+ * this.clazz = clazz;
+ * this.value = value;
+ * }
+ * }
+ *
+ *
+ * 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:
+ *
+ *
+ * private Id() {
+ * this(Object.class, 0L);
+ * }
+ *
+ *
+ * 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}:
+ *
+ *
+ * class IdInstanceCreator implements InstanceCreator<Id> {
+ * public Id createInstance(Type type) {
+ * return new Id(Object.class, 0L);
+ * }
+ * }
+ *
+ *
+ * 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
+ * new 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:
+ *
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdInstanceCreator()).create();
+ *
+ *
+ * @param the type of object that will be created by this implementation.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public interface InstanceCreator {
+
+ /**
+ * 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/src/com/google/gson/JsonArray.java b/src/com/google/gson/JsonArray.java
new file mode 100644
index 0000000..a658ce7
--- /dev/null
+++ b/src/com/google/gson/JsonArray.java
@@ -0,0 +1,282 @@
+/*
+ * 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 {
+ private final List elements;
+
+ /**
+ * Creates an empty JsonArray.
+ */
+ public JsonArray() {
+ elements = new ArrayList();
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * 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 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/src/com/google/gson/JsonDeserializationContext.java b/src/com/google/gson/JsonDeserializationContext.java
new file mode 100644
index 0000000..00c7505
--- /dev/null
+++ b/src/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 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 deserialize(JsonElement json, Type typeOfT) throws JsonParseException;
+}
\ No newline at end of file
diff --git a/src/com/google/gson/JsonDeserializer.java b/src/com/google/gson/JsonDeserializer.java
new file mode 100644
index 0000000..0589eb2
--- /dev/null
+++ b/src/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;
+
+/**
+ * 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)}.
+ *
+ * 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}.
+ *
+ *
+ * public class Id<T> {
+ * private final Class<T> clazz;
+ * private final long value;
+ * public Id(Class<T> clazz, long value) {
+ * this.clazz = clazz;
+ * this.value = value;
+ * }
+ * public long getValue() {
+ * return value;
+ * }
+ * }
+ *
+ *
+ * The default deserialization of {@code Id(com.foo.MyObject.class, 20L)} will require the
+ * Json string to be {"clazz":com.foo.MyObject,"value":20}. 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:
+ *
+ *
+ * class IdDeserializer implements JsonDeserializer<Id>() {
+ * public Id deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ * throws JsonParseException {
+ * return new Id((Class)typeOfT, id.getValue());
+ * }
+ *
+ *
+ * You will also need to register {@code IdDeserializer} with Gson as follows:
+ *
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdDeserializer()).create();
+ *
+ *
+ * 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 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 {
+
+ /**
+ * Gson invokes this call-back method during deserialization when it encounters a field of the
+ * specified type.
+ * 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/src/com/google/gson/JsonElement.java b/src/com/google/gson/JsonElement.java
new file mode 100644
index 0000000..985cfe5
--- /dev/null
+++ b/src/com/google/gson/JsonElement.java
@@ -0,0 +1,324 @@
+/*
+ * 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 {
+ /**
+ * 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 ClassCastException} 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 ClassCastException} 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 ClassCastException} 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 ClassCastException} 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/src/com/google/gson/JsonIOException.java b/src/com/google/gson/JsonIOException.java
new file mode 100644
index 0000000..dfeccd8
--- /dev/null
+++ b/src/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/src/com/google/gson/JsonNull.java b/src/com/google/gson/JsonNull.java
new file mode 100644
index 0000000..d0d8cef
--- /dev/null
+++ b/src/com/google/gson/JsonNull.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;
+
+/**
+ * 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
+ }
+
+ /**
+ * 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/src/com/google/gson/JsonObject.java b/src/com/google/gson/JsonObject.java
new file mode 100644
index 0000000..b628462
--- /dev/null
+++ b/src/com/google/gson/JsonObject.java
@@ -0,0 +1,199 @@
+/*
+ * 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$Preconditions;
+import java.util.LinkedHashMap;
+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 {
+ // We are using a linked hash map because it is important to preserve
+ // the order in which elements are inserted. This is needed to ensure
+ // that the fields of an object are inserted in the order they were
+ // defined in the class.
+ private final Map members = new LinkedHashMap();
+
+ /**
+ * Creates an empty JsonObject.
+ */
+ public JsonObject() {
+ }
+
+ /**
+ * 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($Gson$Preconditions.checkNotNull(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> 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) {
+ if (members.containsKey(memberName)) {
+ JsonElement member = members.get(memberName);
+ return member == null ? JsonNull.INSTANCE : member;
+ }
+ return null;
+ }
+
+ /**
+ * 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/src/com/google/gson/JsonParseException.java b/src/com/google/gson/JsonParseException.java
new file mode 100644
index 0000000..084f661
--- /dev/null
+++ b/src/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.
+ *
+ * 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}.
+ *
+ * @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/src/com/google/gson/JsonParser.java b/src/com/google/gson/JsonParser.java
new file mode 100644
index 0000000..53b5807
--- /dev/null
+++ b/src/com/google/gson/JsonParser.java
@@ -0,0 +1,98 @@
+/*
+ * 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.Streams;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.MalformedJsonException;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+
+/**
+ * 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);
+ } catch (JsonParseException e) {
+ if (e.getCause() instanceof EOFException) {
+ return JsonNull.INSTANCE;
+ }
+ throw e;
+ } finally {
+ json.setLenient(lenient);
+ }
+ }
+}
diff --git a/src/com/google/gson/JsonPrimitive.java b/src/com/google/gson/JsonPrimitive.java
new file mode 100644
index 0000000..90bbfc8
--- /dev/null
+++ b/src/com/google/gson/JsonPrimitive.java
@@ -0,0 +1,336 @@
+/*
+ * 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$Preconditions;
+import com.google.gson.internal.LazilyParsedNumber;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+/**
+ * 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);
+ }
+
+ 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/src/com/google/gson/JsonSerializationContext.java b/src/com/google/gson/JsonSerializationContext.java
new file mode 100644
index 0000000..ca3ec4f
--- /dev/null
+++ b/src/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/src/com/google/gson/JsonSerializer.java b/src/com/google/gson/JsonSerializer.java
new file mode 100644
index 0000000..a605003
--- /dev/null
+++ b/src/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)}.
+ *
+ * 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}.
+ *
+ *
+ * public class Id<T> {
+ * private final Class<T> clazz;
+ * private final long value;
+ *
+ * public Id(Class<T> clazz, long value) {
+ * this.clazz = clazz;
+ * this.value = value;
+ * }
+ *
+ * public long getValue() {
+ * return value;
+ * }
+ * }
+ *
+ *
+ * The default serialization of {@code Id(com.foo.MyObject.class, 20L)} will be
+ * {"clazz":com.foo.MyObject,"value":20}. 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:
+ *
+ *
+ * class IdSerializer implements JsonSerializer<Id>() {
+ * public JsonElement serialize(Id id, Type typeOfId, JsonSerializationContext context) {
+ * return new JsonPrimitive(id.getValue());
+ * }
+ * }
+ *
+ *
+ * You will also need to register {@code IdSerializer} with Gson as follows:
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Id.class, new IdSerializer()).create();
+ *
+ *
+ * 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 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 {
+
+ /**
+ * Gson invokes this call-back method during serialization when it encounters a field of the
+ * specified type.
+ *
+ * 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).
+ *
+ * @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/src/com/google/gson/JsonStreamParser.java b/src/com/google/gson/JsonStreamParser.java
new file mode 100644
index 0000000..f0438db
--- /dev/null
+++ b/src/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.
+ *
+ * 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:
+ *
+ *
+ * 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();
+ * }
+ * }
+ *
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ * @since 1.4
+ */
+public final class JsonStreamParser implements Iterator {
+ 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/src/com/google/gson/JsonSyntaxException.java b/src/com/google/gson/JsonSyntaxException.java
new file mode 100644
index 0000000..17c1d3d
--- /dev/null
+++ b/src/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/src/com/google/gson/LongSerializationPolicy.java b/src/com/google/gson/LongSerializationPolicy.java
new file mode 100644
index 0000000..3d9a2da
--- /dev/null
+++ b/src/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/src/com/google/gson/TreeTypeAdapter.java b/src/com/google/gson/TreeTypeAdapter.java
new file mode 100644
index 0000000..642e1c1
--- /dev/null
+++ b/src/com/google/gson/TreeTypeAdapter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.GsonInternalAccess;
+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 extends TypeAdapter {
+ private final JsonSerializer serializer;
+ private final JsonDeserializer deserializer;
+ private final Gson gson;
+ private final TypeToken typeToken;
+ private final TypeAdapterFactory skipPast;
+
+ /** The delegate is lazily created because it may not be needed, and creating it may fail. */
+ private TypeAdapter delegate;
+
+ private TreeTypeAdapter(JsonSerializer serializer, JsonDeserializer deserializer,
+ Gson gson, TypeToken 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 delegate() {
+ TypeAdapter d = delegate;
+ return d != null
+ ? d
+ : (delegate = GsonInternalAccess.INSTANCE.getNextAdapter(gson, 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 TypeAdapter create(Gson gson, TypeToken type) {
+ boolean matches = exactType != null
+ ? exactType.equals(type) || matchRawType && exactType.getType() == type.getRawType()
+ : hierarchyType.isAssignableFrom(type.getRawType());
+ return matches
+ ? new TreeTypeAdapter((JsonSerializer) serializer,
+ (JsonDeserializer) deserializer, gson, type, this)
+ : null;
+ }
+ }
+}
diff --git a/src/com/google/gson/TypeAdapter.java b/src/com/google/gson/TypeAdapter.java
new file mode 100644
index 0000000..c432570
--- /dev/null
+++ b/src/com/google/gson/TypeAdapter.java
@@ -0,0 +1,283 @@
+/*
+ * 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.
+ *
+ * Defining a type's JSON form
+ * 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: {@code
+ *
+ * public class PointAdapter extends TypeAdapter {
+ * 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);
+ * }
+ * }}
+ * 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.
+ *
+ * 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 value() or nullValue().
+ * 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.
+ *
+ *
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.
+ *
+ *
To use a custom type adapter with Gson, you must register it with a
+ * {@link GsonBuilder}:
{@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();
+ * }
+ *
+ * @since 2.1
+ */
+// non-Javadoc:
+//
+// JSON Conversion
+// 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:
{@code
+//
+// String json = "{'origin':'0,0','points':['1,2','3,4']}";
+// TypeAdapter graphAdapter = gson.getAdapter(Graph.class);
+// Graph graph = graphAdapter.fromJson(json);
+// }
+// And an example for serialization: {@code
+//
+// Graph graph = new Graph(...);
+// TypeAdapter graphAdapter = gson.getAdapter(Graph.class);
+// String json = graphAdapter.toJson(graph);
+// }
+//
+// Type adapters are type-specific . For example, a {@code
+// TypeAdapter} can convert {@code Date} instances to JSON and JSON to
+// instances of {@code Date}, but cannot convert any other types.
+//
+public abstract class TypeAdapter {
+
+ /**
+ * 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.
+ */
+ /*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:
+ * {@code
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
+ * new TypeAdapter() {
+ * 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();
+ * }
+ * 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:
+ * {@code
+ *
+ * Gson gson = new GsonBuilder().registerTypeAdapter(Foo.class,
+ * new TypeAdapter() {
+ * 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();
+ * }
+ * Note that we didn't need to check for nulls in our type adapter after we used nullSafe.
+ */
+ public final TypeAdapter nullSafe() {
+ return new TypeAdapter() {
+ @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.
+ */
+ /*public*/ final String toJson(T value) throws IOException {
+ StringWriter stringWriter = new StringWriter();
+ toJson(stringWriter, value);
+ 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}.
+ */
+ /*public*/ final JsonElement toJsonTree(T value) {
+ try {
+ JsonTreeWriter jsonWriter = new JsonTreeWriter();
+ jsonWriter.setLenient(true);
+ 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.
+ */
+ /*public*/ final T fromJson(Reader in) throws IOException {
+ JsonReader reader = new JsonReader(in);
+ reader.setLenient(true); // TODO: non-lenient?
+ 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.
+ */
+ /*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}.
+ */
+ /*public*/ final T fromJsonTree(JsonElement jsonTree) {
+ try {
+ JsonReader jsonReader = new JsonTreeReader(jsonTree);
+ jsonReader.setLenient(true);
+ return read(jsonReader);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+}
diff --git a/src/com/google/gson/TypeAdapterFactory.java b/src/com/google/gson/TypeAdapterFactory.java
new file mode 100644
index 0000000..536427f
--- /dev/null
+++ b/src/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.
+ *
+ * Example: Converting enums to lowercase
+ * 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: {@code
+ *
+ * public class LowercaseEnumTypeAdapterFactory implements TypeAdapter.Factory {
+ * public TypeAdapter create(Gson gson, TypeToken type) {
+ * Class rawType = (Class) type.getRawType();
+ * if (!rawType.isEnum()) {
+ * return null;
+ * }
+ *
+ * final Map lowercaseToConstant = new HashMap();
+ * for (T constant : rawType.getEnumConstants()) {
+ * lowercaseToConstant.put(toLowercase(constant), constant);
+ * }
+ *
+ * return new TypeAdapter() {
+ * 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);
+ * }
+ * }
+ * }
+ *
+ * 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.
+ *
+ *
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.
+ *
+ *
As with type adapters, factories must be registered with a {@link
+ * com.google.gson.GsonBuilder} for them to take effect:
{@code
+ *
+ * GsonBuilder builder = new GsonBuilder();
+ * builder.registerTypeAdapterFactory(new LowercaseEnumTypeAdapterFactory());
+ * ...
+ * Gson gson = builder.create();
+ * }
+ * If multiple factories support the same type, the factory registered earlier
+ * takes precedence.
+ *
+ * Example: composing other type adapters
+ * 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} is different from the type adapter for {@code
+ * Multiset}.
+ *
+ * The type adapter delegates 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:
{@code
+ *
+ * public class MultisetTypeAdapterFactory implements TypeAdapter.Factory {
+ * public TypeAdapter create(Gson gson, TypeToken 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) newMultisetAdapter(elementAdapter);
+ * }
+ *
+ * private TypeAdapter> newMultisetAdapter(
+ * final TypeAdapter elementAdapter) {
+ * return new TypeAdapter>() {
+ * public void write(JsonWriter out, Multiset value) throws IOException {
+ * if (value == null) {
+ * out.nullValue();
+ * return;
+ * }
+ *
+ * out.beginArray();
+ * for (Multiset.Entry entry : value.entrySet()) {
+ * out.value(entry.getCount());
+ * elementAdapter.write(out, entry.getElement());
+ * }
+ * out.endArray();
+ * }
+ *
+ * public Multiset read(JsonReader in) throws IOException {
+ * if (in.peek() == JsonToken.NULL) {
+ * in.nextNull();
+ * return null;
+ * }
+ *
+ * Multiset 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;
+ * }
+ * };
+ * }
+ * }
+ * }
+ * 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}.
+ */
+ TypeAdapter create(Gson gson, TypeToken type);
+}
diff --git a/src/com/google/gson/annotations/Expose.java b/src/com/google/gson/annotations/Expose.java
new file mode 100644
index 0000000..1b9c70d
--- /dev/null
+++ b/src/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.
+ *
+ * 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.
+ *
+ * Here is an example of how this annotation is meant to be used:
+ *
+ * public class User {
+ * @Expose private String firstName;
+ * @Expose(serialize = false) private String lastName;
+ * @Expose (serialize = false, deserialize = false) private String emailAddress;
+ * private String password;
+ * }
+ *
+ * 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.
+ *
+ * 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/src/com/google/gson/annotations/SerializedName.java b/src/com/google/gson/annotations/SerializedName.java
new file mode 100644
index 0000000..99755eb
--- /dev/null
+++ b/src/com/google/gson/annotations/SerializedName.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ *
+ *
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.
+ *
+ * Here is an example of how this annotation is meant to be used:
+ *
+ * public class SomeClassWithFields {
+ * @SerializedName("name") private final String someField;
+ * private final String someOtherField;
+ *
+ * public SomeClassWithFields(String a, String b) {
+ * this.someField = a;
+ * this.someOtherField = b;
+ * }
+ * }
+ *
+ *
+ * The following shows the output that is generated when serializing an instance of the
+ * above example class:
+ *
+ * SomeClassWithFields objectToSerialize = new SomeClassWithFields("a", "b");
+ * Gson gson = new Gson();
+ * String jsonRepresentation = gson.toJson(objectToSerialize);
+ * System.out.println(jsonRepresentation);
+ *
+ * ===== OUTPUT =====
+ * {"name":"a","someOtherField":"b"}
+ *
+ *
+ * NOTE: The value you specify in this annotation must be a valid JSON field name.
+ *
+ * @see com.google.gson.FieldNamingPolicy
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface SerializedName {
+
+ /**
+ * @return the desired name of the field when it is serialized
+ */
+ String value();
+}
diff --git a/src/com/google/gson/annotations/Since.java b/src/com/google/gson/annotations/Since.java
new file mode 100644
index 0000000..541f154
--- /dev/null
+++ b/src/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.
+ *
+ *
+ * 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.
+ *
+ *
Here is an example of how this annotation is meant to be used:
+ *
+ * public class User {
+ * private String firstName;
+ * private String lastName;
+ * @Since(1.0) private String emailAddress;
+ * @Since(1.0) private String password;
+ * @Since(1.1) private Address address;
+ * }
+ *
+ *
+ * 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}.
+ *
+ * @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/src/com/google/gson/annotations/Until.java b/src/com/google/gson/annotations/Until.java
new file mode 100644
index 0000000..4648b8a
--- /dev/null
+++ b/src/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.
+ *
+ *
+ * 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.
+ *
+ *
Here is an example of how this annotation is meant to be used:
+ *
+ * public class User {
+ * private String firstName;
+ * private String lastName;
+ * @Until(1.1) private String emailAddress;
+ * @Until(1.1) private String password;
+ * }
+ *
+ *
+ * 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/src/com/google/gson/annotations/package-info.java b/src/com/google/gson/annotations/package-info.java
new file mode 100644
index 0000000..1c461fd
--- /dev/null
+++ b/src/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/src/com/google/gson/internal/$Gson$Preconditions.java b/src/com/google/gson/internal/$Gson$Preconditions.java
new file mode 100644
index 0000000..ccc0390
--- /dev/null
+++ b/src/com/google/gson/internal/$Gson$Preconditions.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.internal;
+
+/**
+ * A simple utility class used to check method Preconditions.
+ *
+ *
+ * public long divideBy(long value) {
+ * Preconditions.checkArgument(value != 0);
+ * return this.value / value;
+ * }
+ *
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class $Gson$Preconditions {
+ public static 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/src/com/google/gson/internal/$Gson$Types.java b/src/com/google/gson/internal/$Gson$Types.java
new file mode 100644
index 0000000..044a0ee
--- /dev/null
+++ b/src/com/google/gson/internal/$Gson$Types.java
@@ -0,0 +1,584 @@
+/**
+ * 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.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() {}
+
+ /**
+ * 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} and the
+ * result when the supertype is {@code Collection.class} is {@code Collection}.
+ */
+ 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}, this returns {@code Iterable} 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, but it's declared to
+ * extend Hashtable.
+ */
+ 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;
+ checkArgument(ownerType != null || rawTypeAsClass.getEnclosingClass() == null);
+ checkArgument(ownerType == null || rawTypeAsClass.getEnclosingClass() != null);
+ }
+
+ 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/src/com/google/gson/internal/ConstructorConstructor.java b/src/com/google/gson/internal/ConstructorConstructor.java
new file mode 100644
index 0000000..9378c1d
--- /dev/null
+++ b/src/com/google/gson/internal/ConstructorConstructor.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.internal;
+
+import com.google.gson.InstanceCreator;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+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.SortedSet;
+import java.util.TreeSet;
+
+/**
+ * Returns a function that can construct an instance of a requested type.
+ */
+public final class ConstructorConstructor {
+ private final Map> instanceCreators;
+
+ public ConstructorConstructor(Map> instanceCreators) {
+ this.instanceCreators = instanceCreators;
+ }
+
+ public ConstructorConstructor() {
+ this(Collections.>emptyMap());
+ }
+
+ public ObjectConstructor getConstructor(TypeToken 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 creator = (InstanceCreator) instanceCreators.get(type);
+ if (creator != null) {
+ return new ObjectConstructor() {
+ public T construct() {
+ return creator.createInstance(type);
+ }
+ };
+ }
+
+ ObjectConstructor defaultConstructor = newDefaultConstructor(rawType);
+ if (defaultConstructor != null) {
+ return defaultConstructor;
+ }
+
+ ObjectConstructor defaultImplementation = newDefaultImplementationConstructor(rawType);
+ if (defaultImplementation != null) {
+ return defaultImplementation;
+ }
+
+ // finally try unsafe
+ return newUnsafeAllocator(type, rawType);
+ }
+
+ private ObjectConstructor newDefaultConstructor(Class super T> rawType) {
+ try {
+ final Constructor super T> constructor = rawType.getDeclaredConstructor();
+ if (!constructor.isAccessible()) {
+ constructor.setAccessible(true);
+ }
+ return new ObjectConstructor() {
+ @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
+ * subytpes.
+ */
+ @SuppressWarnings("unchecked") // use runtime checks to guarantee that 'T' is what it is
+ private ObjectConstructor newDefaultImplementationConstructor(Class super T> rawType) {
+ if (Collection.class.isAssignableFrom(rawType)) {
+ if (SortedSet.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor() {
+ public T construct() {
+ return (T) new TreeSet();
+ }
+ };
+ } else if (Set.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor() {
+ public T construct() {
+ return (T) new LinkedHashSet();
+ }
+ };
+ } else if (Queue.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor() {
+ public T construct() {
+ return (T) new LinkedList();
+ }
+ };
+ } else {
+ return new ObjectConstructor() {
+ public T construct() {
+ return (T) new ArrayList();
+ }
+ };
+ }
+ }
+
+ if (Map.class.isAssignableFrom(rawType)) {
+ return new ObjectConstructor() {
+ public T construct() {
+ return (T) new LinkedHashMap();
+ }
+ };
+ // TODO: SortedMap ?
+ }
+
+ return null;
+ }
+
+ private ObjectConstructor newUnsafeAllocator(
+ final Type type, final Class super T> rawType) {
+ return new ObjectConstructor() {
+ 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/src/com/google/gson/internal/Excluder.java b/src/com/google/gson/internal/Excluder.java
new file mode 100644
index 0000000..61c37b7
--- /dev/null
+++ b/src/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.
+ *
+ * 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 serializationStrategies = Collections.emptyList();
+ private List 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(serializationStrategies);
+ result.serializationStrategies.add(exclusionStrategy);
+ }
+ if (deserialization) {
+ result.deserializationStrategies
+ = new ArrayList(deserializationStrategies);
+ result.deserializationStrategies.add(exclusionStrategy);
+ }
+ return result;
+ }
+
+ public TypeAdapter create(final Gson gson, final TypeToken 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() {
+ /** The delegate is lazily created because it may not be needed, and creating it may fail. */
+ private TypeAdapter 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 delegate() {
+ TypeAdapter d = delegate;
+ return d != null
+ ? d
+ : (delegate = GsonInternalAccess.INSTANCE.getNextAdapter(gson, 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 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 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/src/com/google/gson/internal/GsonInternalAccess.java b/src/com/google/gson/internal/GsonInternalAccess.java
new file mode 100644
index 0000000..2bef899
--- /dev/null
+++ b/src/com/google/gson/internal/GsonInternalAccess.java
@@ -0,0 +1,39 @@
+/*
+ * 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.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Internal-only APIs of Gson available only to other classes in Gson.
+ */
+public abstract class GsonInternalAccess {
+ public static GsonInternalAccess INSTANCE;
+
+ /**
+ * Returns a type adapter for {@code} type that isn't {@code skipPast}. This
+ * can be used for type adapters to compose other, simpler type adapters.
+ *
+ * @throws IllegalArgumentException if this GSON cannot serialize and
+ * deserialize {@code type}.
+ */
+ public abstract TypeAdapter getNextAdapter(
+ Gson gson, TypeAdapterFactory skipPast, TypeToken type);
+}
diff --git a/src/com/google/gson/internal/JsonReaderInternalAccess.java b/src/com/google/gson/internal/JsonReaderInternalAccess.java
new file mode 100644
index 0000000..bbd4720
--- /dev/null
+++ b/src/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/src/com/google/gson/internal/LazilyParsedNumber.java b/src/com/google/gson/internal/LazilyParsedNumber.java
new file mode 100644
index 0000000..94ddd79
--- /dev/null
+++ b/src/com/google/gson/internal/LazilyParsedNumber.java
@@ -0,0 +1,69 @@
+/*
+ * 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.math.BigInteger;
+
+/**
+ * This class holds a number value that is lazily converted to a specific number type
+ *
+ * @author Inderjeet Singh
+ */
+@SuppressWarnings("serial")
+public final class LazilyParsedNumber extends Number {
+ private final String value;
+
+ 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 BigInteger(value).intValue();
+ }
+ }
+ }
+
+ @Override
+ public long longValue() {
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ return new BigInteger(value).longValue();
+ }
+ }
+
+ @Override
+ public float floatValue() {
+ return Float.parseFloat(value);
+ }
+
+ @Override
+ public double doubleValue() {
+ return Double.parseDouble(value);
+ }
+
+ @Override
+ public String toString() {
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/gson/internal/ObjectConstructor.java b/src/com/google/gson/internal/ObjectConstructor.java
new file mode 100644
index 0000000..6ef2060
--- /dev/null
+++ b/src/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 {
+
+ /**
+ * Returns a new instance.
+ */
+ public T construct();
+}
\ No newline at end of file
diff --git a/src/com/google/gson/internal/Pair.java b/src/com/google/gson/internal/Pair.java
new file mode 100644
index 0000000..205ae29
--- /dev/null
+++ b/src/com/google/gson/internal/Pair.java
@@ -0,0 +1,61 @@
+/*
+ * 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.internal;
+
+/**
+ * A simple object that holds onto a pair of object references, first and second.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ *
+ * @param
+ * @param
+ */
+public final class Pair {
+ public final FIRST first;
+ public final SECOND second;
+
+ public Pair(FIRST first, SECOND second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public int hashCode() {
+ return 17 * ((first != null) ? first.hashCode() : 0)
+ + 17 * ((second != null) ? second.hashCode() : 0);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Pair, ?>)) {
+ return false;
+ }
+
+ Pair, ?> that = (Pair, ?>) o;
+ return equal(this.first, that.first) && equal(this.second, that.second);
+ }
+
+ private static boolean equal(Object a, Object b) {
+ return a == b || (a != null && a.equals(b));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("{%s,%s}", first, second);
+ }
+}
\ No newline at end of file
diff --git a/src/com/google/gson/internal/Primitives.java b/src/com/google/gson/internal/Primitives.java
new file mode 100644
index 0000000..c928547
--- /dev/null
+++ b/src/com/google/gson/internal/Primitives.java
@@ -0,0 +1,118 @@
+/*
+ * 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.internal.$Gson$Preconditions;
+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() {}
+
+ /** A map from primitive types to their corresponding wrapper types. */
+ private static final Map, Class>> PRIMITIVE_TO_WRAPPER_TYPE;
+
+ /** A map from wrapper types to their corresponding primitive types. */
+ private static final Map, Class>> WRAPPER_TO_PRIMITIVE_TYPE;
+
+ // Sad that we can't use a BiMap. :(
+
+ static {
+ Map, Class>> primToWrap = new HashMap, Class>>(16);
+ Map, Class>> wrapToPrim = new HashMap, 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>> forward,
+ Map, 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.
+ *
+ * wrap(int.class) == Integer.class
+ * wrap(Integer.class) == Integer.class
+ * wrap(String.class) == String.class
+ *
+ */
+ public static Class wrap(Class type) {
+ // cast is safe: long.class and Long.class are both of type Class
+ @SuppressWarnings("unchecked")
+ Class wrapped = (Class) 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.
+ *
+ * unwrap(Integer.class) == int.class
+ * unwrap(int.class) == int.class
+ * unwrap(String.class) == String.class
+ *
+ */
+ public static Class unwrap(Class type) {
+ // cast is safe: long.class and Long.class are both of type Class
+ @SuppressWarnings("unchecked")
+ Class unwrapped = (Class) WRAPPER_TO_PRIMITIVE_TYPE.get(
+ $Gson$Preconditions.checkNotNull(type));
+ return (unwrapped == null) ? type : unwrapped;
+ }
+}
diff --git a/src/com/google/gson/internal/Streams.java b/src/com/google/gson/internal/Streams.java
new file mode 100644
index 0000000..3ac2590
--- /dev/null
+++ b/src/com/google/gson/internal/Streams.java
@@ -0,0 +1,115 @@
+/*
+ * 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 {
+ /**
+ * 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;
+ }
+ throw new JsonIOException(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);
+ }
+
+ 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 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/src/com/google/gson/internal/UnsafeAllocator.java b/src/com/google/gson/internal/UnsafeAllocator.java
new file mode 100644
index 0000000..d69e3dc
--- /dev/null
+++ b/src/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 newInstance(Class 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 newInstance(Class c) throws Exception {
+ return (T) allocateInstance.invoke(unsafe, c);
+ }
+ };
+ } 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 newInstance(Class c) throws Exception {
+ return (T) newInstance.invoke(null, c, Object.class);
+ }
+ };
+ } 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 newInstance(Class c) throws Exception {
+ return (T) newInstance.invoke(null, c, constructorId);
+ }
+ };
+ } catch (Exception ignored) {
+ }
+
+ // give up
+ return new UnsafeAllocator() {
+ @Override
+ public T newInstance(Class c) {
+ throw new UnsupportedOperationException("Cannot allocate " + c);
+ }
+ };
+ }
+}
diff --git a/src/com/google/gson/internal/bind/ArrayTypeAdapter.java b/src/com/google/gson/internal/bind/ArrayTypeAdapter.java
new file mode 100644
index 0000000..39ab762
--- /dev/null
+++ b/src/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 com.google.gson.Gson;
+import com.google.gson.TypeAdapter;
+import com.google.gson.TypeAdapterFactory;
+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.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 extends TypeAdapter {
+ public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public TypeAdapter create(Gson gson, TypeToken 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 componentType;
+ private final TypeAdapter componentTypeAdapter;
+
+ public ArrayTypeAdapter(Gson context, TypeAdapter componentTypeAdapter, Class componentType) {
+ this.componentTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper(context, componentTypeAdapter, componentType);
+ this.componentType = componentType;
+ }
+
+ public Object read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ List list = new ArrayList();
+ 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/src/com/google/gson/internal/bind/BigDecimalTypeAdapter.java b/src/com/google/gson/internal/bind/BigDecimalTypeAdapter.java
new file mode 100644
index 0000000..842812b
--- /dev/null
+++ b/src/com/google/gson/internal/bind/BigDecimalTypeAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * 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.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+
+/**
+ * Adapts a BigDecimal type to and from its JSON representation.
+ *
+ * @author Joel Leitch
+ */
+public final class BigDecimalTypeAdapter extends TypeAdapter {
+
+ @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);
+ }
+}
diff --git a/src/com/google/gson/internal/bind/BigIntegerTypeAdapter.java b/src/com/google/gson/internal/bind/BigIntegerTypeAdapter.java
new file mode 100644
index 0000000..789e078
--- /dev/null
+++ b/src/com/google/gson/internal/bind/BigIntegerTypeAdapter.java
@@ -0,0 +1,51 @@
+/*
+ * 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.JsonSyntaxException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonToken;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.math.BigInteger;
+
+/**
+ * Adapts a BigInteger type to and from its JSON representation.
+ *
+ * @author Joel Leitch
+ */
+public final class BigIntegerTypeAdapter extends TypeAdapter {
+
+ @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);
+ }
+}
diff --git a/src/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java b/src/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java
new file mode 100644
index 0000000..d1bed75
--- /dev/null
+++ b/src/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 TypeAdapter create(Gson gson, TypeToken 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 constructor = constructorConstructor.getConstructor(typeToken);
+
+ @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter
+ TypeAdapter result = new Adapter(gson, elementType, elementTypeAdapter, constructor);
+ return result;
+ }
+
+ private final class Adapter extends TypeAdapter> {
+ private final TypeAdapter elementTypeAdapter;
+ private final ObjectConstructor extends Collection> constructor;
+
+ public Adapter(Gson context, Type elementType,
+ TypeAdapter elementTypeAdapter,
+ ObjectConstructor extends Collection> constructor) {
+ this.elementTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper(context, elementTypeAdapter, elementType);
+ this.constructor = constructor;
+ }
+
+ public Collection read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ Collection 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 collection) throws IOException {
+ if (collection == null) {
+ out.nullValue(); // TODO: better policy here?
+ return;
+ }
+
+ out.beginArray();
+ for (E element : collection) {
+ elementTypeAdapter.write(out, element);
+ }
+ out.endArray();
+ }
+ }
+}
diff --git a/src/com/google/gson/internal/bind/DateTypeAdapter.java b/src/com/google/gson/internal/bind/DateTypeAdapter.java
new file mode 100644
index 0000000..f257172
--- /dev/null
+++ b/src/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 {
+ public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
+ @SuppressWarnings("unchecked") // we use a runtime check to make sure the 'T's equal
+ public TypeAdapter create(Gson gson, TypeToken typeToken) {
+ return typeToken.getRawType() == Date.class ? (TypeAdapter) 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/src/com/google/gson/internal/bind/JsonTreeReader.java b/src/com/google/gson/internal/bind/JsonTreeReader.java
new file mode 100644
index 0000000..6a83628
--- /dev/null
+++ b/src/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 stack = new ArrayList();
+
+ 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/src/com/google/gson/internal/bind/JsonTreeWriter.java b/src/com/google/gson/internal/bind/JsonTreeWriter.java
new file mode 100644
index 0000000..5f9f039
--- /dev/null
+++ b/src/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 stack = new ArrayList();
+
+ /** 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/src/com/google/gson/internal/bind/MapTypeAdapterFactory.java b/src/com/google/gson/internal/bind/MapTypeAdapterFactory.java
new file mode 100644
index 0000000..b64c552
--- /dev/null
+++ b/src/com/google/gson/internal/bind/MapTypeAdapterFactory.java
@@ -0,0 +1,277 @@
+/*
+ * 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.JsonIOException;
+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.
+ *
+ * Maps as JSON objects
+ * 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: {@code
+ * Map original = new LinkedHashMap();
+ * original.put(new Point(5, 6), "a");
+ * original.put(new Point(8, 8), "b");
+ * System.out.println(gson.toJson(original, type));
+ * }
+ * The above code prints this JSON object: {@code
+ * {
+ * "(5,6)": "a",
+ * "(8,8)": "b"
+ * }
+ * }
+ * 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:
+ * com.google.gson.JsonParseException: Expecting object found: "(5,6)"
+ * at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler
+ * at com.google.gson.ObjectNavigator.navigateClassFields
+ * ...
+ *
+ * Maps as JSON arrays
+ * 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.
+ *
+ * Register this adapter when you are creating your GSON instance.
+ *
{@code
+ * Gson gson = new GsonBuilder()
+ * .registerTypeAdapter(Map.class, new MapAsArrayTypeAdapter())
+ * .create();
+ * }
+ * 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:
+ * {@code
+ * [
+ * [
+ * {
+ * "x": 5,
+ * "y": 6
+ * },
+ * "a",
+ * ],
+ * [
+ * {
+ * "x": 8,
+ * "y": 8
+ * },
+ * "b"
+ * ]
+ * ]
+ * }
+ * 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 TypeAdapter create(Gson gson, TypeToken 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 constructor = constructorConstructor.getConstructor(typeToken);
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ // we don't define a type parameter for the key or value types
+ TypeAdapter 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 extends TypeAdapter> {
+ private final TypeAdapter keyTypeAdapter;
+ private final TypeAdapter valueTypeAdapter;
+ private final ObjectConstructor extends Map> constructor;
+
+ public Adapter(Gson context, Type keyType, TypeAdapter keyTypeAdapter,
+ Type valueType, TypeAdapter valueTypeAdapter,
+ ObjectConstructor extends Map> constructor) {
+ this.keyTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper(context, keyTypeAdapter, keyType);
+ this.valueTypeAdapter =
+ new TypeAdapterRuntimeTypeWrapper(context, valueTypeAdapter, valueType);
+ this.constructor = constructor;
+ }
+
+ public Map read(JsonReader in) throws IOException {
+ JsonToken peek = in.peek();
+ if (peek == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ }
+
+ Map 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 map) throws IOException {
+ if (map == null) {
+ out.nullValue();
+ return;
+ }
+
+ if (!complexMapKeySerialization) {
+ out.beginObject();
+ for (Map.Entry entry : map.entrySet()) {
+ out.name(String.valueOf(entry.getKey()));
+ valueTypeAdapter.write(out, entry.getValue());
+ }
+ out.endObject();
+ return;
+ }
+
+ boolean hasComplexKeys = false;
+ List keys = new ArrayList