From 2f5c52d86d0fa96da51505778b53a78bcb49d0dc Mon Sep 17 00:00:00 2001
From: Adrian Cole
Date: Mon, 8 Nov 2010 06:43:17 +0100
Subject: [PATCH] Issue 379: patched gson to override default enum parsing
---
.../gson/JcloudsDefaultTypeAdapters.java | 928 ++++++++++++++++++
.../com/google/gson/JcloudsGsonBuilder.java | 591 +++++++++++
.../org/jclouds/json/config/GsonModule.java | 20 +-
.../test/java/org/jclouds/json/JsonTest.java | 78 ++
4 files changed, 1607 insertions(+), 10 deletions(-)
create mode 100644 core/src/main/java/com/google/gson/JcloudsDefaultTypeAdapters.java
create mode 100644 core/src/main/java/com/google/gson/JcloudsGsonBuilder.java
create mode 100644 core/src/test/java/org/jclouds/json/JsonTest.java
diff --git a/core/src/main/java/com/google/gson/JcloudsDefaultTypeAdapters.java b/core/src/main/java/com/google/gson/JcloudsDefaultTypeAdapters.java
new file mode 100644
index 0000000000..39fdc04c80
--- /dev/null
+++ b/core/src/main/java/com/google/gson/JcloudsDefaultTypeAdapters.java
@@ -0,0 +1,928 @@
+/**
+ *
+ * Copyright (C) 2010 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/*
+ * 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.Method;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import com.google.common.base.Function;
+import com.google.common.collect.MapMaker;
+
+/**
+ * List of all the default type adapters ({@link JsonSerializer}s, {@link JsonDeserializer}s, and
+ * {@link InstanceCreator}s.
+ *
+ *
Note!
+ * changed to edit the default behaviour of enum parsing by Adrian Cole
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+final class JcloudsDefaultTypeAdapters {
+
+ private static final DefaultDateTypeAdapter DATE_TYPE_ADAPTER = new DefaultDateTypeAdapter();
+ private static final DefaultJavaSqlDateTypeAdapter JAVA_SQL_DATE_TYPE_ADAPTER = new DefaultJavaSqlDateTypeAdapter();
+ private static final DefaultTimeTypeAdapter TIME_TYPE_ADAPTER = new DefaultTimeTypeAdapter();
+ private static final DefaultTimestampDeserializer TIMESTAMP_DESERIALIZER = new DefaultTimestampDeserializer();
+
+ @SuppressWarnings({ "rawtypes" })
+ private static final EnumTypeAdapter ENUM_TYPE_ADAPTER = new EnumTypeAdapter();
+ private static final UrlTypeAdapter URL_TYPE_ADAPTER = new UrlTypeAdapter();
+ private static final UriTypeAdapter URI_TYPE_ADAPTER = new UriTypeAdapter();
+ private static final UuidTypeAdapter UUUID_TYPE_ADAPTER = new UuidTypeAdapter();
+ private static final LocaleTypeAdapter LOCALE_TYPE_ADAPTER = new LocaleTypeAdapter();
+ private static final CollectionTypeAdapter COLLECTION_TYPE_ADAPTER = new CollectionTypeAdapter();
+ private static final MapTypeAdapter MAP_TYPE_ADAPTER = new MapTypeAdapter();
+ private static final BigDecimalTypeAdapter BIG_DECIMAL_TYPE_ADAPTER = new BigDecimalTypeAdapter();
+ private static final BigIntegerTypeAdapter BIG_INTEGER_TYPE_ADAPTER = new BigIntegerTypeAdapter();
+
+ private static final BooleanTypeAdapter BOOLEAN_TYPE_ADAPTER = new BooleanTypeAdapter();
+ private static final ByteTypeAdapter BYTE_TYPE_ADAPTER = new ByteTypeAdapter();
+ private static final CharacterTypeAdapter CHARACTER_TYPE_ADAPTER = new CharacterTypeAdapter();
+ private static final DoubleDeserializer DOUBLE_TYPE_ADAPTER = new DoubleDeserializer();
+ private static final FloatDeserializer FLOAT_TYPE_ADAPTER = new FloatDeserializer();
+ private static final IntegerTypeAdapter INTEGER_TYPE_ADAPTER = new IntegerTypeAdapter();
+ private static final LongDeserializer LONG_DESERIALIZER = new LongDeserializer();
+ private static final NumberTypeAdapter NUMBER_TYPE_ADAPTER = new NumberTypeAdapter();
+ private static final ShortTypeAdapter SHORT_TYPE_ADAPTER = new ShortTypeAdapter();
+ private static final StringTypeAdapter STRING_TYPE_ADAPTER = new StringTypeAdapter();
+
+ private static final PropertiesCreator PROPERTIES_CREATOR = new PropertiesCreator();
+ private static final TreeSetCreator TREE_SET_CREATOR = new TreeSetCreator();
+ private static final HashSetCreator HASH_SET_CREATOR = new HashSetCreator();
+ private static final GregorianCalendarTypeAdapter GREGORIAN_CALENDAR_TYPE_ADAPTER = new GregorianCalendarTypeAdapter();
+
+ // The constants DEFAULT_SERIALIZERS, DEFAULT_DESERIALIZERS, and DEFAULT_INSTANCE_CREATORS
+ // must be defined after the constants for the type adapters. Otherwise, the type adapter
+ // constants will appear as nulls.
+ private static final ParameterizedTypeHandlerMap> DEFAULT_SERIALIZERS = createDefaultSerializers();
+ private static final ParameterizedTypeHandlerMap> DEFAULT_DESERIALIZERS = createDefaultDeserializers();
+ private static final ParameterizedTypeHandlerMap> DEFAULT_INSTANCE_CREATORS = createDefaultInstanceCreators();
+
+ private static ParameterizedTypeHandlerMap> createDefaultSerializers() {
+ ParameterizedTypeHandlerMap> map = new ParameterizedTypeHandlerMap>();
+
+ map.registerForTypeHierarchy(Enum.class, ENUM_TYPE_ADAPTER);
+ map.register(URL.class, URL_TYPE_ADAPTER);
+ map.register(URI.class, URI_TYPE_ADAPTER);
+ map.register(UUID.class, UUUID_TYPE_ADAPTER);
+ map.register(Locale.class, LOCALE_TYPE_ADAPTER);
+ map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER);
+ map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER);
+ map.register(Date.class, DATE_TYPE_ADAPTER);
+ map.register(java.sql.Date.class, JAVA_SQL_DATE_TYPE_ADAPTER);
+ map.register(Timestamp.class, DATE_TYPE_ADAPTER);
+ map.register(Time.class, TIME_TYPE_ADAPTER);
+ map.register(Calendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER);
+ map.register(GregorianCalendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER);
+ map.register(BigDecimal.class, BIG_DECIMAL_TYPE_ADAPTER);
+ map.register(BigInteger.class, BIG_INTEGER_TYPE_ADAPTER);
+
+ // Add primitive serializers
+ map.register(Boolean.class, BOOLEAN_TYPE_ADAPTER);
+ map.register(boolean.class, BOOLEAN_TYPE_ADAPTER);
+ map.register(Byte.class, BYTE_TYPE_ADAPTER);
+ map.register(byte.class, BYTE_TYPE_ADAPTER);
+ map.register(Character.class, CHARACTER_TYPE_ADAPTER);
+ map.register(char.class, CHARACTER_TYPE_ADAPTER);
+ map.register(Integer.class, INTEGER_TYPE_ADAPTER);
+ map.register(int.class, INTEGER_TYPE_ADAPTER);
+ map.register(Number.class, NUMBER_TYPE_ADAPTER);
+ map.register(Short.class, SHORT_TYPE_ADAPTER);
+ map.register(short.class, SHORT_TYPE_ADAPTER);
+ map.register(String.class, STRING_TYPE_ADAPTER);
+
+ map.makeUnmodifiable();
+ return map;
+ }
+
+ private static ParameterizedTypeHandlerMap> createDefaultDeserializers() {
+ ParameterizedTypeHandlerMap> map = new ParameterizedTypeHandlerMap>();
+ map.registerForTypeHierarchy(Enum.class, wrapDeserializer(ENUM_TYPE_ADAPTER));
+ map.register(URL.class, wrapDeserializer(URL_TYPE_ADAPTER));
+ map.register(URI.class, wrapDeserializer(URI_TYPE_ADAPTER));
+ map.register(UUID.class, wrapDeserializer(UUUID_TYPE_ADAPTER));
+ map.register(Locale.class, wrapDeserializer(LOCALE_TYPE_ADAPTER));
+ map.registerForTypeHierarchy(Collection.class, wrapDeserializer(COLLECTION_TYPE_ADAPTER));
+ map.registerForTypeHierarchy(Map.class, wrapDeserializer(MAP_TYPE_ADAPTER));
+ map.register(Date.class, wrapDeserializer(DATE_TYPE_ADAPTER));
+ map.register(java.sql.Date.class, wrapDeserializer(JAVA_SQL_DATE_TYPE_ADAPTER));
+ map.register(Timestamp.class, wrapDeserializer(TIMESTAMP_DESERIALIZER));
+ map.register(Time.class, wrapDeserializer(TIME_TYPE_ADAPTER));
+ map.register(Calendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER);
+ map.register(GregorianCalendar.class, GREGORIAN_CALENDAR_TYPE_ADAPTER);
+ map.register(BigDecimal.class, wrapDeserializer(BIG_DECIMAL_TYPE_ADAPTER));
+ map.register(BigInteger.class, wrapDeserializer(BIG_INTEGER_TYPE_ADAPTER));
+
+ // Add primitive deserializers
+ map.register(Boolean.class, wrapDeserializer(BOOLEAN_TYPE_ADAPTER));
+ map.register(boolean.class, wrapDeserializer(BOOLEAN_TYPE_ADAPTER));
+ map.register(Byte.class, wrapDeserializer(BYTE_TYPE_ADAPTER));
+ map.register(byte.class, wrapDeserializer(BYTE_TYPE_ADAPTER));
+ map.register(Character.class, wrapDeserializer(CHARACTER_TYPE_ADAPTER));
+ map.register(char.class, wrapDeserializer(CHARACTER_TYPE_ADAPTER));
+ map.register(Double.class, wrapDeserializer(DOUBLE_TYPE_ADAPTER));
+ map.register(double.class, wrapDeserializer(DOUBLE_TYPE_ADAPTER));
+ map.register(Float.class, wrapDeserializer(FLOAT_TYPE_ADAPTER));
+ map.register(float.class, wrapDeserializer(FLOAT_TYPE_ADAPTER));
+ map.register(Integer.class, wrapDeserializer(INTEGER_TYPE_ADAPTER));
+ map.register(int.class, wrapDeserializer(INTEGER_TYPE_ADAPTER));
+ map.register(Long.class, wrapDeserializer(LONG_DESERIALIZER));
+ map.register(long.class, wrapDeserializer(LONG_DESERIALIZER));
+ map.register(Number.class, wrapDeserializer(NUMBER_TYPE_ADAPTER));
+ map.register(Short.class, wrapDeserializer(SHORT_TYPE_ADAPTER));
+ map.register(short.class, wrapDeserializer(SHORT_TYPE_ADAPTER));
+ map.register(String.class, wrapDeserializer(STRING_TYPE_ADAPTER));
+
+ map.makeUnmodifiable();
+ return map;
+ }
+
+ private static ParameterizedTypeHandlerMap> createDefaultInstanceCreators() {
+ ParameterizedTypeHandlerMap> map = new ParameterizedTypeHandlerMap>();
+ map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER);
+
+ // Add Collection type instance creators
+ map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER);
+
+ map.registerForTypeHierarchy(Set.class, HASH_SET_CREATOR);
+ map.registerForTypeHierarchy(SortedSet.class, TREE_SET_CREATOR);
+ map.register(Properties.class, PROPERTIES_CREATOR);
+ map.makeUnmodifiable();
+ return map;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static JsonDeserializer> wrapDeserializer(JsonDeserializer> deserializer) {
+ return new JsonDeserializerExceptionWrapper(deserializer);
+ }
+
+ static ParameterizedTypeHandlerMap> getDefaultSerializers() {
+ return getDefaultSerializers(false, LongSerializationPolicy.DEFAULT);
+ }
+
+ static ParameterizedTypeHandlerMap> getDefaultSerializers(
+ boolean serializeSpecialFloatingPointValues, LongSerializationPolicy longSerializationPolicy) {
+ ParameterizedTypeHandlerMap> serializers = new ParameterizedTypeHandlerMap>();
+
+ // Double primitive
+ JcloudsDefaultTypeAdapters.DoubleSerializer doubleSerializer = new JcloudsDefaultTypeAdapters.DoubleSerializer(
+ serializeSpecialFloatingPointValues);
+ serializers.registerIfAbsent(Double.class, doubleSerializer);
+ serializers.registerIfAbsent(double.class, doubleSerializer);
+
+ // Float primitive
+ JcloudsDefaultTypeAdapters.FloatSerializer floatSerializer = new JcloudsDefaultTypeAdapters.FloatSerializer(
+ serializeSpecialFloatingPointValues);
+ serializers.registerIfAbsent(Float.class, floatSerializer);
+ serializers.registerIfAbsent(float.class, floatSerializer);
+
+ // Long primitive
+ JcloudsDefaultTypeAdapters.LongSerializer longSerializer = new JcloudsDefaultTypeAdapters.LongSerializer(
+ longSerializationPolicy);
+ serializers.registerIfAbsent(Long.class, longSerializer);
+ serializers.registerIfAbsent(long.class, longSerializer);
+
+ serializers.registerIfAbsent(DEFAULT_SERIALIZERS);
+ return serializers;
+ }
+
+ static ParameterizedTypeHandlerMap> getDefaultDeserializers() {
+ return DEFAULT_DESERIALIZERS;
+ }
+
+ static ParameterizedTypeHandlerMap> getDefaultInstanceCreators() {
+ return DEFAULT_INSTANCE_CREATORS;
+ }
+
+ static class DefaultDateTypeAdapter implements JsonSerializer, JsonDeserializer {
+ private final DateFormat format;
+
+ DefaultDateTypeAdapter() {
+ this.format = DateFormat.getDateTimeInstance();
+ }
+
+ DefaultDateTypeAdapter(final String datePattern) {
+ this.format = new SimpleDateFormat(datePattern);
+ }
+
+ DefaultDateTypeAdapter(final int style) {
+ this.format = DateFormat.getDateInstance(style);
+ }
+
+ public DefaultDateTypeAdapter(final int dateStyle, final int timeStyle) {
+ this.format = DateFormat.getDateTimeInstance(dateStyle, timeStyle);
+ }
+
+ // 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 (format) {
+ String dateFormatAsString = format.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");
+ }
+ try {
+ synchronized (format) {
+ return format.parse(json.getAsString());
+ }
+ } catch (ParseException e) {
+ throw new JsonParseException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(DefaultDateTypeAdapter.class.getSimpleName());
+ sb.append('(').append(format.getClass().getSimpleName()).append(')');
+ return sb.toString();
+ }
+ }
+
+ static class DefaultJavaSqlDateTypeAdapter implements JsonSerializer, JsonDeserializer {
+ private final DateFormat format;
+
+ DefaultJavaSqlDateTypeAdapter() {
+ this.format = new SimpleDateFormat("MMM d, yyyy");
+ }
+
+ public JsonElement serialize(java.sql.Date src, Type typeOfSrc, JsonSerializationContext context) {
+ synchronized (format) {
+ String dateFormatAsString = format.format(src);
+ return new JsonPrimitive(dateFormatAsString);
+ }
+ }
+
+ public java.sql.Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ if (!(json instanceof JsonPrimitive)) {
+ throw new JsonParseException("The date should be a string value");
+ }
+ try {
+ synchronized (format) {
+ Date date = format.parse(json.getAsString());
+ return new java.sql.Date(date.getTime());
+ }
+ } catch (ParseException e) {
+ throw new JsonParseException(e);
+ }
+ }
+ }
+
+ static class DefaultTimestampDeserializer implements JsonDeserializer {
+ public Timestamp deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
+ throws JsonParseException {
+ Date date = context.deserialize(json, Date.class);
+ return new Timestamp(date.getTime());
+ }
+ }
+
+ static class DefaultTimeTypeAdapter implements JsonSerializer
+ *
+ *
NOTE: the order of invocation of configuration methods does not matter.
+ *
+ * @author Inderjeet Singh
+ * @author Joel Leitch
+ */
+public final class JcloudsGsonBuilder {
+ private static final InnerClassExclusionStrategy innerClassExclusionStrategy =
+ new InnerClassExclusionStrategy();
+ private static final ExposeAnnotationSerializationExclusionStrategy
+ exposeAnnotationSerializationExclusionStrategy =
+ new ExposeAnnotationSerializationExclusionStrategy();
+ private static final ExposeAnnotationDeserializationExclusionStrategy
+ exposeAnnotationDeserializationExclusionStrategy =
+ new ExposeAnnotationDeserializationExclusionStrategy();
+
+ private final Collection exclusionStrategies =
+ new HashSet();
+
+ private double ignoreVersionsAfter;
+ private ModifierBasedExclusionStrategy modifierBasedExclusionStrategy;
+ private boolean serializeInnerClasses;
+ private boolean excludeFieldsWithoutExposeAnnotation;
+ private LongSerializationPolicy longSerializationPolicy;
+ private FieldNamingStrategy2 fieldNamingPolicy;
+ private final ParameterizedTypeHandlerMap> instanceCreators;
+ private final ParameterizedTypeHandlerMap> serializers;
+ private final ParameterizedTypeHandlerMap> deserializers;
+ private boolean serializeNulls;
+ private String datePattern;
+ private int dateStyle;
+ private int timeStyle;
+ private boolean serializeSpecialFloatingPointValues;
+ private boolean escapeHtmlChars;
+ private boolean prettyPrinting;
+ private boolean generateNonExecutableJson;
+
+ /**
+ * Creates a JcloudsGsonBuilder instance that can be used to build Gson with various configuration
+ * settings. JcloudsGsonBuilder 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 JcloudsGsonBuilder() {
+ // add default exclusion strategies
+ exclusionStrategies.add(Gson.DEFAULT_ANON_LOCAL_CLASS_EXCLUSION_STRATEGY);
+ exclusionStrategies.add(Gson.DEFAULT_SYNTHETIC_FIELD_EXCLUSION_STRATEGY);
+
+ // setup default values
+ ignoreVersionsAfter = VersionConstants.IGNORE_VERSIONS;
+ serializeInnerClasses = true;
+ prettyPrinting = false;
+ escapeHtmlChars = true;
+ modifierBasedExclusionStrategy = Gson.DEFAULT_MODIFIER_BASED_EXCLUSION_STRATEGY;
+ excludeFieldsWithoutExposeAnnotation = false;
+ longSerializationPolicy = LongSerializationPolicy.DEFAULT;
+ fieldNamingPolicy = Gson.DEFAULT_NAMING_POLICY;
+ instanceCreators = new ParameterizedTypeHandlerMap>();
+ serializers = new ParameterizedTypeHandlerMap>();
+ deserializers = new ParameterizedTypeHandlerMap>();
+ serializeNulls = false;
+ dateStyle = DateFormat.DEFAULT;
+ timeStyle = DateFormat.DEFAULT;
+ serializeSpecialFloatingPointValues = false;
+ generateNonExecutableJson = false;
+ }
+
+ /**
+ * 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public JcloudsGsonBuilder setVersion(double ignoreVersionsAfter) {
+ this.ignoreVersionsAfter = 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public JcloudsGsonBuilder excludeFieldsWithModifiers(int... modifiers) {
+ modifierBasedExclusionStrategy = new ModifierBasedExclusionStrategy(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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public JcloudsGsonBuilder 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public JcloudsGsonBuilder excludeFieldsWithoutExposeAnnotation() {
+ excludeFieldsWithoutExposeAnnotation = true;
+ 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public JcloudsGsonBuilder serializeNulls() {
+ this.serializeNulls = true;
+ return this;
+ }
+
+ /**
+ * Configures Gson to exclude inner classes during serialization.
+ *
+ * @return a reference to this {@code JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public JcloudsGsonBuilder disableInnerClassSerialization() {
+ serializeInnerClasses = false;
+ 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public JcloudsGsonBuilder 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public JcloudsGsonBuilder setFieldNamingPolicy(FieldNamingPolicy namingConvention) {
+ return setFieldNamingStrategy(namingConvention.getFieldNamingPolicy());
+ }
+
+ /**
+ * 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public JcloudsGsonBuilder setFieldNamingStrategy(FieldNamingStrategy fieldNamingStrategy) {
+ return setFieldNamingStrategy(new FieldNamingStrategy2Adapter(fieldNamingStrategy));
+ }
+
+ /**
+ * 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ JcloudsGsonBuilder setFieldNamingStrategy(FieldNamingStrategy2 fieldNamingStrategy) {
+ this.fieldNamingPolicy =
+ new SerializedNameAnnotationInterceptingNamingPolicy(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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.4
+ */
+ public JcloudsGsonBuilder setExclusionStrategies(ExclusionStrategy... strategies) {
+ for (ExclusionStrategy strategy : strategies) {
+ exclusionStrategies.add(strategy);
+ }
+ 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public JcloudsGsonBuilder 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public JcloudsGsonBuilder 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.
+ *
+ *
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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public JcloudsGsonBuilder 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public JcloudsGsonBuilder 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.2
+ */
+ public JcloudsGsonBuilder 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 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 an instance creator,
+ * serializer or deserializer was previously registered for the specified {@code type}, it is
+ * overwritten.
+ *
+ * @param type the type definition for the type adapter being registered
+ * @param typeAdapter This object must implement at least one of the {@link InstanceCreator},
+ * {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
+ * @return a reference to this {@code JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ public JcloudsGsonBuilder registerTypeAdapter(Type type, Object typeAdapter) {
+ Preconditions.checkArgument(typeAdapter instanceof JsonSerializer>
+ || typeAdapter instanceof JsonDeserializer> || typeAdapter instanceof InstanceCreator>);
+ if (typeAdapter instanceof InstanceCreator>) {
+ registerInstanceCreator(type, (InstanceCreator>) typeAdapter);
+ }
+ if (typeAdapter instanceof JsonSerializer>) {
+ registerSerializer(type, (JsonSerializer>) typeAdapter);
+ }
+ if (typeAdapter instanceof JsonDeserializer>) {
+ registerDeserializer(type, (JsonDeserializer>) typeAdapter);
+ }
+ return this;
+ }
+
+ /**
+ * Configures Gson to use a custom {@link InstanceCreator} for the specified type. If an instance
+ * creator was previously registered for the specified class, it is overwritten. Since this method
+ * takes a type instead of a Class object, it can be used to register a specific handler for a
+ * generic type corresponding to a raw type.
+ *
+ * @param the type for which instance creator is being registered
+ * @param typeOfT The Type definition for T
+ * @param instanceCreator the instance creator for T
+ * @return a reference to this {@code JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ private JcloudsGsonBuilder registerInstanceCreator(Type typeOfT,
+ InstanceCreator extends T> instanceCreator) {
+ instanceCreators.register(typeOfT, instanceCreator);
+ return this;
+ }
+
+ /**
+ * Configures Gson to use a custom JSON serializer for the specified type. You should use this
+ * method if you want to register different serializers for different generic types corresponding
+ * to a raw type.
+ *
+ * @param the type for which the serializer is being registered
+ * @param typeOfT The type definition for T
+ * @param serializer the custom serializer
+ * @return a reference to this {@code JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ private JcloudsGsonBuilder registerSerializer(Type typeOfT, final JsonSerializer serializer) {
+ serializers.register(typeOfT, serializer);
+ return this;
+ }
+
+ /**
+ * Configures Gson to use a custom JSON deserializer for the specified type. You should use this
+ * method if you want to register different deserializers for different generic types
+ * corresponding to a raw type.
+ *
+ * @param the type for which the deserializer is being registered
+ * @param typeOfT The type definition for T
+ * @param deserializer the custom deserializer
+ * @return a reference to this {@code JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ */
+ private JcloudsGsonBuilder registerDeserializer(Type typeOfT, JsonDeserializer deserializer) {
+ deserializers.register(typeOfT, new JsonDeserializerExceptionWrapper(deserializer));
+ return this;
+ }
+
+ /**
+ * Configures Gson for custom serialization or deserialization for an inheritance type hierarchy.
+ * This method combines the registration of an {@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 an instance creator, serializer or deserializer was previously registered for the specified
+ * type hierarchy, it is overwritten. If an instance creator, serializer or deserializer 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 the {@link InstanceCreator},
+ * {@link JsonSerializer}, and a {@link JsonDeserializer} interfaces.
+ * @return a reference to this {@code JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.5
+ */
+ JcloudsGsonBuilder registerTypeHierarchyAdapter(Class> baseType, Object typeAdapter) {
+ Preconditions.checkArgument(typeAdapter instanceof JsonSerializer>
+ || typeAdapter instanceof JsonDeserializer> || typeAdapter instanceof InstanceCreator>);
+ if (typeAdapter instanceof InstanceCreator>) {
+ registerInstanceCreatorForTypeHierarchy(baseType, (InstanceCreator>) typeAdapter);
+ }
+ if (typeAdapter instanceof JsonSerializer>) {
+ registerSerializerForTypeHierarchy(baseType, (JsonSerializer>) typeAdapter);
+ }
+ if (typeAdapter instanceof JsonDeserializer>) {
+ registerDeserializerForTypeHierarchy(baseType, (JsonDeserializer>) typeAdapter);
+ }
+ return this;
+ }
+
+ private JcloudsGsonBuilder registerInstanceCreatorForTypeHierarchy(Class> classOfT,
+ InstanceCreator extends T> instanceCreator) {
+ instanceCreators.registerForTypeHierarchy(classOfT, instanceCreator);
+ return this;
+ }
+
+ private JcloudsGsonBuilder registerSerializerForTypeHierarchy(Class> classOfT,
+ final JsonSerializer serializer) {
+ serializers.registerForTypeHierarchy(classOfT, serializer);
+ return this;
+ }
+
+ private JcloudsGsonBuilder registerDeserializerForTypeHierarchy(Class> classOfT,
+ JsonDeserializer deserializer) {
+ deserializers.registerForTypeHierarchy(classOfT,
+ new JsonDeserializerExceptionWrapper(deserializer));
+ 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 JcloudsGsonBuilder} object to fulfill the "Builder" pattern
+ * @since 1.3
+ */
+ public JcloudsGsonBuilder 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 JcloudsGsonBuilder} 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 serializationStrategies =
+ new LinkedList(exclusionStrategies);
+ List deserializationStrategies =
+ new LinkedList(exclusionStrategies);
+
+ serializationStrategies.add(modifierBasedExclusionStrategy);
+ deserializationStrategies.add(modifierBasedExclusionStrategy);
+
+ if (!serializeInnerClasses) {
+ serializationStrategies.add(innerClassExclusionStrategy);
+ deserializationStrategies.add(innerClassExclusionStrategy);
+ }
+ if (ignoreVersionsAfter != VersionConstants.IGNORE_VERSIONS) {
+ serializationStrategies.add(new VersionExclusionStrategy(ignoreVersionsAfter));
+ deserializationStrategies.add(new VersionExclusionStrategy(ignoreVersionsAfter));
+ }
+ if (excludeFieldsWithoutExposeAnnotation) {
+ serializationStrategies.add(exposeAnnotationSerializationExclusionStrategy);
+ deserializationStrategies.add(exposeAnnotationDeserializationExclusionStrategy);
+ }
+ ExclusionStrategy serializationExclusionStrategy =
+ new DisjunctionExclusionStrategy(serializationStrategies);
+ ExclusionStrategy deserializationExclusionStrategy =
+ new DisjunctionExclusionStrategy(deserializationStrategies);
+
+ ParameterizedTypeHandlerMap> customSerializers = serializers.copyOf();
+ ParameterizedTypeHandlerMap> customDeserializers = deserializers.copyOf();
+ addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, customSerializers,
+ customDeserializers);
+
+ customSerializers.registerIfAbsent(JcloudsDefaultTypeAdapters.getDefaultSerializers(
+ serializeSpecialFloatingPointValues, longSerializationPolicy));
+
+ customDeserializers.registerIfAbsent(JcloudsDefaultTypeAdapters.getDefaultDeserializers());
+
+ ParameterizedTypeHandlerMap> customInstanceCreators =
+ instanceCreators.copyOf();
+ customInstanceCreators.registerIfAbsent(JcloudsDefaultTypeAdapters.getDefaultInstanceCreators());
+
+ customSerializers.makeUnmodifiable();
+ customDeserializers.makeUnmodifiable();
+ instanceCreators.makeUnmodifiable();
+
+ MappedObjectConstructor objConstructor = new MappedObjectConstructor(customInstanceCreators);
+
+ JsonFormatter formatter = prettyPrinting ?
+ new JsonPrintFormatter(escapeHtmlChars) : new JsonCompactFormatter(escapeHtmlChars);
+ Gson gson = new Gson(serializationExclusionStrategy, deserializationExclusionStrategy,
+ fieldNamingPolicy, objConstructor, formatter, serializeNulls, customSerializers,
+ customDeserializers, generateNonExecutableJson);
+ return gson;
+ }
+
+ private static void addTypeAdaptersForDate(String datePattern, int dateStyle, int timeStyle,
+ ParameterizedTypeHandlerMap> serializers,
+ ParameterizedTypeHandlerMap> deserializers) {
+ DefaultDateTypeAdapter dateTypeAdapter = null;
+ if (datePattern != null && !"".equals(datePattern.trim())) {
+ dateTypeAdapter = new DefaultDateTypeAdapter(datePattern);
+ } else if (dateStyle != DateFormat.DEFAULT && timeStyle != DateFormat.DEFAULT) {
+ dateTypeAdapter = new DefaultDateTypeAdapter(dateStyle, timeStyle);
+ }
+
+ if (dateTypeAdapter != null) {
+ if (!serializers.hasSpecificHandlerFor(Date.class)) {
+ serializers.register(Date.class, dateTypeAdapter);
+ }
+ if (!deserializers.hasSpecificHandlerFor(Date.class)) {
+ deserializers.register(Date.class, dateTypeAdapter);
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/jclouds/json/config/GsonModule.java b/core/src/main/java/org/jclouds/json/config/GsonModule.java
index 257171505a..55c3836b9b 100644
--- a/core/src/main/java/org/jclouds/json/config/GsonModule.java
+++ b/core/src/main/java/org/jclouds/json/config/GsonModule.java
@@ -38,8 +38,8 @@ import org.jclouds.json.internal.GsonWrapper;
import com.google.common.collect.Maps;
import com.google.common.primitives.Bytes;
import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
import com.google.gson.JcloudsCompactFormatter;
+import com.google.gson.JcloudsGsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@@ -64,9 +64,9 @@ public class GsonModule extends AbstractModule {
@Provides
@Singleton
Gson provideGson(JsonBallAdapter jsonObjectAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
- ByteArrayAdapter byteArrayAdapter, JsonAdapterBindings bindings) throws SecurityException,
- NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
- GsonBuilder builder = new GsonBuilder();
+ ByteArrayAdapter byteArrayAdapter, JsonAdapterBindings bindings) throws ClassNotFoundException, Exception {
+ JcloudsGsonBuilder builder = new JcloudsGsonBuilder();
+
builder.registerTypeAdapter(JsonBall.class, jsonObjectAdapter);
builder.registerTypeAdapter(Date.class, adapter);
builder.registerTypeAdapter(new TypeToken>() {
@@ -105,7 +105,7 @@ public class GsonModule extends AbstractModule {
@Override
public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ throws JsonParseException {
return Bytes.asList(CryptoStreams.hex(json.getAsString()));
}
@@ -121,7 +121,7 @@ public class GsonModule extends AbstractModule {
@Override
public byte[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ throws JsonParseException {
return CryptoStreams.hex(json.getAsString());
}
@@ -145,7 +145,7 @@ public class GsonModule extends AbstractModule {
}
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ throws JsonParseException {
String toParse = json.getAsJsonPrimitive().getAsString();
try {
return dateService.iso8601DateParse(toParse);
@@ -170,7 +170,7 @@ public class GsonModule extends AbstractModule {
}
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ throws JsonParseException {
String toParse = json.getAsJsonPrimitive().getAsString();
Date toReturn = dateService.cDateParse(toParse);
return toReturn;
@@ -191,7 +191,7 @@ public class GsonModule extends AbstractModule {
}
public JsonBall deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ throws JsonParseException {
return new JsonBall(json.toString());
}
@@ -205,7 +205,7 @@ public class GsonModule extends AbstractModule {
}
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
- throws JsonParseException {
+ throws JsonParseException {
long toParse = json.getAsJsonPrimitive().getAsLong();
if (toParse == -1)
return null;
diff --git a/core/src/test/java/org/jclouds/json/JsonTest.java b/core/src/test/java/org/jclouds/json/JsonTest.java
new file mode 100644
index 0000000000..16f7e1eeee
--- /dev/null
+++ b/core/src/test/java/org/jclouds/json/JsonTest.java
@@ -0,0 +1,78 @@
+/**
+ *
+ * Copyright (C) 2010 Cloud Conscious, LLC.
+ *
+ * ====================================================================
+ * 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 org.jclouds.json;
+
+import static org.testng.Assert.assertEquals;
+
+import org.jclouds.json.config.GsonModule;
+import org.testng.annotations.Test;
+
+import com.google.gson.JsonParseException;
+import com.google.inject.Guice;
+
+@Test
+public class JsonTest {
+ private Json json = Guice.createInjector(new GsonModule()).getInstance(Json.class);
+
+ private static class EnumInside {
+ private static enum Test {
+ FOO, BAR;
+ }
+
+ private Test enumValue;
+ }
+
+ public void testDeserializeEnum() {
+ assertEquals(json.fromJson("{enumValue : \"FOO\"}", EnumInside.class).enumValue, EnumInside.Test.FOO);
+ }
+
+ @Test(expectedExceptions = JsonParseException.class)
+ public void testDeserializeEnumWhenBadValue() {
+ assertEquals(json.fromJson("{enumValue : \"s\"}", EnumInside.class).enumValue, EnumInside.Test.FOO);
+ }
+
+ private static class EnumInsideWithParser {
+ private static enum Test {
+ FOO, BAR, UNRECOGNIZED;
+
+ @SuppressWarnings("unused")
+ public static Test fromValue(String state) {
+ try {
+ return valueOf(state);
+ } catch (IllegalArgumentException e) {
+ return UNRECOGNIZED;
+ }
+ }
+ }
+
+ private Test enumValue;
+ }
+
+ public void testDeserializeEnumWithParser() {
+ assertEquals(json.fromJson("{enumValue : \"FOO\"}", EnumInsideWithParser.class).enumValue,
+ EnumInsideWithParser.Test.FOO);
+ }
+
+ public void testDeserializeEnumWithParserAndBadValue() {
+ assertEquals(json.fromJson("{enumValue : \"sd\"}", EnumInsideWithParser.class).enumValue,
+ EnumInsideWithParser.Test.UNRECOGNIZED);
+ }
+
+}