diff --git a/core/pom.xml b/core/pom.xml
index 3b9589c740..2d0d53ccda 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -108,6 +108,11 @@
4.2.0
provided
+
+ com.google.auto.value
+ auto-value
+ test
+
diff --git a/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java
index 87e9d32846..de985fd8fc 100644
--- a/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java
+++ b/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java
@@ -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 TypeAdapter create(Gson gson, TypeToken type) {
com.google.common.reflect.TypeToken token = typeToken(type.getType());
- Invokable deserializationCtor = constructorFieldNamingPolicy.getDeserializer(token);
+ Invokable 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(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) TypeToken.get(Class.forName(autoClassName));
+ } catch (ClassNotFoundException ignored) {
+ }
+ }
+ return new DeserializeIntoParameterizedConstructor(delegateFactory.create(gson, type), deserializationTarget,
+ getParameterReaders(gson, deserializationTarget));
}
private final class DeserializeIntoParameterizedConstructor extends TypeAdapter {
diff --git a/core/src/test/java/org/jclouds/json/JsonTest.java b/core/src/test/java/org/jclouds/json/JsonTest.java
index 6a0b6aa360..cf477b482f 100644
--- a/core/src/test/java/org/jclouds/json/JsonTest.java
+++ b/core/src/test/java/org/jclouds/json/JsonTest.java
@@ -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 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 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 volumes;
+ @AutoValue
+ abstract static class NestedCamelCasedType {
+ abstract UpperCamelCasedType item();
- private UpperCamelCasedTypeImpl(String id, Map volumes) {
- this.id = id;
- this.volumes = volumes;
+ abstract List 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 items) {
+ return new AutoValue_JsonTest_NestedCamelCasedType(item, items);
}
+ }
- @Override String id() {
- return id;
- }
+ private final NestedCamelCasedType nested = NestedCamelCasedType
+ .create(UpperCamelCasedType.create("1234", Collections.emptyMap()),
+ Arrays.asList(UpperCamelCasedType.create("5678", ImmutableMap.of("Foo", "Bar"))));
- @Override @Nullable Map volumes() {
- return volumes;
- }
-
- @Override public boolean equals(Object o) {
- if (o == this) {
- return true;
+ public void nestedCamelCasedType() {
+ Json json = Guice.createInjector(new GsonModule(), new AbstractModule() {
+ @Override protected void configure() {
+ bind(FieldNamingStrategy.class).toInstance(FieldNamingPolicy.UPPER_CAMEL_CASE);
}
- if (o instanceof UpperCamelCasedType) {
- UpperCamelCasedType that = (UpperCamelCasedType) o;
- return Objects.equal(this.id, that.id()) && Objects.equal(this.volumes, that.volumes());
+ }).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);
+ }
+
+ public void nestedCamelCasedTypeOverriddenTypeAdapterFactory() {
+ Json json = Guice.createInjector(new GsonModule(), new AbstractModule() {
+ @Override protected void configure() {
}
- return false;
+
+ @Provides public Set typeAdapterFactories() {
+ return ImmutableSet.of(new NestedCamelCasedTypeAdapterFactory());
+ }
+ }).getInstance(Json.class);
+
+ assertEquals(json.toJson(nested), "{\"id\":\"1234\",\"count\":1}");
+ assertEquals(json.fromJson("{\"id\":\"1234\",\"count\":1}", NestedCamelCasedType.class), nested);
+ }
+
+ private class NestedCamelCasedTypeAdapterFactory extends TypeAdapter
+ implements TypeAdapterFactory {
+
+ @Override public TypeAdapter create(Gson gson, TypeToken typeToken) {
+ if (!(NestedCamelCasedType.class.isAssignableFrom(typeToken.getRawType()))) {
+ return null;
+ }
+ return (TypeAdapter) this;
}
- @Override public int hashCode() {
- return Objects.hashCode(id, volumes);
+ @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;
}
}
}
diff --git a/project/pom.xml b/project/pom.xml
index 566d7f9bb0..a621e1b96a 100644
--- a/project/pom.xml
+++ b/project/pom.xml
@@ -196,6 +196,7 @@
2.17
1.6.1
1.2.0
+ 1.0-rc2
http://archive.apache.org/dist/commons/logging/binaries/commons-logging-1.1.1-bin.tar.gz
@@ -290,6 +291,11 @@
assertj-guava
${assertj-guava.version}
+
+ com.google.auto.value
+ auto-value
+ ${auto-value.version}
+