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