mirror of https://github.com/apache/jclouds.git
JCLOUDS-750 support serializing auto-value types without custom type adapters.
This commit is contained in:
parent
2a43b3a90d
commit
83604e943a
|
@ -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>
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue