JCLOUDS-750 support serializing auto-value types without custom type adapters.

This commit is contained in:
Adrian Cole 2014-10-29 21:04:06 -07:00 committed by Adrian Cole
parent 2a43b3a90d
commit 83604e943a
4 changed files with 104 additions and 30 deletions

View File

@ -108,6 +108,11 @@
<version>4.2.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@ -22,6 +22,7 @@ import static org.jclouds.reflect.Reflection2.typeToken;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
@ -118,14 +119,25 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
com.google.common.reflect.TypeToken<T> token = typeToken(type.getType());
Invokable<T, T> deserializationCtor = constructorFieldNamingPolicy.getDeserializer(token);
Invokable<T, T> deserializationTarget = constructorFieldNamingPolicy.getDeserializer(token);
if (deserializationCtor == null) {
if (deserializationTarget == null) {
return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create())
} else {
return new DeserializeIntoParameterizedConstructor<T>(delegateFactory.create(gson, type), deserializationCtor,
getParameterReaders(gson, deserializationCtor));
}
// @AutoValue is SOURCE retention, which means it cannot be looked up at runtime.
// Assume abstract types built by static methods are AutoValue.
if (Modifier.isAbstract(type.getRawType().getModifiers()) && deserializationTarget.isStatic()) {
// Lookup the generated AutoValue class, whose fields must be read for serialization.
String packageName = type.getRawType().getPackage().getName();
String autoClassName = type.getRawType().getName().replace('$', '_')
.replace(packageName + ".", packageName + ".AutoValue_");
try {
type = (TypeToken<T>) TypeToken.get(Class.forName(autoClassName));
} catch (ClassNotFoundException ignored) {
}
}
return new DeserializeIntoParameterizedConstructor<T>(delegateFactory.create(gson, type), deserializationTarget,
getParameterReaders(gson, deserializationTarget));
}
private final class DeserializeIntoParameterizedConstructor<T> extends TypeAdapter<T> {

View File

@ -20,24 +20,35 @@ import static com.google.common.io.BaseEncoding.base16;
import static com.google.common.primitives.Bytes.asList;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.json.config.GsonModule;
import org.jclouds.json.config.GsonModule.DefaultExclusionStrategy;
import org.testng.annotations.Test;
import com.google.common.base.Objects;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.FieldAttributes;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
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.JsonWriter;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Provides;
import com.google.inject.TypeLiteral;
@Test
@ -214,7 +225,8 @@ public class JsonTest {
EnumInsideWithParser.Test.UNRECOGNIZED);
}
private abstract static class UpperCamelCasedType {
@AutoValue
abstract static class UpperCamelCasedType {
abstract String id();
@Nullable abstract Map<String, String> volumes();
@ -222,7 +234,7 @@ public class JsonTest {
// Currently, this only works for deserialization. Need to set a naming policy for serialization!
@SerializedNames({ "Id", "Volumes" })
private static UpperCamelCasedType create(String id, Map<String, String> volumes) {
return new UpperCamelCasedTypeImpl(id, volumes);
return new AutoValue_JsonTest_UpperCamelCasedType(id, volumes);
}
}
@ -251,36 +263,75 @@ public class JsonTest {
UpperCamelCasedType.create("1234", null));
}
private static class UpperCamelCasedTypeImpl extends UpperCamelCasedType {
private final String id;
private final Map<String, String> volumes;
@AutoValue
abstract static class NestedCamelCasedType {
abstract UpperCamelCasedType item();
private UpperCamelCasedTypeImpl(String id, Map<String, String> volumes) {
this.id = id;
this.volumes = volumes;
abstract List<UpperCamelCasedType> items();
// Currently, this only works for deserialization. Need to set a naming policy for serialization!
@SerializedNames({ "Item", "Items" })
private static NestedCamelCasedType create(UpperCamelCasedType item, List<UpperCamelCasedType> items) {
return new AutoValue_JsonTest_NestedCamelCasedType(item, items);
}
}
@Override String id() {
return id;
private final NestedCamelCasedType nested = NestedCamelCasedType
.create(UpperCamelCasedType.create("1234", Collections.<String, String>emptyMap()),
Arrays.asList(UpperCamelCasedType.create("5678", ImmutableMap.of("Foo", "Bar"))));
public void nestedCamelCasedType() {
Json json = Guice.createInjector(new GsonModule(), new AbstractModule() {
@Override protected void configure() {
bind(FieldNamingStrategy.class).toInstance(FieldNamingPolicy.UPPER_CAMEL_CASE);
}
}).getInstance(Json.class);
String spinalJson = "{\"Item\":{\"Id\":\"1234\",\"Volumes\":{}},\"Items\":[{\"Id\":\"5678\",\"Volumes\":{\"Foo\":\"Bar\"}}]}";
assertEquals(json.toJson(nested), spinalJson);
assertEquals(json.fromJson(spinalJson, NestedCamelCasedType.class), nested);
}
@Override @Nullable Map<String, String> volumes() {
return volumes;
public void nestedCamelCasedTypeOverriddenTypeAdapterFactory() {
Json json = Guice.createInjector(new GsonModule(), new AbstractModule() {
@Override protected void configure() {
}
@Override public boolean equals(Object o) {
if (o == this) {
return true;
@Provides public Set<TypeAdapterFactory> typeAdapterFactories() {
return ImmutableSet.<TypeAdapterFactory>of(new NestedCamelCasedTypeAdapterFactory());
}
if (o instanceof UpperCamelCasedType) {
UpperCamelCasedType that = (UpperCamelCasedType) o;
return Objects.equal(this.id, that.id()) && Objects.equal(this.volumes, that.volumes());
}
return false;
}).getInstance(Json.class);
assertEquals(json.toJson(nested), "{\"id\":\"1234\",\"count\":1}");
assertEquals(json.fromJson("{\"id\":\"1234\",\"count\":1}", NestedCamelCasedType.class), nested);
}
@Override public int hashCode() {
return Objects.hashCode(id, volumes);
private class NestedCamelCasedTypeAdapterFactory extends TypeAdapter<NestedCamelCasedType>
implements TypeAdapterFactory {
@Override public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
if (!(NestedCamelCasedType.class.isAssignableFrom(typeToken.getRawType()))) {
return null;
}
return (TypeAdapter<T>) this;
}
@Override public void write(JsonWriter out, NestedCamelCasedType value) throws IOException {
out.beginObject();
out.name("id").value(value.item().id());
out.name("count").value(value.items().size());
out.endObject();
}
@Override public NestedCamelCasedType read(JsonReader in) throws IOException {
in.beginObject();
in.nextName();
in.nextString();
in.nextName();
in.nextInt();
in.endObject();
return nested;
}
}
}

View File

@ -196,6 +196,7 @@
<surefire.version>2.17</surefire.version>
<assertj-core.version>1.6.1</assertj-core.version>
<assertj-guava.version>1.2.0</assertj-guava.version>
<auto-value.version>1.0-rc2</auto-value.version>
<http.proxyHost />
<http.proxyPort />
<jclouds.wire.httpstream.url>http://archive.apache.org/dist/commons/logging/binaries/commons-logging-1.1.1-bin.tar.gz</jclouds.wire.httpstream.url>
@ -290,6 +291,11 @@
<artifactId>assertj-guava</artifactId>
<version>${assertj-guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value</artifactId>
<version>${auto-value.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>