From: Marius Gavrilescu Date: Thu, 23 May 2013 16:44:11 +0000 (+0300) Subject: Move GSON from libs/ to src/ and fix the search call X-Git-Url: http://git.ieval.ro/?a=commitdiff_plain;h=cfd903b6b5113fa28991db19bf98d0340ae709a4;p=unical.git Move GSON from libs/ to src/ and fix the search call --- diff --git a/.classpath b/.classpath index bb0c759..5e6314e 100644 --- a/.classpath +++ b/.classpath @@ -3,7 +3,6 @@ - diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar deleted file mode 100644 index 65ebaf8..0000000 Binary files a/libs/android-support-v4.jar and /dev/null differ diff --git a/libs/gson-2.2.4-javadoc.jar b/libs/gson-2.2.4-javadoc.jar deleted file mode 100644 index ce8fa7d..0000000 Binary files a/libs/gson-2.2.4-javadoc.jar and /dev/null differ diff --git a/libs/gson-2.2.4-sources.jar b/libs/gson-2.2.4-sources.jar deleted file mode 100644 index 30b13f6..0000000 Binary files a/libs/gson-2.2.4-sources.jar and /dev/null differ diff --git a/libs/gson-2.2.4.jar b/libs/gson-2.2.4.jar deleted file mode 100644 index 9478253..0000000 Binary files a/libs/gson-2.2.4.jar and /dev/null differ diff --git a/res/layout/eventview.xml b/res/layout/eventview.xml index 218fae9..e462f42 100644 --- a/res/layout/eventview.xml +++ b/res/layout/eventview.xml @@ -24,11 +24,6 @@ android:textSize="20sp" /> - - , JsonDeserializer { + + // TODO: migrate to streaming adapter + + private final DateFormat enUsFormat; + private final DateFormat localFormat; + private final DateFormat iso8601Format; + + DefaultDateTypeAdapter() { + this(DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.US), + DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT)); + } + + DefaultDateTypeAdapter(String datePattern) { + this(new SimpleDateFormat(datePattern, Locale.US), new SimpleDateFormat(datePattern)); + } + + DefaultDateTypeAdapter(int style) { + this(DateFormat.getDateInstance(style, Locale.US), DateFormat.getDateInstance(style)); + } + + public DefaultDateTypeAdapter(int dateStyle, int timeStyle) { + this(DateFormat.getDateTimeInstance(dateStyle, timeStyle, Locale.US), + DateFormat.getDateTimeInstance(dateStyle, timeStyle)); + } + + DefaultDateTypeAdapter(DateFormat enUsFormat, DateFormat localFormat) { + this.enUsFormat = enUsFormat; + this.localFormat = localFormat; + this.iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); + this.iso8601Format.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + // These methods need to be synchronized since JDK DateFormat classes are not thread-safe + // See issue 162 + public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) { + synchronized (localFormat) { + String dateFormatAsString = enUsFormat.format(src); + return new JsonPrimitive(dateFormatAsString); + } + } + + public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (!(json instanceof JsonPrimitive)) { + throw new JsonParseException("The date should be a string value"); + } + Date date = deserializeToDate(json); + if (typeOfT == Date.class) { + return date; + } else if (typeOfT == Timestamp.class) { + return new Timestamp(date.getTime()); + } else if (typeOfT == java.sql.Date.class) { + return new java.sql.Date(date.getTime()); + } else { + throw new IllegalArgumentException(getClass() + " cannot deserialize to " + typeOfT); + } + } + + private Date deserializeToDate(JsonElement json) { + synchronized (localFormat) { + try { + return localFormat.parse(json.getAsString()); + } catch (ParseException ignored) { + } + try { + return enUsFormat.parse(json.getAsString()); + } catch (ParseException ignored) { + } + try { + return iso8601Format.parse(json.getAsString()); + } catch (ParseException e) { + throw new JsonSyntaxException(json.getAsString(), e); + } + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(DefaultDateTypeAdapter.class.getSimpleName()); + sb.append('(').append(localFormat.getClass().getSimpleName()).append(')'); + return sb.toString(); + } +} diff --git a/src/com/google/gson/ExclusionStrategy.java b/src/com/google/gson/ExclusionStrategy.java new file mode 100644 index 0000000..08fafed --- /dev/null +++ b/src/com/google/gson/ExclusionStrategy.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +/** + * A strategy (or policy) definition that is used to decide whether or not a field or top-level + * class should be serialized or deserialized as part of the JSON output/input. For serialization, + * if the {@link #shouldSkipClass(Class)} method returns false then that class or field type + * will not be part of the JSON output. For deserialization, if {@link #shouldSkipClass(Class)} + * returns false, then it will not be set as part of the Java object structure. + * + *

The following are a few examples that shows how you can use this exclusion mechanism. + * + *

Exclude fields and objects based on a particular class type: + *

+ * private static class SpecificClassExclusionStrategy implements ExclusionStrategy {
+ *   private final Class<?> excludedThisClass;
+ *
+ *   public SpecificClassExclusionStrategy(Class<?> excludedThisClass) {
+ *     this.excludedThisClass = excludedThisClass;
+ *   }
+ *
+ *   public boolean shouldSkipClass(Class<?> clazz) {
+ *     return excludedThisClass.equals(clazz);
+ *   }
+ *
+ *   public boolean shouldSkipField(FieldAttributes f) {
+ *     return excludedThisClass.equals(f.getDeclaredClass());
+ *   }
+ * }
+ * 
+ * + *

Excludes fields and objects based on a particular annotation: + *

+ * public @interface FooAnnotation {
+ *   // some implementation here
+ * }
+ *
+ * // Excludes any field (or class) that is tagged with an "@FooAnnotation"
+ * private static class FooAnnotationExclusionStrategy implements ExclusionStrategy {
+ *   public boolean shouldSkipClass(Class<?> clazz) {
+ *     return clazz.getAnnotation(FooAnnotation.class) != null;
+ *   }
+ *
+ *   public boolean shouldSkipField(FieldAttributes f) {
+ *     return f.getAnnotation(FooAnnotation.class) != null;
+ *   }
+ * }
+ * 
+ * + *

Now if you want to configure {@code Gson} to use a user defined exclusion strategy, then + * the {@code GsonBuilder} is required. The following is an example of how you can use the + * {@code GsonBuilder} to configure Gson to use one of the above sample: + *

+ * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
+ * Gson gson = new GsonBuilder()
+ *     .setExclusionStrategies(excludeStrings)
+ *     .create();
+ * 
+ * + *

For certain model classes, you may only want to serialize a field, but exclude it for + * deserialization. To do that, you can write an {@code ExclusionStrategy} as per normal; + * however, you would register it with the + * {@link GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy)} method. + * For example: + *

+ * ExclusionStrategy excludeStrings = new UserDefinedExclusionStrategy(String.class);
+ * Gson gson = new GsonBuilder()
+ *     .addDeserializationExclusionStrategy(excludeStrings)
+ *     .create();
+ * 
+ * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @see GsonBuilder#setExclusionStrategies(ExclusionStrategy...) + * @see GsonBuilder#addDeserializationExclusionStrategy(ExclusionStrategy) + * @see GsonBuilder#addSerializationExclusionStrategy(ExclusionStrategy) + * + * @since 1.4 + */ +public interface ExclusionStrategy { + + /** + * @param f the field object that is under test + * @return true if the field should be ignored; otherwise false + */ + public boolean shouldSkipField(FieldAttributes f); + + /** + * @param clazz the class object that is under test + * @return true if the class should be ignored; otherwise false + */ + public boolean shouldSkipClass(Class clazz); +} diff --git a/src/com/google/gson/FieldAttributes.java b/src/com/google/gson/FieldAttributes.java new file mode 100644 index 0000000..cb89ff1 --- /dev/null +++ b/src/com/google/gson/FieldAttributes.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import com.google.gson.internal.$Gson$Preconditions; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; + +/** + * A data object that stores attributes of a field. + * + *

This class is immutable; therefore, it can be safely shared across threads. + * + * @author Inderjeet Singh + * @author Joel Leitch + * + * @since 1.4 + */ +public final class FieldAttributes { + private final Field field; + + /** + * Constructs a Field Attributes object from the {@code f}. + * + * @param f the field to pull attributes from + */ + public FieldAttributes(Field f) { + $Gson$Preconditions.checkNotNull(f); + this.field = f; + } + + /** + * @return the declaring class that contains this field + */ + public Class getDeclaringClass() { + return field.getDeclaringClass(); + } + + /** + * @return the name of the field + */ + public String getName() { + return field.getName(); + } + + /** + *

For example, assume the following class definition: + *

+   * public class Foo {
+   *   private String bar;
+   *   private List<String> red;
+   * }
+   *
+   * Type listParmeterizedType = new TypeToken<List<String>>() {}.getType();
+   * 
+ * + *

This method would return {@code String.class} for the {@code bar} field and + * {@code listParameterizedType} for the {@code red} field. + * + * @return the specific type declared for this field + */ + public Type getDeclaredType() { + return field.getGenericType(); + } + + /** + * Returns the {@code Class} object that was declared for this field. + * + *

For example, assume the following class definition: + *

+   * public class Foo {
+   *   private String bar;
+   *   private List<String> red;
+   * }
+   * 
+ * + *

This method would return {@code String.class} for the {@code bar} field and + * {@code List.class} for the {@code red} field. + * + * @return the specific class object that was declared for the field + */ + public Class getDeclaredClass() { + return field.getType(); + } + + /** + * Return the {@code T} annotation object from this field if it exist; otherwise returns + * {@code null}. + * + * @param annotation the class of the annotation that will be retrieved + * @return the annotation instance if it is bound to the field; otherwise {@code null} + */ + public T getAnnotation(Class annotation) { + return field.getAnnotation(annotation); + } + + /** + * Return the annotations that are present on this field. + * + * @return an array of all the annotations set on the field + * @since 1.4 + */ + public Collection getAnnotations() { + return Arrays.asList(field.getAnnotations()); + } + + /** + * Returns {@code true} if the field is defined with the {@code modifier}. + * + *

This method is meant to be called as: + *

+   * boolean hasPublicModifier = fieldAttribute.hasModifier(java.lang.reflect.Modifier.PUBLIC);
+   * 
+ * + * @see java.lang.reflect.Modifier + */ + public boolean hasModifier(int modifier) { + return (field.getModifiers() & modifier) != 0; + } + + /** + * This is exposed internally only for the removing synthetic fields from the JSON output. + * + * @return true if the field is synthetic; otherwise false + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + Object get(Object instance) throws IllegalAccessException { + return field.get(instance); + } + + /** + * This is exposed internally only for the removing synthetic fields from the JSON output. + * + * @return true if the field is synthetic; otherwise false + */ + boolean isSynthetic() { + return field.isSynthetic(); + } +} diff --git a/src/com/google/gson/FieldNamingPolicy.java b/src/com/google/gson/FieldNamingPolicy.java new file mode 100644 index 0000000..21863f9 --- /dev/null +++ b/src/com/google/gson/FieldNamingPolicy.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.lang.reflect.Field; + +/** + * An enumeration that defines a few standard naming conventions for JSON field names. + * This enumeration should be used in conjunction with {@link com.google.gson.GsonBuilder} + * to configure a {@link com.google.gson.Gson} instance to properly translate Java field + * names into the desired JSON field names. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public enum FieldNamingPolicy implements FieldNamingStrategy { + + /** + * Using this naming policy with Gson will ensure that the field name is + * unchanged. + */ + IDENTITY() { + public String translateName(Field f) { + return f.getName(); + } + }, + + /** + * Using this naming policy with Gson will ensure that the first "letter" of the Java + * field name is capitalized when serialized to its JSON form. + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> SomeFieldName
  • + *
  • _someFieldName ---> _SomeFieldName
  • + *
+ */ + UPPER_CAMEL_CASE() { + public String translateName(Field f) { + return upperCaseFirstLetter(f.getName()); + } + }, + + /** + * Using this naming policy with Gson will ensure that the first "letter" of the Java + * field name is capitalized when serialized to its JSON form and the words will be + * separated by a space. + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> Some Field Name
  • + *
  • _someFieldName ---> _Some Field Name
  • + *
+ * + * @since 1.4 + */ + UPPER_CAMEL_CASE_WITH_SPACES() { + public String translateName(Field f) { + return upperCaseFirstLetter(separateCamelCase(f.getName(), " ")); + } + }, + + /** + * Using this naming policy with Gson will modify the Java Field name from its camel cased + * form to a lower case field name where each word is separated by an underscore (_). + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> some_field_name
  • + *
  • _someFieldName ---> _some_field_name
  • + *
  • aStringField ---> a_string_field
  • + *
  • aURL ---> a_u_r_l
  • + *
+ */ + LOWER_CASE_WITH_UNDERSCORES() { + public String translateName(Field f) { + return separateCamelCase(f.getName(), "_").toLowerCase(); + } + }, + + /** + * Using this naming policy with Gson will modify the Java Field name from its camel cased + * form to a lower case field name where each word is separated by a dash (-). + * + *

Here's a few examples of the form "Java Field Name" ---> "JSON Field Name":

+ *
    + *
  • someFieldName ---> some-field-name
  • + *
  • _someFieldName ---> _some-field-name
  • + *
  • aStringField ---> a-string-field
  • + *
  • aURL ---> a-u-r-l
  • + *
+ * Using dashes in JavaScript is not recommended since dash is also used for a minus sign in + * expressions. This requires that a field named with dashes is always accessed as a quoted + * property like {@code myobject['my-field']}. Accessing it as an object field + * {@code myobject.my-field} will result in an unintended javascript expression. + * @since 1.4 + */ + LOWER_CASE_WITH_DASHES() { + public String translateName(Field f) { + return separateCamelCase(f.getName(), "-").toLowerCase(); + } + }; + + /** + * Converts the field name that uses camel-case define word separation into + * separate words that are separated by the provided {@code separatorString}. + */ + private static String separateCamelCase(String name, String separator) { + StringBuilder translation = new StringBuilder(); + for (int i = 0; i < name.length(); i++) { + char character = name.charAt(i); + if (Character.isUpperCase(character) && translation.length() != 0) { + translation.append(separator); + } + translation.append(character); + } + return translation.toString(); + } + + /** + * Ensures the JSON field names begins with an upper case letter. + */ + private static String upperCaseFirstLetter(String name) { + StringBuilder fieldNameBuilder = new StringBuilder(); + int index = 0; + char firstCharacter = name.charAt(index); + + while (index < name.length() - 1) { + if (Character.isLetter(firstCharacter)) { + break; + } + + fieldNameBuilder.append(firstCharacter); + firstCharacter = name.charAt(++index); + } + + if (index == name.length()) { + return fieldNameBuilder.toString(); + } + + if (!Character.isUpperCase(firstCharacter)) { + String modifiedTarget = modifyString(Character.toUpperCase(firstCharacter), name, ++index); + return fieldNameBuilder.append(modifiedTarget).toString(); + } else { + return name; + } + } + + private static String modifyString(char firstCharacter, String srcString, int indexOfSubstring) { + return (indexOfSubstring < srcString.length()) + ? firstCharacter + srcString.substring(indexOfSubstring) + : String.valueOf(firstCharacter); + } +} \ No newline at end of file diff --git a/src/com/google/gson/FieldNamingStrategy.java b/src/com/google/gson/FieldNamingStrategy.java new file mode 100644 index 0000000..9be453a --- /dev/null +++ b/src/com/google/gson/FieldNamingStrategy.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.lang.reflect.Field; + +/** + * A mechanism for providing custom field naming in Gson. This allows the client code to translate + * field names into a particular convention that is not supported as a normal Java field + * declaration rules. For example, Java does not support "-" characters in a field name. + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.3 + */ +public interface FieldNamingStrategy { + + /** + * Translates the field name into its JSON field name representation. + * + * @param f the field object that we are translating + * @return the translated field name. + * @since 1.3 + */ + public String translateName(Field f); +} diff --git a/src/com/google/gson/Gson.java b/src/com/google/gson/Gson.java new file mode 100644 index 0000000..a54188a --- /dev/null +++ b/src/com/google/gson/Gson.java @@ -0,0 +1,905 @@ +/* + * 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.ConstructorConstructor; +import com.google.gson.internal.Excluder; +import com.google.gson.internal.Primitives; +import com.google.gson.internal.Streams; +import com.google.gson.internal.bind.ArrayTypeAdapter; +import com.google.gson.internal.bind.CollectionTypeAdapterFactory; +import com.google.gson.internal.bind.DateTypeAdapter; +import com.google.gson.internal.bind.JsonTreeReader; +import com.google.gson.internal.bind.JsonTreeWriter; +import com.google.gson.internal.bind.MapTypeAdapterFactory; +import com.google.gson.internal.bind.ObjectTypeAdapter; +import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; +import com.google.gson.internal.bind.SqlDateTypeAdapter; +import com.google.gson.internal.bind.TimeTypeAdapter; +import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import com.google.gson.stream.MalformedJsonException; +import java.io.EOFException; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This is the main class for using Gson. Gson is typically used by first constructing a + * Gson instance and then invoking {@link #toJson(Object)} or {@link #fromJson(String, Class)} + * methods on it. + * + *

You can create a Gson instance by invoking {@code new Gson()} if the default configuration + * is all you need. You can also use {@link GsonBuilder} to build a Gson instance with various + * configuration options such as versioning support, pretty printing, custom + * {@link JsonSerializer}s, {@link JsonDeserializer}s, and {@link InstanceCreator}s.

+ * + *

Here is an example of how Gson is used for a simple Class: + * + *

+ * Gson gson = new Gson(); // Or use new GsonBuilder().create();
+ * MyType target = new MyType();
+ * String json = gson.toJson(target); // serializes target to Json
+ * MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2
+ * 

+ * + *

If the object that your are serializing/deserializing is a {@code ParameterizedType} + * (i.e. contains at least one type parameter and may be an array) then you must use the + * {@link #toJson(Object, Type)} or {@link #fromJson(String, Type)} method. Here is an + * example for serializing and deserialing a {@code ParameterizedType}: + * + *

+ * Type listType = new TypeToken<List<String>>() {}.getType();
+ * List<String> target = new LinkedList<String>();
+ * target.add("blah");
+ *
+ * Gson gson = new Gson();
+ * String json = gson.toJson(target, listType);
+ * List<String> target2 = gson.fromJson(json, listType);
+ * 

+ * + *

See the Gson User Guide + * for a more complete set of examples.

+ * + * @see com.google.gson.reflect.TypeToken + * + * @author Inderjeet Singh + * @author Joel Leitch + * @author Jesse Wilson + */ +public final class Gson { + static final boolean DEFAULT_JSON_NON_EXECUTABLE = false; + + private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; + + /** + * This thread local guards against reentrant calls to getAdapter(). In + * certain object graphs, creating an adapter for a type may recursively + * require an adapter for the same type! Without intervention, the recursive + * lookup would stack overflow. We cheat by returning a proxy type adapter. + * The proxy is wired up once the initial adapter has been created. + */ + private final ThreadLocal, FutureTypeAdapter>> calls + = new ThreadLocal, FutureTypeAdapter>>(); + + private final Map, TypeAdapter> typeTokenCache + = Collections.synchronizedMap(new HashMap, TypeAdapter>()); + + private final List factories; + private final ConstructorConstructor constructorConstructor; + + private final boolean serializeNulls; + private final boolean htmlSafe; + private final boolean generateNonExecutableJson; + private final boolean prettyPrinting; + + final JsonDeserializationContext deserializationContext = new JsonDeserializationContext() { + @SuppressWarnings("unchecked") + public T deserialize(JsonElement json, Type typeOfT) throws JsonParseException { + return (T) fromJson(json, typeOfT); + } + }; + + final JsonSerializationContext serializationContext = new JsonSerializationContext() { + public JsonElement serialize(Object src) { + return toJsonTree(src); + } + public JsonElement serialize(Object src, Type typeOfSrc) { + return toJsonTree(src, typeOfSrc); + } + }; + + /** + * Constructs a Gson object with default configuration. The default configuration has the + * following settings: + *
    + *
  • The JSON generated by toJson methods is in compact representation. This + * means that all the unneeded white-space is removed. You can change this behavior with + * {@link GsonBuilder#setPrettyPrinting()}.
  • + *
  • The generated JSON omits all the fields that are null. Note that nulls in arrays are + * kept as is since an array is an ordered list. Moreover, if a field is not null, but its + * generated JSON is empty, the field is kept. You can configure Gson to serialize null values + * by setting {@link GsonBuilder#serializeNulls()}.
  • + *
  • Gson provides default serialization and deserialization for Enums, {@link Map}, + * {@link java.net.URL}, {@link java.net.URI}, {@link java.util.Locale}, {@link java.util.Date}, + * {@link java.math.BigDecimal}, and {@link java.math.BigInteger} classes. If you would prefer + * to change the default representation, you can do so by registering a type adapter through + * {@link GsonBuilder#registerTypeAdapter(Type, Object)}.
  • + *
  • The default Date format is same as {@link java.text.DateFormat#DEFAULT}. This format + * ignores the millisecond portion of the date during serialization. You can change + * this by invoking {@link GsonBuilder#setDateFormat(int)} or + * {@link GsonBuilder#setDateFormat(String)}.
  • + *
  • By default, Gson ignores the {@link com.google.gson.annotations.Expose} annotation. + * You can enable Gson to serialize/deserialize only those fields marked with this annotation + * through {@link GsonBuilder#excludeFieldsWithoutExposeAnnotation()}.
  • + *
  • By default, Gson ignores the {@link com.google.gson.annotations.Since} annotation. You + * can enable Gson to use this annotation through {@link GsonBuilder#setVersion(double)}.
  • + *
  • The default field naming policy for the output Json is same as in Java. So, a Java class + * field versionNumber will be output as "versionNumber@quot; in + * Json. The same rules are applied for mapping incoming Json to the Java classes. You can + * change this policy through {@link GsonBuilder#setFieldNamingPolicy(FieldNamingPolicy)}.
  • + *
  • By default, Gson excludes transient or static fields from + * consideration for serialization and deserialization. You can change this behavior through + * {@link GsonBuilder#excludeFieldsWithModifiers(int...)}.
  • + *
+ */ + public Gson() { + this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY, + Collections.>emptyMap(), false, false, DEFAULT_JSON_NON_EXECUTABLE, + true, false, false, LongSerializationPolicy.DEFAULT, + Collections.emptyList()); + } + + Gson(final Excluder excluder, final FieldNamingStrategy fieldNamingPolicy, + final Map> instanceCreators, boolean serializeNulls, + boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe, + boolean prettyPrinting, boolean serializeSpecialFloatingPointValues, + LongSerializationPolicy longSerializationPolicy, + List typeAdapterFactories) { + this.constructorConstructor = new ConstructorConstructor(instanceCreators); + this.serializeNulls = serializeNulls; + this.generateNonExecutableJson = generateNonExecutableGson; + this.htmlSafe = htmlSafe; + this.prettyPrinting = prettyPrinting; + + List factories = new ArrayList(); + + // built-in type adapters that cannot be overridden + factories.add(TypeAdapters.JSON_ELEMENT_FACTORY); + factories.add(ObjectTypeAdapter.FACTORY); + + // the excluder must precede all adapters that handle user-defined types + factories.add(excluder); + + // user's type adapters + factories.addAll(typeAdapterFactories); + + // type adapters for basic platform types + factories.add(TypeAdapters.STRING_FACTORY); + factories.add(TypeAdapters.INTEGER_FACTORY); + factories.add(TypeAdapters.BOOLEAN_FACTORY); + factories.add(TypeAdapters.BYTE_FACTORY); + factories.add(TypeAdapters.SHORT_FACTORY); + factories.add(TypeAdapters.newFactory(long.class, Long.class, + longAdapter(longSerializationPolicy))); + factories.add(TypeAdapters.newFactory(double.class, Double.class, + doubleAdapter(serializeSpecialFloatingPointValues))); + factories.add(TypeAdapters.newFactory(float.class, Float.class, + floatAdapter(serializeSpecialFloatingPointValues))); + factories.add(TypeAdapters.NUMBER_FACTORY); + factories.add(TypeAdapters.CHARACTER_FACTORY); + factories.add(TypeAdapters.STRING_BUILDER_FACTORY); + factories.add(TypeAdapters.STRING_BUFFER_FACTORY); + factories.add(TypeAdapters.newFactory(BigDecimal.class, TypeAdapters.BIG_DECIMAL)); + factories.add(TypeAdapters.newFactory(BigInteger.class, TypeAdapters.BIG_INTEGER)); + factories.add(TypeAdapters.URL_FACTORY); + factories.add(TypeAdapters.URI_FACTORY); + factories.add(TypeAdapters.UUID_FACTORY); + factories.add(TypeAdapters.LOCALE_FACTORY); + factories.add(TypeAdapters.INET_ADDRESS_FACTORY); + factories.add(TypeAdapters.BIT_SET_FACTORY); + factories.add(DateTypeAdapter.FACTORY); + factories.add(TypeAdapters.CALENDAR_FACTORY); + factories.add(TimeTypeAdapter.FACTORY); + factories.add(SqlDateTypeAdapter.FACTORY); + factories.add(TypeAdapters.TIMESTAMP_FACTORY); + factories.add(ArrayTypeAdapter.FACTORY); + factories.add(TypeAdapters.ENUM_FACTORY); + factories.add(TypeAdapters.CLASS_FACTORY); + + // type adapters for composite and user-defined types + factories.add(new CollectionTypeAdapterFactory(constructorConstructor)); + factories.add(new MapTypeAdapterFactory(constructorConstructor, complexMapKeySerialization)); + factories.add(new ReflectiveTypeAdapterFactory( + constructorConstructor, fieldNamingPolicy, excluder)); + + this.factories = Collections.unmodifiableList(factories); + } + + private TypeAdapter doubleAdapter(boolean serializeSpecialFloatingPointValues) { + if (serializeSpecialFloatingPointValues) { + return TypeAdapters.DOUBLE; + } + return new TypeAdapter() { + @Override public Double read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return in.nextDouble(); + } + @Override public void write(JsonWriter out, Number value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + double doubleValue = value.doubleValue(); + checkValidFloatingPoint(doubleValue); + out.value(value); + } + }; + } + + private TypeAdapter floatAdapter(boolean serializeSpecialFloatingPointValues) { + if (serializeSpecialFloatingPointValues) { + return TypeAdapters.FLOAT; + } + return new TypeAdapter() { + @Override public Float read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return (float) in.nextDouble(); + } + @Override public void write(JsonWriter out, Number value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + float floatValue = value.floatValue(); + checkValidFloatingPoint(floatValue); + out.value(value); + } + }; + } + + private void checkValidFloatingPoint(double value) { + if (Double.isNaN(value) || Double.isInfinite(value)) { + throw new IllegalArgumentException(value + + " is not a valid double value as per JSON specification. To override this" + + " behavior, use GsonBuilder.serializeSpecialFloatingPointValues() method."); + } + } + + private TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { + if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) { + return TypeAdapters.LONG; + } + return new TypeAdapter() { + @Override public Number read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + return in.nextLong(); + } + @Override public void write(JsonWriter out, Number value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + out.value(value.toString()); + } + }; + } + + /** + * Returns the type adapter for {@code} type. + * + * @throws IllegalArgumentException if this GSON cannot serialize and + * deserialize {@code type}. + */ + @SuppressWarnings("unchecked") + public TypeAdapter getAdapter(TypeToken type) { + TypeAdapter cached = typeTokenCache.get(type); + if (cached != null) { + return (TypeAdapter) cached; + } + + Map, FutureTypeAdapter> threadCalls = calls.get(); + boolean requiresThreadLocalCleanup = false; + if (threadCalls == null) { + threadCalls = new HashMap, FutureTypeAdapter>(); + calls.set(threadCalls); + requiresThreadLocalCleanup = true; + } + + // the key and value type parameters always agree + FutureTypeAdapter ongoingCall = (FutureTypeAdapter) threadCalls.get(type); + if (ongoingCall != null) { + return ongoingCall; + } + + try { + FutureTypeAdapter call = new FutureTypeAdapter(); + threadCalls.put(type, call); + + for (TypeAdapterFactory factory : factories) { + TypeAdapter candidate = factory.create(this, type); + if (candidate != null) { + call.setDelegate(candidate); + typeTokenCache.put(type, candidate); + return candidate; + } + } + throw new IllegalArgumentException("GSON cannot handle " + type); + } finally { + threadCalls.remove(type); + + if (requiresThreadLocalCleanup) { + calls.remove(); + } + } + } + + /** + * This method is used to get an alternate type adapter for the specified type. This is used + * to access a type adapter that is overridden by a {@link TypeAdapterFactory} that you + * may have registered. This features is typically used when you want to register a type + * adapter that does a little bit of work but then delegates further processing to the Gson + * default type adapter. Here is an example: + *

Let's say we want to write a type adapter that counts the number of objects being read + * from or written to JSON. We can achieve this by writing a type adapter factory that uses + * the getDelegateAdapter method: + *

 {@code
+   *  class StatsTypeAdapterFactory implements TypeAdapterFactory {
+   *    public int numReads = 0;
+   *    public int numWrites = 0;
+   *    public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
+   *      final TypeAdapter<T> delegate = gson.getDelegateAdapter(this, type);
+   *      return new TypeAdapter<T>() {
+   *        public void write(JsonWriter out, T value) throws IOException {
+   *          ++numWrites;
+   *          delegate.write(out, value);
+   *        }
+   *        public T read(JsonReader in) throws IOException {
+   *          ++numReads;
+   *          return delegate.read(in);
+   *        }
+   *      };
+   *    }
+   *  }
+   *  } 
+ * This factory can now be used like this: + *
 {@code
+   *  StatsTypeAdapterFactory stats = new StatsTypeAdapterFactory();
+   *  Gson gson = new GsonBuilder().registerTypeAdapterFactory(stats).create();
+   *  // Call gson.toJson() and fromJson methods on objects
+   *  System.out.println("Num JSON reads" + stats.numReads);
+   *  System.out.println("Num JSON writes" + stats.numWrites);
+   *  }
+ * Note that since you can not override type adapter factories for String and Java primitive + * types, our stats factory will not count the number of String or primitives that will be + * read or written. + * @param skipPast The type adapter factory that needs to be skipped while searching for + * a matching type adapter. In most cases, you should just pass this (the type adapter + * factory from where {@link #getDelegateAdapter} method is being invoked). + * @param type Type for which the delegate adapter is being searched for. + * + * @since 2.2 + */ + public TypeAdapter getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken type) { + boolean skipPastFound = false; + + for (TypeAdapterFactory factory : factories) { + if (!skipPastFound) { + if (factory == skipPast) { + skipPastFound = true; + } + continue; + } + + TypeAdapter candidate = factory.create(this, type); + if (candidate != null) { + return candidate; + } + } + throw new IllegalArgumentException("GSON cannot serialize " + type); + } + + /** + * Returns the type adapter for {@code} type. + * + * @throws IllegalArgumentException if this GSON cannot serialize and + * deserialize {@code type}. + */ + public TypeAdapter getAdapter(Class type) { + return getAdapter(TypeToken.get(type)); + } + + /** + * This method serializes the specified object into its equivalent representation as a tree of + * {@link JsonElement}s. This method should be used when the specified object is not a generic + * type. This method uses {@link Class#getClass()} to get the type for the specified object, but + * the {@code getClass()} loses the generic type information because of the Type Erasure feature + * of Java. Note that this method works fine if the any of the object fields are of generic type, + * just the object itself should not be of a generic type. If the object is of generic type, use + * {@link #toJsonTree(Object, Type)} instead. + * + * @param src the object for which Json representation is to be created setting for Gson + * @return Json representation of {@code src}. + * @since 1.4 + */ + public JsonElement toJsonTree(Object src) { + if (src == null) { + return JsonNull.INSTANCE; + } + return toJsonTree(src, src.getClass()); + } + + /** + * This method serializes the specified object, including those of generic types, into its + * equivalent representation as a tree of {@link JsonElement}s. This method must be used if the + * specified object is a generic type. For non-generic objects, use {@link #toJsonTree(Object)} + * instead. + * + * @param src the object for which JSON representation is to be created + * @param typeOfSrc The specific genericized type of src. You can obtain + * this type by using the {@link com.google.gson.reflect.TypeToken} class. For example, + * to get the type for {@code Collection}, you should use: + *
+   * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return Json representation of {@code src} + * @since 1.4 + */ + public JsonElement toJsonTree(Object src, Type typeOfSrc) { + JsonTreeWriter writer = new JsonTreeWriter(); + toJson(src, typeOfSrc, writer); + return writer.get(); + } + + /** + * This method serializes the specified object into its equivalent Json representation. + * This method should be used when the specified object is not a generic type. This method uses + * {@link Class#getClass()} to get the type for the specified object, but the + * {@code getClass()} loses the generic type information because of the Type Erasure feature + * of Java. Note that this method works fine if the any of the object fields are of generic type, + * just the object itself should not be of a generic type. If the object is of generic type, use + * {@link #toJson(Object, Type)} instead. If you want to write out the object to a + * {@link Writer}, use {@link #toJson(Object, Appendable)} instead. + * + * @param src the object for which Json representation is to be created setting for Gson + * @return Json representation of {@code src}. + */ + public String toJson(Object src) { + if (src == null) { + return toJson(JsonNull.INSTANCE); + } + return toJson(src, src.getClass()); + } + + /** + * This method serializes the specified object, including those of generic types, into its + * equivalent Json representation. This method must be used if the specified object is a generic + * type. For non-generic objects, use {@link #toJson(Object)} instead. If you want to write out + * the object to a {@link Appendable}, use {@link #toJson(Object, Type, Appendable)} instead. + * + * @param src the object for which JSON representation is to be created + * @param typeOfSrc The specific genericized type of src. You can obtain + * this type by using the {@link com.google.gson.reflect.TypeToken} class. For example, + * to get the type for {@code Collection}, you should use: + *
+   * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return Json representation of {@code src} + */ + public String toJson(Object src, Type typeOfSrc) { + StringWriter writer = new StringWriter(); + toJson(src, typeOfSrc, writer); + return writer.toString(); + } + + /** + * This method serializes the specified object into its equivalent Json representation. + * This method should be used when the specified object is not a generic type. This method uses + * {@link Class#getClass()} to get the type for the specified object, but the + * {@code getClass()} loses the generic type information because of the Type Erasure feature + * of Java. Note that this method works fine if the any of the object fields are of generic type, + * just the object itself should not be of a generic type. If the object is of generic type, use + * {@link #toJson(Object, Type, Appendable)} instead. + * + * @param src the object for which Json representation is to be created setting for Gson + * @param writer Writer to which the Json representation needs to be written + * @throws JsonIOException if there was a problem writing to the writer + * @since 1.2 + */ + public void toJson(Object src, Appendable writer) throws JsonIOException { + if (src != null) { + toJson(src, src.getClass(), writer); + } else { + toJson(JsonNull.INSTANCE, writer); + } + } + + /** + * This method serializes the specified object, including those of generic types, into its + * equivalent Json representation. This method must be used if the specified object is a generic + * type. For non-generic objects, use {@link #toJson(Object, Appendable)} instead. + * + * @param src the object for which JSON representation is to be created + * @param typeOfSrc The specific genericized type of src. You can obtain + * this type by using the {@link com.google.gson.reflect.TypeToken} class. For example, + * to get the type for {@code Collection}, you should use: + *
+   * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @param writer Writer to which the Json representation of src needs to be written. + * @throws JsonIOException if there was a problem writing to the writer + * @since 1.2 + */ + public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOException { + try { + JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer)); + toJson(src, typeOfSrc, jsonWriter); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + + /** + * Writes the JSON representation of {@code src} of type {@code typeOfSrc} to + * {@code writer}. + * @throws JsonIOException if there was a problem writing to the writer + */ + @SuppressWarnings("unchecked") + public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOException { + TypeAdapter adapter = getAdapter(TypeToken.get(typeOfSrc)); + boolean oldLenient = writer.isLenient(); + writer.setLenient(true); + boolean oldHtmlSafe = writer.isHtmlSafe(); + writer.setHtmlSafe(htmlSafe); + boolean oldSerializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(serializeNulls); + try { + ((TypeAdapter) adapter).write(writer, src); + } catch (IOException e) { + throw new JsonIOException(e); + } finally { + writer.setLenient(oldLenient); + writer.setHtmlSafe(oldHtmlSafe); + writer.setSerializeNulls(oldSerializeNulls); + } + } + + /** + * Converts a tree of {@link JsonElement}s into its equivalent JSON representation. + * + * @param jsonElement root of a tree of {@link JsonElement}s + * @return JSON String representation of the tree + * @since 1.4 + */ + public String toJson(JsonElement jsonElement) { + StringWriter writer = new StringWriter(); + toJson(jsonElement, writer); + return writer.toString(); + } + + /** + * Writes out the equivalent JSON for a tree of {@link JsonElement}s. + * + * @param jsonElement root of a tree of {@link JsonElement}s + * @param writer Writer to which the Json representation needs to be written + * @throws JsonIOException if there was a problem writing to the writer + * @since 1.4 + */ + public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOException { + try { + JsonWriter jsonWriter = newJsonWriter(Streams.writerForAppendable(writer)); + toJson(jsonElement, jsonWriter); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Returns a new JSON writer configured for this GSON and with the non-execute + * prefix if that is configured. + */ + private JsonWriter newJsonWriter(Writer writer) throws IOException { + if (generateNonExecutableJson) { + writer.write(JSON_NON_EXECUTABLE_PREFIX); + } + JsonWriter jsonWriter = new JsonWriter(writer); + if (prettyPrinting) { + jsonWriter.setIndent(" "); + } + jsonWriter.setSerializeNulls(serializeNulls); + return jsonWriter; + } + + /** + * Writes the JSON for {@code jsonElement} to {@code writer}. + * @throws JsonIOException if there was a problem writing to the writer + */ + public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOException { + boolean oldLenient = writer.isLenient(); + writer.setLenient(true); + boolean oldHtmlSafe = writer.isHtmlSafe(); + writer.setHtmlSafe(htmlSafe); + boolean oldSerializeNulls = writer.getSerializeNulls(); + writer.setSerializeNulls(serializeNulls); + try { + Streams.write(jsonElement, writer); + } catch (IOException e) { + throw new JsonIOException(e); + } finally { + writer.setLenient(oldLenient); + writer.setHtmlSafe(oldHtmlSafe); + writer.setSerializeNulls(oldSerializeNulls); + } + } + + /** + * This method deserializes the specified Json into an object of the specified class. It is not + * suitable to use if the specified class is a generic type since it will not have the generic + * type information because of the Type Erasure feature of Java. Therefore, this method should not + * be used if the desired type is a generic type. Note that this method works fine if the any of + * the fields of the specified object are generics, just the object itself should not be a + * generic type. For the cases when the object is of generic type, invoke + * {@link #fromJson(String, Type)}. If you have the Json in a {@link Reader} instead of + * a String, use {@link #fromJson(Reader, Class)} instead. + * + * @param the type of the desired object + * @param json the string from which the object is to be deserialized + * @param classOfT the class of T + * @return an object of type T from the string + * @throws JsonSyntaxException if json is not a valid representation for an object of type + * classOfT + */ + public T fromJson(String json, Class classOfT) throws JsonSyntaxException { + Object object = fromJson(json, (Type) classOfT); + return Primitives.wrap(classOfT).cast(object); + } + + /** + * This method deserializes the specified Json into an object of the specified type. This method + * is useful if the specified object is a generic type. For non-generic objects, use + * {@link #fromJson(String, Class)} instead. If you have the Json in a {@link Reader} instead of + * a String, use {@link #fromJson(Reader, Type)} instead. + * + * @param the type of the desired object + * @param json the string from which the object is to be deserialized + * @param typeOfT The specific genericized type of src. You can obtain this type by using the + * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for + * {@code Collection}, you should use: + *
+   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return an object of type T from the string + * @throws JsonParseException if json is not a valid representation for an object of type typeOfT + * @throws JsonSyntaxException if json is not a valid representation for an object of type + */ + @SuppressWarnings("unchecked") + public T fromJson(String json, Type typeOfT) throws JsonSyntaxException { + if (json == null) { + return null; + } + StringReader reader = new StringReader(json); + T target = (T) fromJson(reader, typeOfT); + return target; + } + + /** + * This method deserializes the Json read from the specified reader into an object of the + * specified class. It is not suitable to use if the specified class is a generic type since it + * will not have the generic type information because of the Type Erasure feature of Java. + * Therefore, this method should not be used if the desired type is a generic type. Note that + * this method works fine if the any of the fields of the specified object are generics, just the + * object itself should not be a generic type. For the cases when the object is of generic type, + * invoke {@link #fromJson(Reader, Type)}. If you have the Json in a String form instead of a + * {@link Reader}, use {@link #fromJson(String, Class)} instead. + * + * @param the type of the desired object + * @param json the reader producing the Json from which the object is to be deserialized. + * @param classOfT the class of T + * @return an object of type T from the string + * @throws JsonIOException if there was a problem reading from the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + * @since 1.2 + */ + public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { + JsonReader jsonReader = new JsonReader(json); + Object object = fromJson(jsonReader, classOfT); + assertFullConsumption(object, jsonReader); + return Primitives.wrap(classOfT).cast(object); + } + + /** + * This method deserializes the Json read from the specified reader into an object of the + * specified type. This method is useful if the specified object is a generic type. For + * non-generic objects, use {@link #fromJson(Reader, Class)} instead. If you have the Json in a + * String form instead of a {@link Reader}, use {@link #fromJson(String, Type)} instead. + * + * @param the type of the desired object + * @param json the reader producing Json from which the object is to be deserialized + * @param typeOfT The specific genericized type of src. You can obtain this type by using the + * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for + * {@code Collection}, you should use: + *
+   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return an object of type T from the json + * @throws JsonIOException if there was a problem reading from the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + * @since 1.2 + */ + @SuppressWarnings("unchecked") + public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { + JsonReader jsonReader = new JsonReader(json); + T object = (T) fromJson(jsonReader, typeOfT); + assertFullConsumption(object, jsonReader); + return object; + } + + private static void assertFullConsumption(Object obj, JsonReader reader) { + try { + if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonIOException("JSON document was not fully consumed."); + } + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + + /** + * Reads the next JSON value from {@code reader} and convert it to an object + * of type {@code typeOfT}. + * Since Type is not parameterized by T, this method is type unsafe and should be used carefully + * + * @throws JsonIOException if there was a problem writing to the Reader + * @throws JsonSyntaxException if json is not a valid representation for an object of type + */ + @SuppressWarnings("unchecked") + public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException { + boolean isEmpty = true; + boolean oldLenient = reader.isLenient(); + reader.setLenient(true); + try { + reader.peek(); + isEmpty = false; + TypeToken typeToken = (TypeToken) TypeToken.get(typeOfT); + TypeAdapter typeAdapter = getAdapter(typeToken); + T object = typeAdapter.read(reader); + return object; + } catch (EOFException e) { + /* + * For compatibility with JSON 1.5 and earlier, we return null for empty + * documents instead of throwing. + */ + if (isEmpty) { + return null; + } + throw new JsonSyntaxException(e); + } catch (IllegalStateException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + // TODO(inder): Figure out whether it is indeed right to rethrow this as JsonSyntaxException + throw new JsonSyntaxException(e); + } finally { + reader.setLenient(oldLenient); + } + } + + /** + * This method deserializes the Json read from the specified parse tree into an object of the + * specified type. It is not suitable to use if the specified class is a generic type since it + * will not have the generic type information because of the Type Erasure feature of Java. + * Therefore, this method should not be used if the desired type is a generic type. Note that + * this method works fine if the any of the fields of the specified object are generics, just the + * object itself should not be a generic type. For the cases when the object is of generic type, + * invoke {@link #fromJson(JsonElement, Type)}. + * @param the type of the desired object + * @param json the root of the parse tree of {@link JsonElement}s from which the object is to + * be deserialized + * @param classOfT The class of T + * @return an object of type T from the json + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * @since 1.3 + */ + public T fromJson(JsonElement json, Class classOfT) throws JsonSyntaxException { + Object object = fromJson(json, (Type) classOfT); + return Primitives.wrap(classOfT).cast(object); + } + + /** + * This method deserializes the Json read from the specified parse tree into an object of the + * specified type. This method is useful if the specified object is a generic type. For + * non-generic objects, use {@link #fromJson(JsonElement, Class)} instead. + * + * @param the type of the desired object + * @param json the root of the parse tree of {@link JsonElement}s from which the object is to + * be deserialized + * @param typeOfT The specific genericized type of src. You can obtain this type by using the + * {@link com.google.gson.reflect.TypeToken} class. For example, to get the type for + * {@code Collection}, you should use: + *
+   * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
+   * 
+ * @return an object of type T from the json + * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT + * @since 1.3 + */ + @SuppressWarnings("unchecked") + public T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException { + if (json == null) { + return null; + } + return (T) fromJson(new JsonTreeReader(json), typeOfT); + } + + static class FutureTypeAdapter extends TypeAdapter { + private TypeAdapter delegate; + + public void setDelegate(TypeAdapter typeAdapter) { + if (delegate != null) { + throw new AssertionError(); + } + delegate = typeAdapter; + } + + @Override public T read(JsonReader in) throws IOException { + if (delegate == null) { + throw new IllegalStateException(); + } + return delegate.read(in); + } + + @Override public void write(JsonWriter out, T value) throws IOException { + if (delegate == null) { + throw new IllegalStateException(); + } + delegate.write(out, value); + } + } + + @Override + public String toString() { + return new StringBuilder("{serializeNulls:") + .append(serializeNulls) + .append("factories:").append(factories) + .append(",instanceCreators:").append(constructorConstructor) + .append("}") + .toString(); + } +} diff --git a/src/com/google/gson/GsonBuilder.java b/src/com/google/gson/GsonBuilder.java new file mode 100644 index 0000000..e6c0b8c --- /dev/null +++ b/src/com/google/gson/GsonBuilder.java @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.lang.reflect.Type; +import java.sql.Timestamp; +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.google.gson.internal.$Gson$Preconditions; +import com.google.gson.internal.Excluder; +import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.reflect.TypeToken; + +/** + *

Use this builder to construct a {@link Gson} instance when you need to set configuration + * options other than the default. For {@link Gson} with default configuration, it is simpler to + * use {@code new Gson()}. {@code GsonBuilder} is best used by creating it, and then invoking its + * various configuration methods, and finally calling create.

+ * + *

The following is an example shows how to use the {@code GsonBuilder} to construct a Gson + * instance: + * + *

+ * Gson gson = new GsonBuilder()
+ *     .registerTypeAdapter(Id.class, new IdTypeAdapter())
+ *     .enableComplexMapKeySerialization()
+ *     .serializeNulls()
+ *     .setDateFormat(DateFormat.LONG)
+ *     .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
+ *     .setPrettyPrinting()
+ *     .setVersion(1.0)
+ *     .create();
+ * 

+ * + *

NOTES: + *

    + *
  • the order of invocation of configuration methods does not matter.
  • + *
  • The default serialization of {@link Date} and its subclasses in Gson does + * not contain time-zone information. So, if you are using date/time instances, + * use {@code GsonBuilder} and its {@code setDateFormat} methods.
  • + *
+ *

+ * + * @author Inderjeet Singh + * @author Joel Leitch + * @author Jesse Wilson + */ +public final class GsonBuilder { + private Excluder excluder = Excluder.DEFAULT; + private LongSerializationPolicy longSerializationPolicy = LongSerializationPolicy.DEFAULT; + private FieldNamingStrategy fieldNamingPolicy = FieldNamingPolicy.IDENTITY; + private final Map> instanceCreators + = new HashMap>(); + private final List factories = new ArrayList(); + /** tree-style hierarchy factories. These come after factories for backwards compatibility. */ + private final List hierarchyFactories = new ArrayList(); + private boolean serializeNulls; + 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. + * + *

This registers the type specified and no other types: you must manually register related + * types! For example, applications registering {@code boolean.class} should also register {@code + * Boolean.class}. + * + * @param type the type definition for the type adapter being registered + * @param typeAdapter This object must implement at least one of the {@link TypeAdapter}, + * {@link InstanceCreator}, {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public GsonBuilder registerTypeAdapter(Type type, Object typeAdapter) { + $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer + || typeAdapter instanceof JsonDeserializer + || typeAdapter instanceof InstanceCreator + || typeAdapter instanceof TypeAdapter); + if (typeAdapter instanceof InstanceCreator) { + instanceCreators.put(type, (InstanceCreator) typeAdapter); + } + if (typeAdapter instanceof JsonSerializer || typeAdapter instanceof JsonDeserializer) { + TypeToken typeToken = TypeToken.get(type); + factories.add(TreeTypeAdapter.newFactoryWithMatchRawType(typeToken, typeAdapter)); + } + if (typeAdapter instanceof TypeAdapter) { + factories.add(TypeAdapters.newFactory(TypeToken.get(type), (TypeAdapter)typeAdapter)); + } + return this; + } + + /** + * Register a factory for type adapters. Registering a factory is useful when the type + * adapter needs to be configured based on the type of the field being processed. Gson + * is designed to handle a large number of factories, so you should consider registering + * them to be at par with registering an individual type adapter. + * + * @since 2.1 + */ + public GsonBuilder registerTypeAdapterFactory(TypeAdapterFactory factory) { + factories.add(factory); + return this; + } + + /** + * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy. + * This method combines the registration of a {@link TypeAdapter}, {@link JsonSerializer} and + * a {@link JsonDeserializer}. If a type adapter was previously registered for the specified + * type hierarchy, it is overridden. If a type adapter is registered for a specific type in + * the type hierarchy, it will be invoked instead of the one registered for the type hierarchy. + * + * @param baseType the class definition for the type adapter being registered for the base class + * or interface + * @param typeAdapter This object must implement at least one of {@link TypeAdapter}, + * {@link JsonSerializer} or {@link JsonDeserializer} interfaces. + * @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern + * @since 1.7 + */ + @SuppressWarnings({"unchecked", "rawtypes"}) + public GsonBuilder registerTypeHierarchyAdapter(Class baseType, Object typeAdapter) { + $Gson$Preconditions.checkArgument(typeAdapter instanceof JsonSerializer + || typeAdapter instanceof JsonDeserializer + || typeAdapter instanceof TypeAdapter); + if (typeAdapter instanceof JsonDeserializer || typeAdapter instanceof JsonSerializer) { + hierarchyFactories.add(0, + TreeTypeAdapter.newTypeHierarchyFactory(baseType, typeAdapter)); + } + if (typeAdapter instanceof TypeAdapter) { + factories.add(TypeAdapters.newTypeHierarchyFactory(baseType, (TypeAdapter)typeAdapter)); + } + return this; + } + + /** + * Section 2.4 of 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..885dc20 --- /dev/null +++ b/src/com/google/gson/JsonArray.java @@ -0,0 +1,291 @@ +/* + * 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(); + } + + @Override + JsonArray deepCopy() { + JsonArray result = new JsonArray(); + for (JsonElement element : elements) { + result.add(element.deepCopy()); + } + return result; + } + + /** + * 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..0f9f964 --- /dev/null +++ b/src/com/google/gson/JsonElement.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.math.BigInteger; + +/** + * A class representing an element of Json. It could either be a {@link JsonObject}, a + * {@link JsonArray}, a {@link JsonPrimitive} or a {@link JsonNull}. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public abstract class JsonElement { + /** + * Returns a deep copy of this element. Immutable elements like primitives + * and nulls are not copied. + */ + abstract JsonElement deepCopy(); + + /** + * provides check for verifying if this element is an array or not. + * + * @return true if this element is of type {@link JsonArray}, false otherwise. + */ + public boolean isJsonArray() { + return this instanceof JsonArray; + } + + /** + * provides check for verifying if this element is a Json object or not. + * + * @return true if this element is of type {@link JsonObject}, false otherwise. + */ + public boolean isJsonObject() { + return this instanceof JsonObject; + } + + /** + * provides check for verifying if this element is a primitive or not. + * + * @return true if this element is of type {@link JsonPrimitive}, false otherwise. + */ + public boolean isJsonPrimitive() { + return this instanceof JsonPrimitive; + } + + /** + * provides check for verifying if this element represents a null value or not. + * + * @return true if this element is of type {@link JsonNull}, false otherwise. + * @since 1.2 + */ + public boolean isJsonNull() { + return this instanceof JsonNull; + } + + /** + * convenience method to get this element as a {@link JsonObject}. If the element is of some + * other type, a {@link 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 100755 index 0000000..5688336 --- /dev/null +++ b/src/com/google/gson/JsonNull.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +/** + * A class representing a Json {@code null} value. + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.2 + */ +public final class JsonNull extends JsonElement { + /** + * singleton for JsonNull + * + * @since 1.8 + */ + public static final JsonNull INSTANCE = new JsonNull(); + + /** + * Creates a new JsonNull object. + * Deprecated since Gson version 1.8. Use {@link #INSTANCE} instead + */ + @Deprecated + public JsonNull() { + // Do nothing + } + + @Override + JsonNull deepCopy() { + return INSTANCE; + } + + /** + * All instances of JsonNull have the same hash code since they are indistinguishable + */ + @Override + public int hashCode() { + return JsonNull.class.hashCode(); + } + + /** + * All instances of JsonNull are the same + */ + @Override + public boolean equals(Object other) { + return this == other || other instanceof JsonNull; + } +} diff --git a/src/com/google/gson/JsonObject.java b/src/com/google/gson/JsonObject.java new file mode 100644 index 0000000..78c7a17 --- /dev/null +++ b/src/com/google/gson/JsonObject.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import com.google.gson.internal.LinkedTreeMap; + +import java.util.Map; +import java.util.Set; + +/** + * A class representing an object type in Json. An object consists of name-value pairs where names + * are strings, and values are any other type of {@link JsonElement}. This allows for a creating a + * tree of JsonElements. The member elements of this object are maintained in order they were added. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonObject extends JsonElement { + private final LinkedTreeMap members = + new LinkedTreeMap(); + + @Override + JsonObject deepCopy() { + JsonObject result = new JsonObject(); + for (Map.Entry entry : members.entrySet()) { + result.add(entry.getKey(), entry.getValue().deepCopy()); + } + return result; + } + + /** + * Adds a member, which is a name-value pair, to self. The name must be a String, but the value + * can be an arbitrary JsonElement, thereby allowing you to build a full tree of JsonElements + * rooted at this node. + * + * @param property name of the member. + * @param value the member object. + */ + public void add(String property, JsonElement value) { + if (value == null) { + value = JsonNull.INSTANCE; + } + members.put(property, value); + } + + /** + * Removes the {@code property} from this {@link JsonObject}. + * + * @param property name of the member that should be removed. + * @return the {@link JsonElement} object that is being removed. + * @since 1.3 + */ + public JsonElement remove(String property) { + return members.remove(property); + } + + /** + * Convenience method to add a primitive member. The specified value is converted to a + * JsonPrimitive of String. + * + * @param property name of the member. + * @param value the string value associated with the member. + */ + public void addProperty(String property, String value) { + add(property, createJsonElement(value)); + } + + /** + * Convenience method to add a primitive member. The specified value is converted to a + * JsonPrimitive of Number. + * + * @param property name of the member. + * @param value the number value associated with the member. + */ + public void addProperty(String property, Number value) { + add(property, createJsonElement(value)); + } + + /** + * Convenience method to add a boolean member. The specified value is converted to a + * JsonPrimitive of Boolean. + * + * @param property name of the member. + * @param value the number value associated with the member. + */ + public void addProperty(String property, Boolean value) { + add(property, createJsonElement(value)); + } + + /** + * Convenience method to add a char member. The specified value is converted to a + * JsonPrimitive of Character. + * + * @param property name of the member. + * @param value the number value associated with the member. + */ + public void addProperty(String property, Character value) { + add(property, createJsonElement(value)); + } + + /** + * Creates the proper {@link JsonElement} object from the given {@code value} object. + * + * @param value the object to generate the {@link JsonElement} for + * @return a {@link JsonPrimitive} if the {@code value} is not null, otherwise a {@link JsonNull} + */ + private JsonElement createJsonElement(Object value) { + return value == null ? JsonNull.INSTANCE : new JsonPrimitive(value); + } + + /** + * Returns a set of members of this object. The set is ordered, and the order is in which the + * elements were added. + * + * @return a set of members of this object. + */ + public Set> entrySet() { + return members.entrySet(); + } + + /** + * Convenience method to check if a member with the specified name is present in this object. + * + * @param memberName name of the member that is being checked for presence. + * @return true if there is a member with the specified name, false otherwise. + */ + public boolean has(String memberName) { + return members.containsKey(memberName); + } + + /** + * Returns the member with the specified name. + * + * @param memberName name of the member that is being requested. + * @return the member matching the name. Null if no such member exists. + */ + public JsonElement get(String memberName) { + return members.get(memberName); + } + + /** + * Convenience method to get the specified member as a JsonPrimitive element. + * + * @param memberName name of the member being requested. + * @return the JsonPrimitive corresponding to the specified member. + */ + public JsonPrimitive getAsJsonPrimitive(String memberName) { + return (JsonPrimitive) members.get(memberName); + } + + /** + * Convenience method to get the specified member as a JsonArray. + * + * @param memberName name of the member being requested. + * @return the JsonArray corresponding to the specified member. + */ + public JsonArray getAsJsonArray(String memberName) { + return (JsonArray) members.get(memberName); + } + + /** + * Convenience method to get the specified member as a JsonObject. + * + * @param memberName name of the member being requested. + * @return the JsonObject corresponding to the specified member. + */ + public JsonObject getAsJsonObject(String memberName) { + return (JsonObject) members.get(memberName); + } + + @Override + public boolean equals(Object o) { + return (o == this) || (o instanceof JsonObject + && ((JsonObject) o).members.equals(members)); + } + + @Override + public int hashCode() { + return members.hashCode(); + } +} diff --git a/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 100755 index 0000000..a8ae337 --- /dev/null +++ b/src/com/google/gson/JsonParser.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.gson; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.MalformedJsonException; + +/** + * A parser to parse Json into a parse tree of {@link JsonElement}s + * + * @author Inderjeet Singh + * @author Joel Leitch + * @since 1.3 + */ +public final class JsonParser { + + /** + * Parses the specified JSON string into a parse tree + * + * @param json JSON text + * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON + * @throws JsonParseException if the specified text is not valid JSON + * @since 1.3 + */ + public JsonElement parse(String json) throws JsonSyntaxException { + return parse(new StringReader(json)); + } + + /** + * Parses the specified JSON string into a parse tree + * + * @param json JSON text + * @return a parse tree of {@link JsonElement}s corresponding to the specified JSON + * @throws JsonParseException if the specified text is not valid JSON + * @since 1.3 + */ + public JsonElement parse(Reader json) throws JsonIOException, JsonSyntaxException { + try { + JsonReader jsonReader = new JsonReader(json); + JsonElement element = parse(jsonReader); + if (!element.isJsonNull() && jsonReader.peek() != JsonToken.END_DOCUMENT) { + throw new JsonSyntaxException("Did not consume the entire document."); + } + return element; + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } + } + + /** + * Returns the next value from the JSON stream as a parse tree. + * + * @throws JsonParseException if there is an IOException or if the specified + * text is not valid JSON + * @since 1.6 + */ + public JsonElement parse(JsonReader json) throws JsonIOException, JsonSyntaxException { + boolean lenient = json.isLenient(); + json.setLenient(true); + try { + return Streams.parse(json); + } catch (StackOverflowError e) { + throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e); + } catch (OutOfMemoryError e) { + throw new JsonParseException("Failed parsing JSON source: " + json + " to Json", e); + } finally { + json.setLenient(lenient); + } + } +} diff --git a/src/com/google/gson/JsonPrimitive.java b/src/com/google/gson/JsonPrimitive.java new file mode 100644 index 0000000..e2443d4 --- /dev/null +++ b/src/com/google/gson/JsonPrimitive.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import java.math.BigDecimal; +import java.math.BigInteger; + +import com.google.gson.internal.$Gson$Preconditions; +import com.google.gson.internal.LazilyParsedNumber; + +/** + * A class representing a Json primitive value. A primitive value + * is either a String, a Java primitive, or a Java primitive + * wrapper type. + * + * @author Inderjeet Singh + * @author Joel Leitch + */ +public final class JsonPrimitive extends JsonElement { + + private static final Class[] PRIMITIVE_TYPES = { int.class, long.class, short.class, + float.class, double.class, byte.class, boolean.class, char.class, Integer.class, Long.class, + Short.class, Float.class, Double.class, Byte.class, Boolean.class, Character.class }; + + private Object value; + + /** + * Create a primitive containing a boolean value. + * + * @param bool the value to create the primitive with. + */ + public JsonPrimitive(Boolean bool) { + setValue(bool); + } + + /** + * Create a primitive containing a {@link Number}. + * + * @param number the value to create the primitive with. + */ + public JsonPrimitive(Number number) { + setValue(number); + } + + /** + * Create a primitive containing a String value. + * + * @param string the value to create the primitive with. + */ + public JsonPrimitive(String string) { + setValue(string); + } + + /** + * Create a primitive containing a character. The character is turned into a one character String + * since Json only supports String. + * + * @param c the value to create the primitive with. + */ + public JsonPrimitive(Character c) { + setValue(c); + } + + /** + * Create a primitive using the specified Object. It must be an instance of {@link Number}, a + * Java primitive type, or a String. + * + * @param primitive the value to create the primitive with. + */ + JsonPrimitive(Object primitive) { + setValue(primitive); + } + + @Override + JsonPrimitive deepCopy() { + return this; + } + + void setValue(Object primitive) { + if (primitive instanceof Character) { + // convert characters to strings since in JSON, characters are represented as a single + // character string + char c = ((Character) primitive).charValue(); + this.value = String.valueOf(c); + } else { + $Gson$Preconditions.checkArgument(primitive instanceof Number + || isPrimitiveOrString(primitive)); + this.value = primitive; + } + } + + /** + * Check whether this primitive contains a boolean value. + * + * @return true if this primitive contains a boolean value, false otherwise. + */ + public boolean isBoolean() { + return value instanceof Boolean; + } + + /** + * convenience method to get this element as a {@link Boolean}. + * + * @return get this element as a {@link Boolean}. + */ + @Override + Boolean getAsBooleanWrapper() { + return (Boolean) value; + } + + /** + * convenience method to get this element as a boolean value. + * + * @return get this element as a primitive boolean value. + */ + @Override + public boolean getAsBoolean() { + if (isBoolean()) { + return getAsBooleanWrapper().booleanValue(); + } else { + // Check to see if the value as a String is "true" in any case. + return Boolean.parseBoolean(getAsString()); + } + } + + /** + * Check whether this primitive contains a Number. + * + * @return true if this primitive contains a Number, false otherwise. + */ + public boolean isNumber() { + return value instanceof Number; + } + + /** + * convenience method to get this element as a Number. + * + * @return get this element as a Number. + * @throws NumberFormatException if the value contained is not a valid Number. + */ + @Override + public Number getAsNumber() { + return value instanceof String ? new LazilyParsedNumber((String) value) : (Number) value; + } + + /** + * Check whether this primitive contains a String value. + * + * @return true if this primitive contains a String value, false otherwise. + */ + public boolean isString() { + return value instanceof String; + } + + /** + * convenience method to get this element as a String. + * + * @return get this element as a String. + */ + @Override + public String getAsString() { + if (isNumber()) { + return getAsNumber().toString(); + } else if (isBoolean()) { + return getAsBooleanWrapper().toString(); + } else { + return (String) value; + } + } + + /** + * convenience method to get this element as a primitive double. + * + * @return get this element as a primitive double. + * @throws NumberFormatException if the value contained is not a valid double. + */ + @Override + public double getAsDouble() { + return isNumber() ? getAsNumber().doubleValue() : Double.parseDouble(getAsString()); + } + + /** + * convenience method to get this element as a {@link BigDecimal}. + * + * @return get this element as a {@link BigDecimal}. + * @throws NumberFormatException if the value contained is not a valid {@link BigDecimal}. + */ + @Override + public BigDecimal getAsBigDecimal() { + return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString()); + } + + /** + * convenience method to get this element as a {@link BigInteger}. + * + * @return get this element as a {@link BigInteger}. + * @throws NumberFormatException if the value contained is not a valid {@link BigInteger}. + */ + @Override + public BigInteger getAsBigInteger() { + return value instanceof BigInteger ? + (BigInteger) value : new BigInteger(value.toString()); + } + + /** + * convenience method to get this element as a float. + * + * @return get this element as a float. + * @throws NumberFormatException if the value contained is not a valid float. + */ + @Override + public float getAsFloat() { + return isNumber() ? getAsNumber().floatValue() : Float.parseFloat(getAsString()); + } + + /** + * convenience method to get this element as a primitive long. + * + * @return get this element as a primitive long. + * @throws NumberFormatException if the value contained is not a valid long. + */ + @Override + public long getAsLong() { + return isNumber() ? getAsNumber().longValue() : Long.parseLong(getAsString()); + } + + /** + * convenience method to get this element as a primitive short. + * + * @return get this element as a primitive short. + * @throws NumberFormatException if the value contained is not a valid short value. + */ + @Override + public short getAsShort() { + return isNumber() ? getAsNumber().shortValue() : Short.parseShort(getAsString()); + } + + /** + * convenience method to get this element as a primitive integer. + * + * @return get this element as a primitive integer. + * @throws NumberFormatException if the value contained is not a valid integer. + */ + @Override + public int getAsInt() { + return isNumber() ? getAsNumber().intValue() : Integer.parseInt(getAsString()); + } + + @Override + public byte getAsByte() { + return isNumber() ? getAsNumber().byteValue() : Byte.parseByte(getAsString()); + } + + @Override + public char getAsCharacter() { + return getAsString().charAt(0); + } + + private static boolean isPrimitiveOrString(Object target) { + if (target instanceof String) { + return true; + } + + Class classOfPrimitive = target.getClass(); + for (Class standardPrimitive : PRIMITIVE_TYPES) { + if (standardPrimitive.isAssignableFrom(classOfPrimitive)) { + return true; + } + } + return false; + } + + @Override + public int hashCode() { + if (value == null) { + return 31; + } + // Using recommended hashing algorithm from Effective Java for longs and doubles + if (isIntegral(this)) { + long value = getAsNumber().longValue(); + return (int) (value ^ (value >>> 32)); + } + if (value instanceof Number) { + long value = Double.doubleToLongBits(getAsNumber().doubleValue()); + return (int) (value ^ (value >>> 32)); + } + return value.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + JsonPrimitive other = (JsonPrimitive)obj; + if (value == null) { + return other.value == null; + } + if (isIntegral(this) && isIntegral(other)) { + return getAsNumber().longValue() == other.getAsNumber().longValue(); + } + if (value instanceof Number && other.value instanceof Number) { + double a = getAsNumber().doubleValue(); + // Java standard types other than double return true for two NaN. So, need + // special handling for double. + double b = other.getAsNumber().doubleValue(); + return a == b || (Double.isNaN(a) && Double.isNaN(b)); + } + return value.equals(other.value); + } + + /** + * Returns true if the specified number is an integral type + * (Long, Integer, Short, Byte, BigInteger) + */ + private static boolean isIntegral(JsonPrimitive primitive) { + if (primitive.value instanceof Number) { + Number number = (Number) primitive.value; + return number instanceof BigInteger || number instanceof Long || number instanceof Integer + || number instanceof Short || number instanceof Byte; + } + return false; + } +} diff --git a/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..a05c1fe --- /dev/null +++ b/src/com/google/gson/TreeTypeAdapter.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson; + +import com.google.gson.internal.$Gson$Preconditions; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; + +/** + * Adapts a Gson 1.x tree-style adapter as a streaming TypeAdapter. Since the + * tree adapter may be serialization-only or deserialization-only, this class + * has a facility to lookup a delegate type adapter on demand. + */ +final class TreeTypeAdapter 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 = gson.getDelegateAdapter(skipPast, typeToken)); + } + + /** + * Returns a new factory that will match each type against {@code exactType}. + */ + public static TypeAdapterFactory newFactory(TypeToken exactType, Object typeAdapter) { + return new SingleTypeFactory(typeAdapter, exactType, false, null); + } + + /** + * Returns a new factory that will match each type and its raw type against + * {@code exactType}. + */ + public static TypeAdapterFactory newFactoryWithMatchRawType( + TypeToken exactType, Object typeAdapter) { + // only bother matching raw types if exact type is a raw type + boolean matchRawType = exactType.getType() == exactType.getRawType(); + return new SingleTypeFactory(typeAdapter, exactType, matchRawType, null); + } + + /** + * Returns a new factory that will match each type's raw type for assignability + * to {@code hierarchyType}. + */ + public static TypeAdapterFactory newTypeHierarchyFactory( + Class hierarchyType, Object typeAdapter) { + return new SingleTypeFactory(typeAdapter, null, false, hierarchyType); + } + + private static class SingleTypeFactory implements TypeAdapterFactory { + private final TypeToken exactType; + private final boolean matchRawType; + private final Class hierarchyType; + private final JsonSerializer serializer; + private final JsonDeserializer deserializer; + + private SingleTypeFactory(Object typeAdapter, TypeToken exactType, boolean matchRawType, + Class hierarchyType) { + serializer = typeAdapter instanceof JsonSerializer + ? (JsonSerializer) typeAdapter + : null; + deserializer = typeAdapter instanceof JsonDeserializer + ? (JsonDeserializer) typeAdapter + : null; + $Gson$Preconditions.checkArgument(serializer != null || deserializer != null); + this.exactType = exactType; + this.matchRawType = matchRawType; + this.hierarchyType = hierarchyType; + } + + @SuppressWarnings("unchecked") // guarded by typeToken.equals() call + public 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..58dd642 --- /dev/null +++ b/src/com/google/gson/TypeAdapter.java @@ -0,0 +1,286 @@ +/* + * 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. + * @since 2.2 + */ + public final void toJson(Writer out, T value) throws IOException { + JsonWriter writer = new JsonWriter(out); + write(writer, value); + } + + /** + * This wrapper method is used to make a type adapter null tolerant. In general, a + * type adapter is required to handle nulls in write and read methods. Here is how this + * is typically done:
+ *

   {@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. + * @since 2.2 + */ + 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}. + * @since 2.2 + */ + public final JsonElement toJsonTree(T value) { + try { + JsonTreeWriter jsonWriter = new JsonTreeWriter(); + write(jsonWriter, value); + return jsonWriter.get(); + } catch (IOException e) { + throw new JsonIOException(e); + } + } + + /** + * Reads one JSON value (an array, object, string, number, boolean or null) + * and converts it to a Java object. Returns the converted object. + * + * @return the converted Java object. May be null. + */ + public abstract T read(JsonReader in) throws IOException; + + /** + * Converts the JSON document in {@code in} to a Java object. Unlike Gson's + * similar {@link Gson#fromJson(java.io.Reader, Class) fromJson} method, this + * read is strict. Create a {@link JsonReader#setLenient(boolean) lenient} + * {@code JsonReader} and call {@link #read(JsonReader)} for lenient reading. + * + * @return the converted Java object. May be null. + * @since 2.2 + */ + public final T fromJson(Reader in) throws IOException { + JsonReader reader = new JsonReader(in); + return read(reader); + } + + /** + * Converts the JSON document in {@code json} to a Java object. Unlike Gson's + * similar {@link Gson#fromJson(String, Class) fromJson} method, this read is + * strict. Create a {@link JsonReader#setLenient(boolean) lenient} {@code + * JsonReader} and call {@link #read(JsonReader)} for lenient reading. + * + * @return the converted Java object. May be null. + * @since 2.2 + */ + public final T fromJson(String json) throws IOException { + return fromJson(new StringReader(json)); + } + + /** + * Converts {@code jsonTree} to a Java object. + * + * @param jsonTree the Java object to convert. May be {@link JsonNull}. + * @since 2.2 + */ + public final T fromJsonTree(JsonElement jsonTree) { + try { + JsonReader jsonReader = new JsonTreeReader(jsonTree); + return read(jsonReader); + } catch (IOException e) { + throw new JsonIOException(e); + } + } +} diff --git a/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..a0a3e9d --- /dev/null +++ b/src/com/google/gson/internal/ConstructorConstructor.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal; + +import com.google.gson.InstanceCreator; +import com.google.gson.JsonIOException; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.SortedMap; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Returns a function that can construct an instance of a requested type. + */ +public final class ConstructorConstructor { + private final Map> instanceCreators; + + public ConstructorConstructor(Map> instanceCreators) { + this.instanceCreators = instanceCreators; + } + + public ObjectConstructor get(TypeToken typeToken) { + final Type type = typeToken.getType(); + final Class rawType = typeToken.getRawType(); + + // first try an instance creator + + @SuppressWarnings("unchecked") // types must agree + final InstanceCreator typeCreator = (InstanceCreator) instanceCreators.get(type); + if (typeCreator != null) { + return new ObjectConstructor() { + public T construct() { + return typeCreator.createInstance(type); + } + }; + } + + // Next try raw type match for instance creators + @SuppressWarnings("unchecked") // types must agree + final InstanceCreator rawTypeCreator = + (InstanceCreator) instanceCreators.get(rawType); + if (rawTypeCreator != null) { + return new ObjectConstructor() { + public T construct() { + return rawTypeCreator.createInstance(type); + } + }; + } + + ObjectConstructor defaultConstructor = newDefaultConstructor(rawType); + if (defaultConstructor != null) { + return defaultConstructor; + } + + ObjectConstructor defaultImplementation = newDefaultImplementationConstructor(type, rawType); + if (defaultImplementation != null) { + return defaultImplementation; + } + + // finally try unsafe + return newUnsafeAllocator(type, rawType); + } + + private ObjectConstructor newDefaultConstructor(Class rawType) { + try { + final Constructor 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( + final Type type, Class rawType) { + if (Collection.class.isAssignableFrom(rawType)) { + if (SortedSet.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + public T construct() { + return (T) new TreeSet(); + } + }; + } else if (EnumSet.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + @SuppressWarnings("rawtypes") + public T construct() { + if (type instanceof ParameterizedType) { + Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (elementType instanceof Class) { + return (T) EnumSet.noneOf((Class)elementType); + } else { + throw new JsonIOException("Invalid EnumSet type: " + type.toString()); + } + } else { + throw new JsonIOException("Invalid EnumSet type: " + type.toString()); + } + } + }; + } else if (Set.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + 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)) { + if (SortedMap.class.isAssignableFrom(rawType)) { + return new ObjectConstructor() { + public T construct() { + return (T) new TreeMap(); + } + }; + } else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom( + TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) { + return new ObjectConstructor() { + public T construct() { + return (T) new LinkedHashMap(); + } + }; + } else { + return new ObjectConstructor() { + public T construct() { + return (T) new LinkedTreeMap(); + } + }; + } + } + + return null; + } + + private ObjectConstructor newUnsafeAllocator( + final Type type, final Class 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..1c71e3e --- /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 = gson.getDelegateAdapter(Excluder.this, type)); + } + }; + } + + public boolean excludeField(Field field, boolean serialize) { + if ((modifiers & field.getModifiers()) != 0) { + return true; + } + + if (version != Excluder.IGNORE_VERSIONS + && !isValidVersion(field.getAnnotation(Since.class), field.getAnnotation(Until.class))) { + return true; + } + + if (field.isSynthetic()) { + return true; + } + + if (requireExpose) { + Expose annotation = field.getAnnotation(Expose.class); + if (annotation == null || (serialize ? !annotation.serialize() : !annotation.deserialize())) { + return true; + } + } + + if (!serializeInnerClasses && isInnerClass(field.getType())) { + return true; + } + + if (isAnonymousOrLocal(field.getType())) { + return true; + } + + List 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/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..37ee503 --- /dev/null +++ b/src/com/google/gson/internal/LazilyParsedNumber.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.gson.internal; + +import java.io.ObjectStreamException; +import java.math.BigDecimal; + +/** + * This class holds a number value that is lazily converted to a specific number type + * + * @author Inderjeet Singh + */ +public final class LazilyParsedNumber extends Number { + private final String value; + + public LazilyParsedNumber(String value) { + this.value = value; + } + + @Override + public int intValue() { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + try { + return (int) Long.parseLong(value); + } catch (NumberFormatException nfe) { + return new BigDecimal(value).intValue(); + } + } + } + + @Override + public long longValue() { + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + return new BigDecimal(value).longValue(); + } + } + + @Override + public float floatValue() { + return Float.parseFloat(value); + } + + @Override + public double doubleValue() { + return Double.parseDouble(value); + } + + @Override + public String toString() { + return value; + } + + /** + * If somebody is unlucky enough to have to serialize one of these, serialize + * it as a BigDecimal so that they won't need Gson on the other side to + * deserialize it. + */ + private Object writeReplace() throws ObjectStreamException { + return new BigDecimal(value); + } +} \ No newline at end of file diff --git a/src/com/google/gson/internal/LinkedTreeMap.java b/src/com/google/gson/internal/LinkedTreeMap.java new file mode 100644 index 0000000..578795a --- /dev/null +++ b/src/com/google/gson/internal/LinkedTreeMap.java @@ -0,0 +1,627 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal; + +import java.io.ObjectStreamException; +import java.io.Serializable; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.Comparator; +import java.util.ConcurrentModificationException; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A map of comparable keys to values. Unlike {@code TreeMap}, this class uses + * insertion order for iteration order. Comparison order is only used as an + * optimization for efficient insertion and removal. + * + *

This implementation was derived from Android 4.1's TreeMap class. + */ +public final class LinkedTreeMap extends AbstractMap implements Serializable { + @SuppressWarnings({ "unchecked", "rawtypes" }) // to avoid Comparable>> + private static final Comparator NATURAL_ORDER = new Comparator() { + public int compare(Comparable a, Comparable b) { + return a.compareTo(b); + } + }; + + Comparator comparator; + Node root; + int size = 0; + int modCount = 0; + + // Used to preserve iteration order + final Node header = new Node(); + + /** + * Create a natural order, empty tree map whose keys must be mutually + * comparable and non-null. + */ + @SuppressWarnings("unchecked") // unsafe! this assumes K is comparable + public LinkedTreeMap() { + this((Comparator) NATURAL_ORDER); + } + + /** + * Create a tree map ordered by {@code comparator}. This map's keys may only + * be null if {@code comparator} permits. + * + * @param comparator the comparator to order elements with, or {@code null} to + * use the natural ordering. + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) // unsafe! if comparator is null, this assumes K is comparable + public LinkedTreeMap(Comparator comparator) { + this.comparator = comparator != null + ? comparator + : (Comparator) NATURAL_ORDER; + } + + @Override public int size() { + return size; + } + + @Override public V get(Object key) { + Node node = findByObject(key); + return node != null ? node.value : null; + } + + @Override public boolean containsKey(Object key) { + return findByObject(key) != null; + } + + @Override public V put(K key, V value) { + if (key == null) { + throw new NullPointerException("key == null"); + } + Node created = find(key, true); + V result = created.value; + created.value = value; + return result; + } + + @Override public void clear() { + root = null; + size = 0; + modCount++; + + // Clear iteration order + Node header = this.header; + header.next = header.prev = header; + } + + @Override public V remove(Object key) { + Node node = removeInternalByKey(key); + return node != null ? node.value : null; + } + + /** + * Returns the node at or adjacent to the given key, creating it if requested. + * + * @throws ClassCastException if {@code key} and the tree's keys aren't + * mutually comparable. + */ + Node find(K key, boolean create) { + Comparator comparator = this.comparator; + Node nearest = root; + int comparison = 0; + + if (nearest != null) { + // Micro-optimization: avoid polymorphic calls to Comparator.compare(). + @SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble. + Comparable comparableKey = (comparator == NATURAL_ORDER) + ? (Comparable) key + : null; + + while (true) { + comparison = (comparableKey != null) + ? comparableKey.compareTo(nearest.key) + : comparator.compare(key, nearest.key); + + // We found the requested key. + if (comparison == 0) { + return nearest; + } + + // If it exists, the key is in a subtree. Go deeper. + Node child = (comparison < 0) ? nearest.left : nearest.right; + if (child == null) { + break; + } + + nearest = child; + } + } + + // The key doesn't exist in this tree. + if (!create) { + return null; + } + + // Create the node and add it to the tree or the table. + Node header = this.header; + Node created; + if (nearest == null) { + // Check that the value is comparable if we didn't do any comparisons. + if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) { + throw new ClassCastException(key.getClass().getName() + " is not Comparable"); + } + created = new Node(nearest, key, header, header.prev); + root = created; + } else { + created = new Node(nearest, key, header, header.prev); + if (comparison < 0) { // nearest.key is higher + nearest.left = created; + } else { // comparison > 0, nearest.key is lower + nearest.right = created; + } + rebalance(nearest, true); + } + size++; + modCount++; + + return created; + } + + @SuppressWarnings("unchecked") + Node findByObject(Object key) { + try { + return key != null ? find((K) key, false) : null; + } catch (ClassCastException e) { + return null; + } + } + + /** + * Returns this map's entry that has the same key and value as {@code + * entry}, or null if this map has no such entry. + * + *

This method uses the comparator for key equality rather than {@code + * equals}. If this map's comparator isn't consistent with equals (such as + * {@code String.CASE_INSENSITIVE_ORDER}), then {@code remove()} and {@code + * contains()} will violate the collections API. + */ + Node findByEntry(Entry entry) { + Node mine = findByObject(entry.getKey()); + boolean valuesEqual = mine != null && equal(mine.value, entry.getValue()); + return valuesEqual ? mine : null; + } + + private boolean equal(Object a, Object b) { + return a == b || (a != null && a.equals(b)); + } + + /** + * Removes {@code node} from this tree, rearranging the tree's structure as + * necessary. + * + * @param unlink true to also unlink this node from the iteration linked list. + */ + void removeInternal(Node node, boolean unlink) { + if (unlink) { + node.prev.next = node.next; + node.next.prev = node.prev; + } + + Node left = node.left; + Node right = node.right; + Node originalParent = node.parent; + if (left != null && right != null) { + + /* + * To remove a node with both left and right subtrees, move an + * adjacent node from one of those subtrees into this node's place. + * + * Removing the adjacent node may change this node's subtrees. This + * node may no longer have two subtrees once the adjacent node is + * gone! + */ + + Node adjacent = (left.height > right.height) ? left.last() : right.first(); + removeInternal(adjacent, false); // takes care of rebalance and size-- + + int leftHeight = 0; + left = node.left; + if (left != null) { + leftHeight = left.height; + adjacent.left = left; + left.parent = adjacent; + node.left = null; + } + + int rightHeight = 0; + right = node.right; + if (right != null) { + rightHeight = right.height; + adjacent.right = right; + right.parent = adjacent; + node.right = null; + } + + adjacent.height = Math.max(leftHeight, rightHeight) + 1; + replaceInParent(node, adjacent); + return; + } else if (left != null) { + replaceInParent(node, left); + node.left = null; + } else if (right != null) { + replaceInParent(node, right); + node.right = null; + } else { + replaceInParent(node, null); + } + + rebalance(originalParent, false); + size--; + modCount++; + } + + Node removeInternalByKey(Object key) { + Node node = findByObject(key); + if (node != null) { + removeInternal(node, true); + } + return node; + } + + private void replaceInParent(Node node, Node replacement) { + Node parent = node.parent; + node.parent = null; + if (replacement != null) { + replacement.parent = parent; + } + + if (parent != null) { + if (parent.left == node) { + parent.left = replacement; + } else { + assert (parent.right == node); + parent.right = replacement; + } + } else { + root = replacement; + } + } + + /** + * Rebalances the tree by making any AVL rotations necessary between the + * newly-unbalanced node and the tree's root. + * + * @param insert true if the node was unbalanced by an insert; false if it + * was by a removal. + */ + private void rebalance(Node unbalanced, boolean insert) { + for (Node node = unbalanced; node != null; node = node.parent) { + Node left = node.left; + Node right = node.right; + int leftHeight = left != null ? left.height : 0; + int rightHeight = right != null ? right.height : 0; + + int delta = leftHeight - rightHeight; + if (delta == -2) { + Node rightLeft = right.left; + Node rightRight = right.right; + int rightRightHeight = rightRight != null ? rightRight.height : 0; + int rightLeftHeight = rightLeft != null ? rightLeft.height : 0; + + int rightDelta = rightLeftHeight - rightRightHeight; + if (rightDelta == -1 || (rightDelta == 0 && !insert)) { + rotateLeft(node); // AVL right right + } else { + assert (rightDelta == 1); + rotateRight(right); // AVL right left + rotateLeft(node); + } + if (insert) { + break; // no further rotations will be necessary + } + + } else if (delta == 2) { + Node leftLeft = left.left; + Node leftRight = left.right; + int leftRightHeight = leftRight != null ? leftRight.height : 0; + int leftLeftHeight = leftLeft != null ? leftLeft.height : 0; + + int leftDelta = leftLeftHeight - leftRightHeight; + if (leftDelta == 1 || (leftDelta == 0 && !insert)) { + rotateRight(node); // AVL left left + } else { + assert (leftDelta == -1); + rotateLeft(left); // AVL left right + rotateRight(node); + } + if (insert) { + break; // no further rotations will be necessary + } + + } else if (delta == 0) { + node.height = leftHeight + 1; // leftHeight == rightHeight + if (insert) { + break; // the insert caused balance, so rebalancing is done! + } + + } else { + assert (delta == -1 || delta == 1); + node.height = Math.max(leftHeight, rightHeight) + 1; + if (!insert) { + break; // the height hasn't changed, so rebalancing is done! + } + } + } + } + + /** + * Rotates the subtree so that its root's right child is the new root. + */ + private void rotateLeft(Node root) { + Node left = root.left; + Node pivot = root.right; + Node pivotLeft = pivot.left; + Node pivotRight = pivot.right; + + // move the pivot's left child to the root's right + root.right = pivotLeft; + if (pivotLeft != null) { + pivotLeft.parent = root; + } + + replaceInParent(root, pivot); + + // move the root to the pivot's left + pivot.left = root; + root.parent = pivot; + + // fix heights + root.height = Math.max(left != null ? left.height : 0, + pivotLeft != null ? pivotLeft.height : 0) + 1; + pivot.height = Math.max(root.height, + pivotRight != null ? pivotRight.height : 0) + 1; + } + + /** + * Rotates the subtree so that its root's left child is the new root. + */ + private void rotateRight(Node root) { + Node pivot = root.left; + Node right = root.right; + Node pivotLeft = pivot.left; + Node pivotRight = pivot.right; + + // move the pivot's right child to the root's left + root.left = pivotRight; + if (pivotRight != null) { + pivotRight.parent = root; + } + + replaceInParent(root, pivot); + + // move the root to the pivot's right + pivot.right = root; + root.parent = pivot; + + // fixup heights + root.height = Math.max(right != null ? right.height : 0, + pivotRight != null ? pivotRight.height : 0) + 1; + pivot.height = Math.max(root.height, + pivotLeft != null ? pivotLeft.height : 0) + 1; + } + + private EntrySet entrySet; + private KeySet keySet; + + @Override public Set> entrySet() { + EntrySet result = entrySet; + return result != null ? result : (entrySet = new EntrySet()); + } + + @Override public Set keySet() { + KeySet result = keySet; + return result != null ? result : (keySet = new KeySet()); + } + + static final class Node implements Entry { + Node parent; + Node left; + Node right; + Node next; + Node prev; + final K key; + V value; + int height; + + /** Create the header entry */ + Node() { + key = null; + next = prev = this; + } + + /** Create a regular entry */ + Node(Node parent, K key, Node next, Node prev) { + this.parent = parent; + this.key = key; + this.height = 1; + this.next = next; + this.prev = prev; + prev.next = this; + next.prev = this; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + public V setValue(V value) { + V oldValue = this.value; + this.value = value; + return oldValue; + } + + @SuppressWarnings("rawtypes") + @Override public boolean equals(Object o) { + if (o instanceof Entry) { + Entry other = (Entry) o; + return (key == null ? other.getKey() == null : key.equals(other.getKey())) + && (value == null ? other.getValue() == null : value.equals(other.getValue())); + } + return false; + } + + @Override public int hashCode() { + return (key == null ? 0 : key.hashCode()) + ^ (value == null ? 0 : value.hashCode()); + } + + @Override public String toString() { + return key + "=" + value; + } + + /** + * Returns the first node in this subtree. + */ + public Node first() { + Node node = this; + Node child = node.left; + while (child != null) { + node = child; + child = node.left; + } + return node; + } + + /** + * Returns the last node in this subtree. + */ + public Node last() { + Node node = this; + Node child = node.right; + while (child != null) { + node = child; + child = node.right; + } + return node; + } + } + + private abstract class LinkedTreeMapIterator implements Iterator { + Node next = header.next; + Node lastReturned = null; + int expectedModCount = modCount; + + public final boolean hasNext() { + return next != header; + } + + final Node nextNode() { + Node e = next; + if (e == header) { + throw new NoSuchElementException(); + } + if (modCount != expectedModCount) { + throw new ConcurrentModificationException(); + } + next = e.next; + return lastReturned = e; + } + + public final void remove() { + if (lastReturned == null) { + throw new IllegalStateException(); + } + removeInternal(lastReturned, true); + lastReturned = null; + expectedModCount = modCount; + } + } + + class EntrySet extends AbstractSet> { + @Override public int size() { + return size; + } + + @Override public Iterator> iterator() { + return new LinkedTreeMapIterator>() { + public Entry next() { + return nextNode(); + } + }; + } + + @Override public boolean contains(Object o) { + return o instanceof Entry && findByEntry((Entry) o) != null; + } + + @Override public boolean remove(Object o) { + if (!(o instanceof Entry)) { + return false; + } + + Node node = findByEntry((Entry) o); + if (node == null) { + return false; + } + removeInternal(node, true); + return true; + } + + @Override public void clear() { + LinkedTreeMap.this.clear(); + } + } + + class KeySet extends AbstractSet { + @Override public int size() { + return size; + } + + @Override public Iterator iterator() { + return new LinkedTreeMapIterator() { + public K next() { + return nextNode().key; + } + }; + } + + @Override public boolean contains(Object o) { + return containsKey(o); + } + + @Override public boolean remove(Object key) { + return removeInternalByKey(key) != null; + } + + @Override public void clear() { + LinkedTreeMap.this.clear(); + } + } + + /** + * If somebody is unlucky enough to have to serialize one of these, serialize + * it as a LinkedHashMap so that they won't need Gson on the other side to + * deserialize it. Using serialization defeats our DoS defence, so most apps + * shouldn't use it. + */ + private Object writeReplace() throws ObjectStreamException { + return new LinkedHashMap(this); + } +} \ 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/Primitives.java b/src/com/google/gson/internal/Primitives.java new file mode 100644 index 0000000..b797139 --- /dev/null +++ b/src/com/google/gson/internal/Primitives.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal; + + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Contains static utility methods pertaining to primitive types and their + * corresponding wrapper types. + * + * @author Kevin Bourrillion + */ +public final class Primitives { + private Primitives() {} + + /** 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..607c929 --- /dev/null +++ b/src/com/google/gson/internal/Streams.java @@ -0,0 +1,116 @@ +/* + * 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; + } + // The stream ended prematurely so it is likely a syntax error. + throw new JsonSyntaxException(e); + } catch (MalformedJsonException e) { + throw new JsonSyntaxException(e); + } catch (IOException e) { + throw new JsonIOException(e); + } catch (NumberFormatException e) { + throw new JsonSyntaxException(e); + } + } + + /** + * Writes the JSON element to the writer, recursively. + */ + public static void write(JsonElement element, JsonWriter writer) throws IOException { + TypeAdapters.JSON_ELEMENT.write(writer, element); + } + + public static Writer writerForAppendable(Appendable appendable) { + return appendable instanceof Writer ? (Writer) appendable : new AppendableWriter(appendable); + } + + /** + * Adapts an {@link Appendable} so it can be passed anywhere a {@link Writer} + * is used. + */ + private static final class AppendableWriter extends Writer { + private final Appendable appendable; + private final CurrentWrite currentWrite = new CurrentWrite(); + + private AppendableWriter(Appendable appendable) { + this.appendable = appendable; + } + + @Override public void write(char[] chars, int offset, int length) throws IOException { + currentWrite.chars = chars; + appendable.append(currentWrite, offset, offset + length); + } + + @Override public void write(int i) throws IOException { + appendable.append((char) i); + } + + @Override public void flush() {} + @Override public void close() {} + + /** + * A mutable char sequence pointing at a single char[]. + */ + static class CurrentWrite implements CharSequence { + char[] chars; + public int length() { + return chars.length; + } + public char charAt(int i) { + return chars[i]; + } + public CharSequence subSequence(int start, int end) { + return new String(chars, start, end - start); + } + } + } + +} diff --git a/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..55d7e30 --- /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 java.io.IOException; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.$Gson$Types; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +/** + * Adapt an array of objects. + */ +public final class ArrayTypeAdapter 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/CollectionTypeAdapterFactory.java b/src/com/google/gson/internal/bind/CollectionTypeAdapterFactory.java new file mode 100644 index 0000000..0b95445 --- /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 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.get(typeToken); + + @SuppressWarnings({"unchecked", "rawtypes"}) // create() doesn't define a type parameter + TypeAdapter result = new Adapter(gson, elementType, elementTypeAdapter, constructor); + return result; + } + + private static final class Adapter extends TypeAdapter> { + private final TypeAdapter elementTypeAdapter; + private final ObjectConstructor> constructor; + + public Adapter(Gson context, Type elementType, + TypeAdapter elementTypeAdapter, + ObjectConstructor> 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(); + 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..c3c616c --- /dev/null +++ b/src/com/google/gson/internal/bind/MapTypeAdapterFactory.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal.bind; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.$Gson$Types; +import com.google.gson.internal.ConstructorConstructor; +import com.google.gson.internal.JsonReaderInternalAccess; +import com.google.gson.internal.ObjectConstructor; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Adapts maps to either JSON objects or JSON arrays. + * + *

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 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.get(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> constructor; + + public Adapter(Gson context, Type keyType, TypeAdapter keyTypeAdapter, + Type valueType, TypeAdapter valueTypeAdapter, + ObjectConstructor> 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(map.size()); + + List values = new ArrayList(map.size()); + for (Map.Entry entry : map.entrySet()) { + JsonElement keyElement = keyTypeAdapter.toJsonTree(entry.getKey()); + keys.add(keyElement); + values.add(entry.getValue()); + hasComplexKeys |= keyElement.isJsonArray() || keyElement.isJsonObject(); + } + + if (hasComplexKeys) { + out.beginArray(); + for (int i = 0; i < keys.size(); i++) { + out.beginArray(); // entry array + Streams.write(keys.get(i), out); + valueTypeAdapter.write(out, values.get(i)); + out.endArray(); + } + out.endArray(); + } else { + out.beginObject(); + for (int i = 0; i < keys.size(); i++) { + JsonElement keyElement = keys.get(i); + out.name(keyToString(keyElement)); + valueTypeAdapter.write(out, values.get(i)); + } + out.endObject(); + } + } + + private String keyToString(JsonElement keyElement) { + if (keyElement.isJsonPrimitive()) { + JsonPrimitive primitive = keyElement.getAsJsonPrimitive(); + if (primitive.isNumber()) { + return String.valueOf(primitive.getAsNumber()); + } else if (primitive.isBoolean()) { + return Boolean.toString(primitive.getAsBoolean()); + } else if (primitive.isString()) { + return primitive.getAsString(); + } else { + throw new AssertionError(); + } + } else if (keyElement.isJsonNull()) { + return "null"; + } else { + throw new AssertionError(); + } + } + } +} diff --git a/src/com/google/gson/internal/bind/ObjectTypeAdapter.java b/src/com/google/gson/internal/bind/ObjectTypeAdapter.java new file mode 100644 index 0000000..235d2b3 --- /dev/null +++ b/src/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal.bind; + +import com.google.gson.Gson; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Adapts types whose static type is only 'Object'. Uses getClass() on + * serialization and a primitive/Map/List on deserialization. + */ +public final class ObjectTypeAdapter extends TypeAdapter { + public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { + @SuppressWarnings("unchecked") + public TypeAdapter create(Gson gson, TypeToken type) { + if (type.getRawType() == Object.class) { + return (TypeAdapter) new ObjectTypeAdapter(gson); + } + return null; + } + }; + + private final Gson gson; + + private ObjectTypeAdapter(Gson gson) { + this.gson = gson; + } + + @Override public Object read(JsonReader in) throws IOException { + JsonToken token = in.peek(); + switch (token) { + case BEGIN_ARRAY: + List list = new ArrayList(); + in.beginArray(); + while (in.hasNext()) { + list.add(read(in)); + } + in.endArray(); + return list; + + case BEGIN_OBJECT: + Map map = new LinkedTreeMap(); + in.beginObject(); + while (in.hasNext()) { + map.put(in.nextName(), read(in)); + } + in.endObject(); + return map; + + case STRING: + return in.nextString(); + + case NUMBER: + return in.nextDouble(); + + case BOOLEAN: + return in.nextBoolean(); + + case NULL: + in.nextNull(); + return null; + + default: + throw new IllegalStateException(); + } + } + + @SuppressWarnings("unchecked") + @Override public void write(JsonWriter out, Object value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + + TypeAdapter typeAdapter = (TypeAdapter) gson.getAdapter(value.getClass()); + if (typeAdapter instanceof ObjectTypeAdapter) { + out.beginObject(); + out.endObject(); + return; + } + + typeAdapter.write(out, value); + } +} diff --git a/src/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java b/src/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java new file mode 100644 index 0000000..e64ed17 --- /dev/null +++ b/src/com/google/gson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -0,0 +1,204 @@ +/* + * 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.FieldNamingStrategy; +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.annotations.SerializedName; +import com.google.gson.internal.$Gson$Types; +import com.google.gson.internal.ConstructorConstructor; +import com.google.gson.internal.Excluder; +import com.google.gson.internal.ObjectConstructor; +import com.google.gson.internal.Primitives; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Type adapter that reflects over the fields and methods of a class. + */ +public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { + private final ConstructorConstructor constructorConstructor; + private final FieldNamingStrategy fieldNamingPolicy; + private final Excluder excluder; + + public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, + FieldNamingStrategy fieldNamingPolicy, Excluder excluder) { + this.constructorConstructor = constructorConstructor; + this.fieldNamingPolicy = fieldNamingPolicy; + this.excluder = excluder; + } + + public boolean excludeField(Field f, boolean serialize) { + return !excluder.excludeClass(f.getType(), serialize) && !excluder.excludeField(f, serialize); + } + + private String getFieldName(Field f) { + SerializedName serializedName = f.getAnnotation(SerializedName.class); + return serializedName == null ? fieldNamingPolicy.translateName(f) : serializedName.value(); + } + + public TypeAdapter create(Gson gson, final TypeToken type) { + Class raw = type.getRawType(); + + if (!Object.class.isAssignableFrom(raw)) { + return null; // it's a primitive! + } + + ObjectConstructor constructor = constructorConstructor.get(type); + return new Adapter(constructor, getBoundFields(gson, type, raw)); + } + + private ReflectiveTypeAdapterFactory.BoundField createBoundField( + final Gson context, final Field field, final String name, + final TypeToken fieldType, boolean serialize, boolean deserialize) { + final boolean isPrimitive = Primitives.isPrimitive(fieldType.getRawType()); + + // special casing primitives here saves ~5% on Android... + return new ReflectiveTypeAdapterFactory.BoundField(name, serialize, deserialize) { + final TypeAdapter typeAdapter = context.getAdapter(fieldType); + @SuppressWarnings({"unchecked", "rawtypes"}) // the type adapter and field type always agree + @Override void write(JsonWriter writer, Object value) + throws IOException, IllegalAccessException { + Object fieldValue = field.get(value); + TypeAdapter t = + new TypeAdapterRuntimeTypeWrapper(context, this.typeAdapter, fieldType.getType()); + t.write(writer, fieldValue); + } + @Override void read(JsonReader reader, Object value) + throws IOException, IllegalAccessException { + Object fieldValue = typeAdapter.read(reader); + if (fieldValue != null || !isPrimitive) { + field.set(value, fieldValue); + } + } + }; + } + + private Map getBoundFields(Gson context, TypeToken type, Class raw) { + Map result = new LinkedHashMap(); + if (raw.isInterface()) { + return result; + } + + Type declaredType = type.getType(); + while (raw != Object.class) { + Field[] fields = raw.getDeclaredFields(); + for (Field field : fields) { + boolean serialize = excludeField(field, true); + boolean deserialize = excludeField(field, false); + if (!serialize && !deserialize) { + continue; + } + field.setAccessible(true); + Type fieldType = $Gson$Types.resolve(type.getType(), raw, field.getGenericType()); + BoundField boundField = createBoundField(context, field, getFieldName(field), + TypeToken.get(fieldType), serialize, deserialize); + BoundField previous = result.put(boundField.name, boundField); + if (previous != null) { + throw new IllegalArgumentException(declaredType + + " declares multiple JSON fields named " + previous.name); + } + } + type = TypeToken.get($Gson$Types.resolve(type.getType(), raw, raw.getGenericSuperclass())); + raw = type.getRawType(); + } + return result; + } + + static abstract class BoundField { + final String name; + final boolean serialized; + final boolean deserialized; + + protected BoundField(String name, boolean serialized, boolean deserialized) { + this.name = name; + this.serialized = serialized; + this.deserialized = deserialized; + } + + abstract void write(JsonWriter writer, Object value) throws IOException, IllegalAccessException; + abstract void read(JsonReader reader, Object value) throws IOException, IllegalAccessException; + } + + public static final class Adapter extends TypeAdapter { + private final ObjectConstructor constructor; + private final Map boundFields; + + private Adapter(ObjectConstructor constructor, Map boundFields) { + this.constructor = constructor; + this.boundFields = boundFields; + } + + @Override public T read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + + T instance = constructor.construct(); + + try { + in.beginObject(); + while (in.hasNext()) { + String name = in.nextName(); + BoundField field = boundFields.get(name); + if (field == null || !field.deserialized) { + in.skipValue(); + } else { + field.read(in, instance); + } + } + } catch (IllegalStateException e) { + throw new JsonSyntaxException(e); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + in.endObject(); + return instance; + } + + @Override public void write(JsonWriter out, T value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + + out.beginObject(); + try { + for (BoundField boundField : boundFields.values()) { + if (boundField.serialized) { + out.name(boundField.name); + boundField.write(out, value); + } + } + } catch (IllegalAccessException e) { + throw new AssertionError(); + } + out.endObject(); + } + } +} diff --git a/src/com/google/gson/internal/bind/SqlDateTypeAdapter.java b/src/com/google/gson/internal/bind/SqlDateTypeAdapter.java new file mode 100644 index 0000000..5ecc2e9 --- /dev/null +++ b/src/com/google/gson/internal/bind/SqlDateTypeAdapter.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal.bind; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +/** + * Adapter for java.sql.Date. Although this class appears stateless, it is not. + * DateFormat captures its time zone and locale when it is created, which gives + * this class state. DateFormat isn't thread safe either, so this class has + * to synchronize its read and write methods. + */ +public final class SqlDateTypeAdapter extends TypeAdapter { + 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() == java.sql.Date.class + ? (TypeAdapter) new SqlDateTypeAdapter() : null; + } + }; + + private final DateFormat format = new SimpleDateFormat("MMM d, yyyy"); + + @Override + public synchronized java.sql.Date read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } + try { + final long utilDate = format.parse(in.nextString()).getTime(); + return new java.sql.Date(utilDate); + } catch (ParseException e) { + throw new JsonSyntaxException(e); + } + } + + @Override + public synchronized void write(JsonWriter out, java.sql.Date value) throws IOException { + out.value(value == null ? null : format.format(value)); + } +} diff --git a/src/com/google/gson/internal/bind/TimeTypeAdapter.java b/src/com/google/gson/internal/bind/TimeTypeAdapter.java new file mode 100644 index 0000000..bbbb4d9 --- /dev/null +++ b/src/com/google/gson/internal/bind/TimeTypeAdapter.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.gson.internal.bind; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.google.gson.TypeAdapter; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; +import java.io.IOException; +import java.sql.Time; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Adapter for Time. Although this class appears stateless, it is not. + * DateFormat captures its time zone and locale when it is created, which gives + * this class state. DateFormat isn't thread safe either, so this class has + * to synchronize its read and write methods. + */ +public final class TimeTypeAdapter extends TypeAdapter