JCLOUDS-750 allow apis to bind FieldNamingPolicy, which controls fallback naming policy of serialized fields.

This commit is contained in:
Adrian Cole 2014-10-26 10:22:48 -07:00
parent 0b88dadb8f
commit 5b6f1e929e
5 changed files with 94 additions and 16 deletions

View File

@ -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);

View File

@ -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<? extends NameExtractor<?>> extractors) {
private final FieldNamingPolicy fallback;
public AnnotationFieldNamingStrategy(Iterable<? extends NameExtractor<?>> 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<? extends NameExtractor<?>> extractors) {
super(extractors);
public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor<?>> extractors,
FieldNamingPolicy fallback) {
super(extractors, fallback);
}
@Override

View File

@ -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;
}
}
}

View File

@ -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()));

View File

@ -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");