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 afbed11ef1..9aebbe97a9 100644 --- a/core/src/main/java/org/jclouds/json/config/GsonModule.java +++ b/core/src/main/java/org/jclouds/json/config/GsonModule.java @@ -24,6 +24,7 @@ import com.google.common.collect.Sets; import com.google.common.primitives.Bytes; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; +import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingStrategy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -38,6 +39,7 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.google.inject.AbstractModule; import com.google.inject.ImplementedBy; +import com.google.inject.Inject; import com.google.inject.Provides; import org.jclouds.date.DateService; import org.jclouds.domain.JsonBall; @@ -63,7 +65,6 @@ import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.SetTypeAdapte import org.jclouds.json.internal.NullHackJsonLiteralAdapter; import org.jclouds.json.internal.OptionalTypeAdapterFactory; -import javax.inject.Inject; import javax.inject.Provider; import javax.inject.Singleton; import java.beans.ConstructorProperties; @@ -78,11 +79,14 @@ import java.util.Set; import static com.google.common.io.BaseEncoding.base16; -/** - * Contains logic for parsing objects from Strings. - */ public class GsonModule extends AbstractModule { + /** Optionally override the fallback policy when name annotations aren't on fields. */ + private static class FallbackFieldNamingPolicy { + @Inject(optional = true) + FieldNamingPolicy fallback = FieldNamingPolicy.IDENTITY; + } + @SuppressWarnings("rawtypes") @Provides @Singleton @@ -92,10 +96,11 @@ public class GsonModule extends AbstractModule { MapTypeAdapterFactory map, MultimapTypeAdapterFactory multimap, IterableTypeAdapterFactory iterable, CollectionTypeAdapterFactory collection, ListTypeAdapterFactory list, ImmutableListTypeAdapterFactory immutableList, FluentIterableTypeAdapterFactory fluentIterable, - ImmutableMapTypeAdapterFactory immutableMap, DefaultExclusionStrategy exclusionStrategy) { + ImmutableMapTypeAdapterFactory immutableMap, DefaultExclusionStrategy exclusionStrategy, + FallbackFieldNamingPolicy fallbackFieldNamingPolicy) { FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of( - new ExtractSerializedName(), new ExtractNamed())); + new ExtractSerializedName(), new ExtractNamed()), fallbackFieldNamingPolicy.fallback); GsonBuilder builder = new GsonBuilder().setFieldNamingStrategy(serializationPolicy) .setExclusionStrategies(exclusionStrategy); diff --git a/core/src/main/java/org/jclouds/json/internal/NamingStrategies.java b/core/src/main/java/org/jclouds/json/internal/NamingStrategies.java index 7a94ce3c90..c3931a1de3 100644 --- a/core/src/main/java/org/jclouds/json/internal/NamingStrategies.java +++ b/core/src/main/java/org/jclouds/json/internal/NamingStrategies.java @@ -43,6 +43,7 @@ import com.google.common.collect.FluentIterable; import com.google.common.collect.Maps; import com.google.common.reflect.Invokable; import com.google.common.reflect.TypeToken; +import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingStrategy; import com.google.gson.annotations.SerializedName; @@ -162,10 +163,14 @@ public class NamingStrategies { public static class AnnotationFieldNamingStrategy extends AnnotationBasedNamingStrategy implements FieldNamingStrategy { - public AnnotationFieldNamingStrategy(Iterable> extractors) { + private final FieldNamingPolicy fallback; + + public AnnotationFieldNamingStrategy(Iterable> extractors, + FieldNamingPolicy fallback) { super(extractors); checkArgument(extractors.iterator().hasNext(), "you must supply at least one name extractor, for example: " + ExtractSerializedName.class.getSimpleName()); + this.fallback = checkNotNull(fallback, "fallback fieldNamingPolicy"); } @Override @@ -175,15 +180,16 @@ public class NamingStrategies { return annotationToNameExtractor.get(annotation.annotationType()).apply(annotation); } } - return null; + return fallback.translateName(f); } } public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements FieldNamingStrategy { - public AnnotationOrNameFieldNamingStrategy(Iterable> extractors) { - super(extractors); + public AnnotationOrNameFieldNamingStrategy(Iterable> extractors, + FieldNamingPolicy fallback) { + super(extractors, fallback); } @Override diff --git a/core/src/test/java/org/jclouds/json/JsonTest.java b/core/src/test/java/org/jclouds/json/JsonTest.java index 3dd5584c52..ed322200dc 100644 --- a/core/src/test/java/org/jclouds/json/JsonTest.java +++ b/core/src/test/java/org/jclouds/json/JsonTest.java @@ -31,6 +31,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.gson.FieldAttributes; +import com.google.gson.FieldNamingPolicy; import com.google.inject.AbstractModule; import com.google.inject.Guice; import com.google.inject.TypeLiteral; @@ -209,4 +210,67 @@ public class JsonTest { EnumInsideWithParser.Test.UNRECOGNIZED); } + private abstract static class SpinalCasedType { + abstract String id(); + + abstract String contentType(); + + // Currently, this only works for deserialization. Need to set a naming policy for serialization! + @SerializedNames({ "id", "content-type" }) + private static SpinalCasedType create(String id, String contentType) { + return new SpinalCasedTypeImpl(id, contentType); + } + } + + public void spinalCaseWithSerializedNames() { + Json json = Guice.createInjector(new GsonModule(), new AbstractModule() { + @Override protected void configure() { + bind(FieldNamingPolicy.class).toInstance(FieldNamingPolicy.LOWER_CASE_WITH_DASHES); + } + }).getInstance(Json.class); + + SpinalCasedType resource = SpinalCasedType.create("1234", "application/json"); + String spinalJson = "{\"id\":\"1234\",\"content-type\":\"application/json\"}"; + + assertEquals(json.toJson(resource), spinalJson); + assertEquals(spinalJson, json.toJson(resource)); + } + + private static class SpinalCasedTypeImpl extends SpinalCasedType { + private final String id; + private final String contentType; + + private SpinalCasedTypeImpl(String id, String contentType) { + this.id = id; + this.contentType = contentType; + } + + @Override String id() { + return id; + } + + @Override String contentType() { + return contentType; + } + + @Override public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof SpinalCasedType) { + SpinalCasedType that = (SpinalCasedType) o; + return (this.id.equals(that.id())) && (this.contentType.equals(that.contentType())); + } + return false; + } + + @Override public int hashCode() { + int h = 1; + h *= 1000003; + h ^= id.hashCode(); + h *= 1000003; + h ^= contentType.hashCode(); + return h; + } + } } diff --git a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java index 1d54d09c61..b4f02020d6 100644 --- a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java +++ b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java @@ -43,6 +43,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingStrategy; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -62,7 +63,7 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() { FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of( - new ExtractSerializedName(), new ExtractNamed())); + new ExtractSerializedName(), new ExtractNamed()), FieldNamingPolicy.IDENTITY); AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy( ImmutableSet.of(ConstructorProperties.class, SerializedNames.class, Inject.class), ImmutableSet.of(new ExtractNamed())); diff --git a/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java b/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java index ccd947a25b..20c6046128 100644 --- a/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java +++ b/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java @@ -19,7 +19,6 @@ package org.jclouds.json.internal; import static org.jclouds.reflect.Reflection2.typeToken; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import static org.testng.Assert.fail; import java.beans.ConstructorProperties; @@ -37,6 +36,7 @@ import org.testng.annotations.Test; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.Invokable; +import com.google.gson.FieldNamingPolicy; import com.google.gson.FieldNamingStrategy; import com.google.gson.annotations.SerializedName; @@ -101,16 +101,18 @@ public final class NamingStrategiesTest { } public void testAnnotationFieldNamingStrategy() throws Exception { - FieldNamingStrategy strategy = new AnnotationFieldNamingStrategy(ImmutableSet.of(new ExtractNamed())); + FieldNamingStrategy strategy = new AnnotationFieldNamingStrategy(ImmutableSet.of(new ExtractNamed()), + FieldNamingPolicy.UPPER_CAMEL_CASE); - assertNull(strategy.translateName(SimpleTest.class.getDeclaredField("a"))); - assertNull(strategy.translateName(SimpleTest.class.getDeclaredField("b"))); + assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("a")), "A"); // Per fallback! + assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("b")), "B"); // Per fallback! assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("c")), "cat"); assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("d")), "dog"); } public void testAnnotationOrNameFieldNamingStrategy() throws Exception { - FieldNamingStrategy strategy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(new ExtractNamed())); + FieldNamingStrategy strategy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(new ExtractNamed()), + FieldNamingPolicy.IDENTITY); assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("a")), "a"); assertEquals(strategy.translateName(SimpleTest.class.getDeclaredField("b")), "b");