refactored json internals to use Reflections2

This commit is contained in:
Adrian Cole 2013-01-20 22:40:33 -08:00
parent a625127fd2
commit d5f7f8b07e
29 changed files with 1134 additions and 874 deletions

View File

@ -26,12 +26,10 @@ import javax.inject.Inject;
import org.jclouds.date.DateService;
import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.json.config.GsonModule.Iso8601DateAdapter;
import org.jclouds.json.internal.IgnoreNullIterableTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.IterableTypeAdapterFactory;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
@ -47,15 +45,14 @@ public class CloudStackParserModule extends AbstractModule {
@Override
protected void configure() {
bind(DateAdapter.class).to(CloudStackDateAdapter.class);
bind(IgnoreNullIterableTypeAdapterFactory.class).to(CommaDelimitedOKIgnoreNullIterableTypeAdapterFactory.class);
bind(IterableTypeAdapterFactory.class).to(CommaDelimitedOKIterableTypeAdapterFactory.class);
}
/**
* Data adapter for the date formats used by CloudStack.
*
* Essentially this is a workaround for the CloudStack getUsage() API call returning a corrupted
* form of ISO-8601 dates, which have an unexpected pair of apostrophes, like
* 2011-12-12'T'00:00:00+00:00
* Essentially this is a workaround for the CloudStack getUsage() API call returning a corrupted form of ISO-8601
* dates, which have an unexpected pair of apostrophes, like 2011-12-12'T'00:00:00+00:00
*
* @author Richard Downer
*/
@ -77,39 +74,38 @@ public class CloudStackParserModule extends AbstractModule {
*
* @author Adrian Cole
*/
public static class CommaDelimitedOKIgnoreNullIterableTypeAdapterFactory extends IgnoreNullIterableTypeAdapterFactory {
public static class CommaDelimitedOKIterableTypeAdapterFactory extends IterableTypeAdapterFactory {
@Override
protected <E> TypeAdapter<Iterable<E>> newIterableAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Iterable<E>>() {
public void write(JsonWriter out, Iterable<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new Adapter<E>(elementAdapter);
}
@SuppressWarnings("unchecked")
public Iterable<E> read(JsonReader in) throws IOException {
// HACK as cloudstack changed a field from String to Set!
if (in.peek() == JsonToken.STRING) {
String val = Strings.emptyToNull(in.nextString());
return (Iterable<E>) (val != null ? Splitter.on(',').split(val) : ImmutableSet.of());
} else {
Builder<E> builder = ImmutableList.<E> builder();
in.beginArray();
while (in.hasNext()) {
E element = elementAdapter.read(in);
if (element != null)
builder.add(element);
}
in.endArray();
return builder.build();
}
public static final class Adapter<E> extends TypeAdapter<Iterable<E>> {
private final IterableTypeAdapterFactory.IterableTypeAdapter<E> delegate;
public Adapter(TypeAdapter<E> elementAdapter) {
this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter<E>(elementAdapter);
nullSafe();
}
public void write(JsonWriter out, Iterable<E> value) throws IOException {
this.delegate.write(out, value);
}
@SuppressWarnings("unchecked")
@Override
public Iterable<E> read(JsonReader in) throws IOException {
// HACK as cloudstack changed a field from String to Set!
if (in.peek() == JsonToken.STRING) {
String val = Strings.emptyToNull(in.nextString());
return (Iterable<E>) (val != null ? Splitter.on(',').split(val) : ImmutableSet.of());
} else {
return delegate.read(in);
}
}.nullSafe();
}
}
}
}

View File

@ -21,37 +21,30 @@ package org.jclouds.openstack.keystone.v2_0.config;
import static com.google.common.base.Preconditions.checkState;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
import org.jclouds.json.config.GsonModule;
import org.jclouds.json.config.GsonModule.DateAdapter;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.SetTypeAdapterFactory;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.common.collect.ImmutableSet.Builder;
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.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.google.inject.AbstractModule;
import com.google.inject.TypeLiteral;
/**
* @author Adam Lowe
*/
public class KeystoneParserModule extends AbstractModule {
@Override
protected void configure() {
bind(DateAdapter.class).to(GsonModule.Iso8601DateAdapter.class);
bind(new TypeLiteral<Set<TypeAdapterFactory>>() {
}).toInstance(ImmutableSet.<TypeAdapterFactory>of(new SetTypeAdapterFactory()));
bind(SetTypeAdapterFactory.class).to(ValuesSetTypeAdapterFactory.class);
}
/**
@ -60,61 +53,49 @@ public class KeystoneParserModule extends AbstractModule {
* <p/>
* Treats [A,B,C] and {"values"=[A,B,C], "someotherstuff"=...} as the same Set
*/
public static class SetTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Set.class || !(type instanceof ParameterizedType)) {
return null;
}
public static class ValuesSetTypeAdapterFactory extends SetTypeAdapterFactory {
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return TypeAdapter.class.cast(newSetAdapter(elementAdapter));
@Override
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new Adapter<E>(elementAdapter);
}
private <E> TypeAdapter<Set<E>> newSetAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Set<E>>() {
public void write(JsonWriter out, Set<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
public static final class Adapter<E> extends TypeAdapter<Set<E>> {
public Set<E> read(JsonReader in) throws IOException {
Set<E> result = Sets.newLinkedHashSet();
if (in.peek() == JsonToken.BEGIN_OBJECT) {
boolean foundValues = false;
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (Objects.equal("values", name)) {
foundValues = true;
readArray(in, result);
} else {
in.skipValue();
}
}
checkState(foundValues, "Expected BEGIN_ARRAY or the object to contain an array called 'values'");
in.endObject();
} else {
readArray(in, result);
}
private final SetTypeAdapterFactory.SetTypeAdapter<E> delegate;
return result;
}
public Adapter(TypeAdapter<E> elementAdapter) {
this.delegate = new SetTypeAdapterFactory.SetTypeAdapter<E>(elementAdapter);
nullSafe();
}
private void readArray(JsonReader in, Set<E> result) throws IOException {
in.beginArray();
public void write(JsonWriter out, Set<E> value) throws IOException {
this.delegate.write(out, value);
}
@Override
public Set<E> read(JsonReader in) throws IOException {
if (in.peek() == JsonToken.BEGIN_OBJECT) {
Builder<E> builder = ImmutableSet.<E>builder();
boolean foundValues = false;
in.beginObject();
while (in.hasNext()) {
E element = elementAdapter.read(in);
result.add(element);
String name = in.nextName();
if (Objects.equal("values", name)) {
foundValues = true;
builder.addAll(delegate.read(in));
} else {
in.skipValue();
}
}
in.endArray();
checkState(foundValues, "Expected BEGIN_ARRAY or the object to contain an array called 'values'");
in.endObject();
return builder.build();
} else {
return delegate.read(in);
}
}.nullSafe();
}
}
}
}

View File

@ -44,10 +44,6 @@ import com.google.common.collect.Iterables;
*/
@Beta
public class PaginatedCollection<T> extends IterableWithMarker<T> {
@SuppressWarnings({ "rawtypes", "unchecked" })
public static final PaginatedCollection EMPTY = new PaginatedCollection(ImmutableSet.of(), ImmutableSet.of());
private Iterable<T> resources;
private Iterable<Link> links;

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseTenants extends ParseJson<Tenants<? extends Tenant>> {
static class Tenants<T extends Tenant> extends PaginatedCollection<T> {
public class ParseTenants extends ParseJson<Tenants> {
static class Tenants extends PaginatedCollection<Tenant> {
@ConstructorProperties({ "tenants", "tenants_links" })
protected Tenants(Iterable<T> tenants, Iterable<Link> tenants_links) {
protected Tenants(Iterable<Tenant> tenants, Iterable<Link> tenants_links) {
super(tenants, tenants_links);
}
@ -60,8 +60,7 @@ public class ParseTenants extends ParseJson<Tenants<? extends Tenant>> {
@Inject
public ParseTenants(Json json) {
super(json, new TypeLiteral<Tenants<? extends Tenant>>() {
});
super(json, TypeLiteral.get(Tenants.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Tenant, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseUsers extends ParseJson<Users<? extends User>> {
static class Users<T extends User> extends PaginatedCollection<T> {
public class ParseUsers extends ParseJson<Users> {
static class Users extends PaginatedCollection<User> {
@ConstructorProperties({ "users", "users_links" })
protected Users(Iterable<T> users, Iterable<Link> users_links) {
protected Users(Iterable<User> users, Iterable<Link> users_links) {
super(users, users_links);
}
@ -60,8 +60,7 @@ public class ParseUsers extends ParseJson<Users<? extends User>> {
@Inject
public ParseUsers(Json json) {
super(json, new TypeLiteral<Users<? extends User>>() {
});
super(json, TypeLiteral.get(Users.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<User, ToPagedIterable> {

View File

@ -0,0 +1,70 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.openstack.keystone.v2_0.functions.internal;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
import org.jclouds.json.config.GsonModule;
import org.jclouds.openstack.keystone.v2_0.config.KeystoneParserModule;
import org.jclouds.openstack.keystone.v2_0.domain.User;
import org.jclouds.openstack.keystone.v2_0.functions.internal.ParseUsers.Users;
import org.jclouds.util.Strings2;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.inject.Guice;
/**
*
* @author Adrian Cole
*/
@Test(testName = "ParseUsersTest")
public class ParseUsersTest {
private Gson gson = Guice.createInjector(new GsonModule(), new KeystoneParserModule()).getInstance(Gson.class);
private Type usersMapType = new TypeToken<Map<String, Set<? extends User>>>() {
private static final long serialVersionUID = 1L;
}.getType();
Set<User> expectedUsers = ImmutableSet.of(
User.builder().name("nova").id("e021dfd758eb44a89f1c57c8ef3be8e2").build(),
User.builder().name("glance").id("3f6c1c9ba993495ead7d2eb2192e284f").build(),
User.builder().name("demo").id("667b2e1420604df8b67cd8ea57d4ee64").build(),
User.builder().name("admin").id("2b9b606181634ae9ac86fd95a8bc2cde").build());
public void testParseUsersInMap() throws JsonSyntaxException, IOException {
String json = Strings2.toStringAndClose(getClass().getResourceAsStream("/user_list.json"));
Map<String, Set<? extends User>> users = gson.fromJson(json, usersMapType);
assertEquals(users.get("users"), expectedUsers);
}
public void testParseUsers() throws JsonSyntaxException, IOException {
String json = Strings2.toStringAndClose(getClass().getResourceAsStream("/user_list.json"));
Users users = gson.fromJson(json, Users.class);
assertEquals(users.toSet(), expectedUsers);
}
}

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseFlavorDetails extends ParseJson<Flavors<? extends Flavor>> {
static class Flavors<T extends Flavor> extends PaginatedCollection<T> {
public class ParseFlavorDetails extends ParseJson<Flavors> {
static class Flavors extends PaginatedCollection<Flavor> {
@ConstructorProperties({ "flavors", "flavors_links" })
protected Flavors(Iterable<T> flavors, Iterable<Link> flavors_links) {
protected Flavors(Iterable<Flavor> flavors, Iterable<Link> flavors_links) {
super(flavors, flavors_links);
}
@ -60,8 +60,7 @@ public class ParseFlavorDetails extends ParseJson<Flavors<? extends Flavor>> {
@Inject
public ParseFlavorDetails(Json json) {
super(json, new TypeLiteral<Flavors<? extends Flavor>>() {
});
super(json, TypeLiteral.get(Flavors.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Flavor, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseFlavors extends ParseJson<Flavors<? extends Resource>> {
static class Flavors<T extends Resource> extends PaginatedCollection<T> {
public class ParseFlavors extends ParseJson<Flavors> {
static class Flavors extends PaginatedCollection<Resource> {
@ConstructorProperties({ "flavors", "flavors_links" })
protected Flavors(Iterable<T> flavors, Iterable<Link> flavors_links) {
protected Flavors(Iterable<Resource> flavors, Iterable<Link> flavors_links) {
super(flavors, flavors_links);
}
@ -60,8 +60,7 @@ public class ParseFlavors extends ParseJson<Flavors<? extends Resource>> {
@Inject
public ParseFlavors(Json json) {
super(json, new TypeLiteral<Flavors<? extends Resource>>() {
});
super(json, TypeLiteral.get(Flavors.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Resource, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseImageDetails extends ParseJson<Images<? extends Image>> {
static class Images<T extends Image> extends PaginatedCollection<T> {
public class ParseImageDetails extends ParseJson<Images> {
static class Images extends PaginatedCollection<Image> {
@ConstructorProperties({ "images", "images_links" })
protected Images(Iterable<T> images, Iterable<Link> images_links) {
protected Images(Iterable<Image> images, Iterable<Link> images_links) {
super(images, images_links);
}
@ -60,8 +60,7 @@ public class ParseImageDetails extends ParseJson<Images<? extends Image>> {
@Inject
public ParseImageDetails(Json json) {
super(json, new TypeLiteral<Images<? extends Image>>() {
});
super(json, TypeLiteral.get(Images.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Image, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseImages extends ParseJson<Images<? extends Resource>> {
static class Images<T extends Resource> extends PaginatedCollection<T> {
public class ParseImages extends ParseJson<Images> {
static class Images extends PaginatedCollection<Resource> {
@ConstructorProperties({ "images", "images_links" })
protected Images(Iterable<T> images, Iterable<Link> images_links) {
protected Images(Iterable<Resource> images, Iterable<Link> images_links) {
super(images, images_links);
}
@ -60,8 +60,7 @@ public class ParseImages extends ParseJson<Images<? extends Resource>> {
@Inject
public ParseImages(Json json) {
super(json, new TypeLiteral<Images<? extends Resource>>() {
});
super(json, TypeLiteral.get(Images.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Resource, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseServerDetails extends ParseJson<Servers<? extends Server>> {
static class Servers<T extends Server> extends PaginatedCollection<T> {
public class ParseServerDetails extends ParseJson<Servers> {
static class Servers extends PaginatedCollection<Server> {
@ConstructorProperties({ "servers", "servers_links" })
protected Servers(Iterable<T> servers, Iterable<Link> servers_links) {
protected Servers(Iterable<Server> servers, Iterable<Link> servers_links) {
super(servers, servers_links);
}
@ -60,8 +60,7 @@ public class ParseServerDetails extends ParseJson<Servers<? extends Server>> {
@Inject
public ParseServerDetails(Json json) {
super(json, new TypeLiteral<Servers<? extends Server>>() {
});
super(json, TypeLiteral.get(Servers.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Server, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseServers extends ParseJson<Servers<? extends Resource>> {
static class Servers<T extends Resource> extends PaginatedCollection<T> {
public class ParseServers extends ParseJson<Servers> {
static class Servers extends PaginatedCollection<Resource> {
@ConstructorProperties({ "servers", "servers_links" })
protected Servers(Iterable<T> servers, Iterable<Link> servers_links) {
protected Servers(Iterable<Resource> servers, Iterable<Link> servers_links) {
super(servers, servers_links);
}
@ -60,8 +60,7 @@ public class ParseServers extends ParseJson<Servers<? extends Resource>> {
@Inject
public ParseServers(Json json) {
super(json, new TypeLiteral<Servers<? extends Resource>>() {
});
super(json, TypeLiteral.get(Servers.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Resource, ToPagedIterable> {

View File

@ -40,15 +40,16 @@ import org.jclouds.json.Json;
import org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactory;
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
import org.jclouds.json.internal.GsonWrapper;
import org.jclouds.json.internal.IgnoreNullFluentIterableTypeAdapterFactory;
import org.jclouds.json.internal.IgnoreNullIterableTypeAdapterFactory;
import org.jclouds.json.internal.IgnoreNullMapTypeAdapterFactory;
import org.jclouds.json.internal.IgnoreNullMultimapTypeAdapterFactory;
import org.jclouds.json.internal.IgnoreNullSetTypeAdapterFactory;
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.CollectionTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.FluentIterableTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.IterableTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MapTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MultimapTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.SetTypeAdapterFactory;
import org.jclouds.json.internal.NullHackJsonLiteralAdapter;
import org.jclouds.json.internal.OptionalTypeAdapterFactory;
@ -84,14 +85,13 @@ public class GsonModule extends AbstractModule {
@Provides
@Singleton
Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings,
OptionalTypeAdapterFactory optional, IgnoreNullSetTypeAdapterFactory set,
IgnoreNullMapTypeAdapterFactory map, IgnoreNullMultimapTypeAdapterFactory multimap,
IgnoreNullIterableTypeAdapterFactory iterable, IgnoreNullFluentIterableTypeAdapterFactory fluentIterable)
throws Exception {
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings,
OptionalTypeAdapterFactory optional, SetTypeAdapterFactory set, MapTypeAdapterFactory map,
MultimapTypeAdapterFactory multimap, IterableTypeAdapterFactory iterable,
CollectionTypeAdapterFactory collection, FluentIterableTypeAdapterFactory fluentIterable) throws Exception {
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(new ExtractSerializedName(),
new ExtractNamed());
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
new ExtractSerializedName(), new ExtractNamed()));
GsonBuilder builder = new GsonBuilder().setFieldNamingStrategy(serializationPolicy);
@ -104,18 +104,17 @@ public class GsonModule extends AbstractModule {
builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe());
builder.registerTypeAdapterFactory(optional);
builder.registerTypeAdapterFactory(iterable);
builder.registerTypeAdapterFactory(collection);
builder.registerTypeAdapterFactory(set);
builder.registerTypeAdapterFactory(map);
builder.registerTypeAdapterFactory(multimap);
builder.registerTypeAdapterFactory(fluentIterable);
AnnotationConstructorNamingStrategy deserializationPolicy =
new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
builder.registerTypeAdapterFactory(
new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
builder.registerTypeAdapterFactory(new DeserializationConstructorAndReflectiveTypeAdapterFactory(
new ConstructorConstructor(), serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
// complicated (serializers/deserializers as they need context to operate)
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue());

View File

@ -20,22 +20,25 @@ package org.jclouds.json.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.reflect.Reflection2.typeToken;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import org.jclouds.json.internal.NamingStrategies.ConstructorFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
import com.google.common.collect.Maps;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
@ -49,21 +52,20 @@ import com.google.gson.stream.JsonWriter;
* <p/>
* <ul>
* <li>Deserialization</li>
* If there's an annotation designating a parameterized constructor, invoke that for fields
* correlating to named parameter annotations. Otherwise, use {@link ConstructorConstructor}, and
* set fields via reflection.
* If there's an annotation designating a parameterized constructor, invoke that for fields correlating to named
* parameter annotations. Otherwise, use {@link ConstructorConstructor}, and set fields via reflection.
* <p/>
* Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and
* the empty object ({}) is treated as a null if the constructor for the object throws an NPE.
* <li>Serialization</li>
* Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor.
* Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and the empty object
* ({}) is treated as a null if the constructor for the object throws an NPE.
* <li>Serialization</li> Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor.
* </ul>
* <h3>Example: Using javax inject to select a constructor and corresponding named parameters</h3>
* <p/>
*
* <pre>
*
*
* import NamingStrategies.*;
*
*
* serializationStrategy = new AnnotationOrNameFieldNamingStrategy(
* new ExtractSerializedName(), new ExtractNamed());
*
@ -73,19 +75,20 @@ import com.google.gson.stream.JsonWriter;
*
* factory = new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
* serializationStrategy, Excluder.DEFAULT, deserializationStrategy);
*
*
* gson = new GsonBuilder(serializationStrategy).registerTypeAdapterFactory(factory).create();
*
*
* </pre>
* <p/>
* The above would work fine on the following class, which has no gson-specific annotations:
* <p/>
*
* <pre>
* private static class ImmutableAndVerifiedInCtor {
* final int foo;
* &#064;Named(&quot;_bar&quot;)
* final int bar;
*
*
* &#064;Inject
* ImmutableAndVerifiedInCtor(@Named(&quot;foo&quot;) int foo, @Named(&quot;_bar&quot;) int bar) {
* if (foo &lt; 0)
@ -97,54 +100,47 @@ import com.google.gson.stream.JsonWriter;
* </pre>
* <p/>
* <br/>
*
*
* @author Adrian Cole
* @author Adam Lowe
*/
public final class DeserializationConstructorAndReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorFieldNamingStrategy constructorFieldNamingPolicy;
private final AnnotationConstructorNamingStrategy constructorFieldNamingPolicy;
private final ReflectiveTypeAdapterFactory delegateFactory;
/**
* @param constructorConstructor passed through to delegate ReflectiveTypeAdapterFactory for serialization
* @param serializationFieldNamingPolicy passed through to delegate ReflectiveTypeAdapterFactory for serialization
* @param excluder passed through to delegate ReflectiveTypeAdapterFactory for serialization
* @param deserializationFieldNamingPolicy
* determines which constructor to use and how to determine field names for
* deserialization
* @see ReflectiveTypeAdapterFactory
*/
public DeserializationConstructorAndReflectiveTypeAdapterFactory(
ConstructorConstructor constructorConstructor,
FieldNamingStrategy serializationFieldNamingPolicy,
Excluder excluder,
ConstructorFieldNamingStrategy deserializationFieldNamingPolicy) {
this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy, "deserializationFieldNamingPolicy");
this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull(serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder"));
public DeserializationConstructorAndReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
FieldNamingStrategy serializationFieldNamingPolicy, Excluder excluder,
AnnotationConstructorNamingStrategy deserializationFieldNamingPolicy) {
this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy,
"deserializationFieldNamingPolicy");
this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull(
serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder"));
}
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) {
Class<? super T> raw = type.getRawType();
Constructor<? super T> deserializationCtor = constructorFieldNamingPolicy.getDeserializationConstructor(raw);
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);
if (deserializationCtor == null) {
return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create())
} else {
deserializationCtor.setAccessible(true);
return new DeserializeWithParameterizedConstructorSerializeWithDelegate<T>(delegateFactory.create(gson, type), deserializationCtor,
getParameterReaders(gson, type, deserializationCtor));
return new DeserializeIntoParameterizedConstructor<T>(delegateFactory.create(gson, type), deserializationCtor,
getParameterReaders(gson, deserializationCtor));
}
}
private final class DeserializeWithParameterizedConstructorSerializeWithDelegate<T> extends TypeAdapter<T> {
private final Constructor<? super T> parameterizedCtor;
private final class DeserializeIntoParameterizedConstructor<T> extends TypeAdapter<T> {
private final TypeAdapter<T> serializer;
private final Invokable<T, T> parameterizedCtor;
private final Map<String, ParameterReader<?>> parameterReaders;
private final TypeAdapter<T> delegate;
private DeserializeWithParameterizedConstructorSerializeWithDelegate(TypeAdapter<T> delegate,
Constructor<? super T> parameterizedCtor, Map<String, ParameterReader<?>> parameterReaders) {
this.delegate = delegate;
this.parameterizedCtor = parameterizedCtor;
private DeserializeIntoParameterizedConstructor(TypeAdapter<T> serializer, Invokable<T, T> deserializationCtor,
Map<String, ParameterReader<?>> parameterReaders) {
this.serializer = serializer;
this.parameterizedCtor = deserializationCtor;
this.parameterReaders = parameterReaders;
}
@ -155,16 +151,16 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
return null;
}
Class<?>[] paramTypes = parameterizedCtor.getParameterTypes();
Object[] ctorParams = new Object[paramTypes.length];
List<Parameter> params = parameterizedCtor.getParameters();
Object[] values = new Object[params.size()];
boolean empty = true;
// Set all primitive constructor params to defaults
for (int i = 0; i < paramTypes.length; i++) {
if (paramTypes[i] == boolean.class) {
ctorParams[i] = Boolean.FALSE;
} else if (paramTypes[i].isPrimitive()) {
ctorParams[i] = 0;
for (Parameter param : params) {
if (param.getType().getRawType() == boolean.class) {
values[param.hashCode()] = Boolean.FALSE;
} else if (param.getType().getRawType().isPrimitive()) {
values[param.hashCode()] = 0;
}
}
@ -178,26 +174,27 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
in.skipValue();
} else {
Object value = parameter.read(in);
if (value != null) ctorParams[parameter.index] = value;
if (value != null)
values[parameter.position] = value;
}
}
} catch (IllegalStateException e) {
throw new JsonSyntaxException(e);
}
for (int i = 0; i < paramTypes.length; i++) {
if (paramTypes[i].isPrimitive()) {
checkArgument(ctorParams[i] != null, "Primitive param[" + i + "] in constructor " + parameterizedCtor
+ " cannot be absent!");
for (Parameter param : params) {
if (param.getType().getRawType().isPrimitive()) {
checkArgument(values[param.hashCode()] != null, "Primitive param[" + param.hashCode()
+ "] in constructor " + parameterizedCtor + " cannot be absent!");
}
}
in.endObject();
try {
return newInstance(ctorParams);
return newInstance(values);
} catch (NullPointerException ex) {
// If {} was found and constructor threw NPE, we treat the field as null
if (empty && paramTypes.length > 0) {
if (empty && values.length > 0) {
return null;
}
throw ex;
@ -209,15 +206,12 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
*/
@Override
public void write(JsonWriter out, T value) throws IOException {
delegate.write(out, value);
serializer.write(out, value);
}
@SuppressWarnings("unchecked")
private T newInstance(Object[] ctorParams) throws AssertionError {
try {
return (T) parameterizedCtor.newInstance(ctorParams);
} catch (InstantiationException e) {
throw new AssertionError(e);
return (T) parameterizedCtor.invoke(null, ctorParams);
} catch (IllegalAccessException e) {
throw new AssertionError(e);
} catch (InvocationTargetException e) {
@ -226,43 +220,79 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
throw new AssertionError(e);
}
}
@Override
public int hashCode() {
return Objects.hashCode(serializer, parameterizedCtor, parameterReaders);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
DeserializeIntoParameterizedConstructor<?> that = DeserializeIntoParameterizedConstructor.class.cast(obj);
return Objects.equal(this.serializer, that.serializer)
&& Objects.equal(this.parameterizedCtor, that.parameterizedCtor)
&& Objects.equal(this.parameterReaders, that.parameterReaders);
}
@Override
public String toString() {
return Objects.toStringHelper(this).add("parameterizedCtor", parameterizedCtor)
.add("parameterReaders", parameterReaders).add("serializer", serializer).toString();
}
}
// logic borrowed from ReflectiveTypeAdapterFactory
static class ParameterReader<T> {
final String name;
final int index;
final int position;
final TypeAdapter<T> typeAdapter;
ParameterReader(String name, int index, TypeAdapter<T> typeAdapter) {
ParameterReader(int position, String name, TypeAdapter<T> typeAdapter) {
this.name = name;
this.index = index;
this.position = position;
this.typeAdapter = typeAdapter;
}
public Object read(JsonReader reader) throws IOException {
return typeAdapter.read(reader);
}
}
private Map<String, ParameterReader<?>> getParameterReaders(Gson context, TypeToken<?> declaring, Constructor<?> constructor) {
Map<String, ParameterReader<?>> result = Maps.newLinkedHashMap();
for (int index = 0; index < constructor.getGenericParameterTypes().length; index++) {
Type parameterType = getTypeOfConstructorParameter(declaring, constructor, index);
TypeAdapter<?> adapter = context.getAdapter(TypeToken.get(parameterType));
String parameterName = constructorFieldNamingPolicy.translateName(constructor, index);
checkArgument(parameterName != null, constructor + " parameter " + 0 + " failed to be named by " + constructorFieldNamingPolicy);
@SuppressWarnings({ "rawtypes", "unchecked" })
ParameterReader<?> parameterReader = new ParameterReader(parameterName, index, adapter);
ParameterReader<?> previous = result.put(parameterReader.name, parameterReader);
checkArgument(previous == null, constructor + " declares multiple JSON parameters named " + parameterReader.name);
@Override
public boolean equals(Object obj) {
if (obj instanceof ParameterReader) {
ParameterReader<?> that = ParameterReader.class.cast(obj);
return position == that.position && name.equals(that.name);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(position, name);
}
@Override
public String toString() {
return typeAdapter + " arg" + position;
}
return result;
}
private Type getTypeOfConstructorParameter(TypeToken<?> declaring, Constructor<?> constructor, int index) {
Type genericParameter = constructor.getGenericParameterTypes()[index];
return $Gson$Types.resolve(declaring.getType(), declaring.getRawType(), genericParameter);
private <T> Map<String, ParameterReader<?>> getParameterReaders(Gson context, Invokable<T, T> deserializationCtor) {
Builder<String, ParameterReader<?>> result = ImmutableMap.builder();
for (Parameter param : deserializationCtor.getParameters()) {
TypeAdapter<?> adapter = context.getAdapter(TypeToken.get(param.getType().getType()));
String parameterName = constructorFieldNamingPolicy.translateName(deserializationCtor, param.hashCode());
checkArgument(parameterName != null, deserializationCtor + " parameter " + 0 + " failed to be named by "
+ constructorFieldNamingPolicy);
@SuppressWarnings({ "rawtypes", "unchecked" })
ParameterReader<?> parameterReader = new ParameterReader(param.hashCode(), parameterName, adapter);
result.put(parameterReader.name, parameterReader);
}
return result.build();
}
}

View File

@ -18,13 +18,11 @@
*/
package org.jclouds.json.internal;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.ExecutionException;
import static org.jclouds.reflect.Reflection2.method;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.lang.reflect.Type;
import com.google.common.reflect.Invokable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
@ -48,32 +46,11 @@ public class EnumTypeAdapterThatReturnsFromValue<T extends Enum<T>> implements J
return (T) Enum.valueOf((Class<T>) classOfT, json.getAsString());
} catch (IllegalArgumentException e) {
try {
Method converter = classToConvert.get((Class<?>) classOfT);
Invokable<?, Object> converter = method((Class<?>) classOfT, "fromValue", String.class);
return (T) converter.invoke(null, json.getAsString());
} catch (Exception e1) {
throw e;
}
}
}
private static final LoadingCache<Class<?>, Method> classToConvert = CacheBuilder.newBuilder()
.build(new CacheLoader<Class<?>, Method>() {
@Override
public Method load(Class<?> from) throws ExecutionException {
try {
Method method = from.getMethod("fromValue", String.class);
method.setAccessible(true);
return method;
} catch (Exception e) {
throw new ExecutionException(e);
}
}
});
@Override
public String toString() {
return EnumTypeAdapterThatReturnsFromValue.class.getSimpleName();
}
}

View File

@ -1,78 +0,0 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
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;
/**
* Eliminates null values when deserializing FluentIterables
* <p/>
* Treats [null] as the empty set; [A, null] as [A]; etc.
*
* @author Adam Lowe
*/
public class IgnoreNullFluentIterableTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != FluentIterable.class || !(type instanceof ParameterizedType)) {
return null;
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) newFluentIterableAdapter(elementAdapter);
}
protected <E> TypeAdapter<FluentIterable<E>> newFluentIterableAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<FluentIterable<E>>() {
public void write(JsonWriter out, FluentIterable<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
public FluentIterable<E> read(JsonReader in) throws IOException {
in.beginArray();
Builder<E> builder = ImmutableList.<E>builder();
while (in.hasNext()) {
E element = elementAdapter.read(in);
if (element != null) builder.add(element);
}
in.endArray();
return FluentIterable.from(builder.build());
}
}.nullSafe();
}
}

View File

@ -1,77 +0,0 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
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;
/**
* Eliminates null values when deserializing Iterables
* <p/>
* Treats [null] as the empty set; [A, null] as [A]; etc.
*
* @author Adam Lowe
*/
public class IgnoreNullIterableTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Iterable.class || !(type instanceof ParameterizedType)) {
return null;
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) newIterableAdapter(elementAdapter);
}
protected <E> TypeAdapter<Iterable<E>> newIterableAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Iterable<E>>() {
public void write(JsonWriter out, Iterable<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
public Iterable<E> read(JsonReader in) throws IOException {
in.beginArray();
Builder<E> builder = ImmutableList.<E>builder();
while (in.hasNext()) {
E element = elementAdapter.read(in);
if (element != null) builder.add(element);
}
in.endArray();
return builder.build();
}
}.nullSafe();
}
}

View File

@ -1,82 +0,0 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Eliminates null values when deserializing Maps
* <p/>
* Treats {"a":null} as the empty map; {"a":1, "b":null} as {"a":1}; etc.
*
* @author Adam Lowe
*/
public class IgnoreNullMapTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Map.class || !(type instanceof ParameterizedType)) {
return null;
}
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return (TypeAdapter<T>) newMapAdapter(keyAdapter, valueAdapter);
}
protected <K,V> TypeAdapter<Map<K, V>> newMapAdapter(final TypeAdapter<K> keyAdapter, final TypeAdapter<V> valueAdapter) {
return new TypeAdapter<Map<K, V>>() {
public void write(JsonWriter out, Map<K, V> value) throws IOException {
out.beginObject();
for (Map.Entry<K, V> element : value.entrySet()) {
out.name(String.valueOf(element.getKey()));
valueAdapter.write(out, element.getValue());
}
out.endObject();
}
public Map<K, V> read(JsonReader in) throws IOException {
Map<K, V> result = Maps.newLinkedHashMap();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
V value = valueAdapter.read(in);
if (value != null) result.put(name, value);
}
in.endObject();
return result;
}
}.nullSafe();
}
}

View File

@ -1,90 +0,0 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Parses Multimaps to/from json - strips out any null values when deserializing
*
* @author Adam Lowe
*/
public class IgnoreNullMultimapTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if ((typeToken.getRawType() != Multimap.class) || !(type instanceof ParameterizedType)) {
return null;
}
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return (TypeAdapter<T>) newMultimapAdapter(keyAdapter, valueAdapter);
}
protected <K,V> TypeAdapter<Multimap<K, V>> newMultimapAdapter(final TypeAdapter<K> keyAdapter, final TypeAdapter<V> valueAdapter) {
return new TypeAdapter<Multimap<K, V>>() {
public void write(JsonWriter out, Multimap<K, V> map) throws IOException {
out.beginObject();
for (K key : map.keySet()) {
out.name(String.valueOf(key));
out.beginArray();
for (V value : map.get(key)) {
valueAdapter.write(out, value);
}
out.endArray();
}
out.endObject();
}
public Multimap<K, V> read(JsonReader in) throws IOException {
ImmutableMultimap.Builder<K, V> result = ImmutableListMultimap.builder();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
in.beginArray();
while (in.hasNext()) {
V value = valueAdapter.read(in);
if (value != null) result.put(name, value);
}
in.endArray();
}
in.endObject();
return result.build();
}
}.nullSafe();
}
}

View File

@ -1,78 +0,0 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
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;
/**
* Eliminates null values when deserializing Sets.
* <p/>
* Treats [null] as the empty set; [A, null] as [A]; etc.
*
* @author Adam Lowe
*/
public class IgnoreNullSetTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Set.class || !(type instanceof ParameterizedType)) {
return null;
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) newSetAdapter(elementAdapter);
}
protected <E> TypeAdapter<Set<E>> newSetAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Set<E>>() {
public void write(JsonWriter out, Set<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
public Set<E> read(JsonReader in) throws IOException {
ImmutableSet.Builder<E> result = ImmutableSet.<E> builder();
in.beginArray();
while (in.hasNext()) {
E element = elementAdapter.read(in);
if (element != null)
result.add(element);
}
in.endArray();
return result.build();
}
}.nullSafe();
}
}

View File

@ -20,49 +20,74 @@ package org.jclouds.json.internal;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.in;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Iterables.tryFind;
import static org.jclouds.reflect.Reflection2.constructors;
import java.beans.ConstructorProperties;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import javax.inject.Named;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
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.FieldNamingStrategy;
import com.google.gson.annotations.SerializedName;
/**
* NamingStrategies used for JSON deserialization using GSON
*
*
* @author Adrian Cole
* @author Adam Lowe
*/
public class NamingStrategies {
/**
* Specifies how to extract the name from an annotation for use in determining the serialized
* name.
*
* Specifies how to extract the name from an annotation for use in determining the serialized name.
*
* @see com.google.gson.annotations.SerializedName
* @see ExtractSerializedName
*/
public abstract static class NameExtractor<A extends Annotation> {
public abstract static class NameExtractor<A extends Annotation> implements Function<Annotation, String>,
Supplier<Predicate<Annotation>> {
protected final Class<A> annotationType;
protected final Predicate<Annotation> predicate;
protected NameExtractor(Class<A> annotationType) {
protected NameExtractor(final Class<A> annotationType) {
this.annotationType = checkNotNull(annotationType, "annotationType");
this.predicate = new Predicate<Annotation>() {
public boolean apply(Annotation input) {
return input.getClass().equals(annotationType);
}
};
}
public abstract String extractName(A in);
@SuppressWarnings("unchecked")
public Class<Annotation> annotationType() {
return (Class<Annotation>) annotationType;
}
public Class<A> annotationType() {
return annotationType;
@Override
public String apply(Annotation in) {
return extractName(annotationType.cast(in));
}
protected abstract String extractName(A cast);
@Override
public Predicate<Annotation> get() {
return predicate;
}
@Override
@ -90,7 +115,6 @@ public class NamingStrategies {
super(SerializedName.class);
}
@Override
public String extractName(SerializedName in) {
return checkNotNull(in, "input annotation").value();
}
@ -108,22 +132,22 @@ public class NamingStrategies {
}
public abstract static class AnnotationBasedNamingStrategy {
protected final Map<Class<? extends Annotation>, ? extends NameExtractor> annotationToNameExtractor;
private String forToString;
protected final Map<Class<? extends Annotation>, ? extends NameExtractor<?>> annotationToNameExtractor;
protected final String forToString;
@SuppressWarnings("unchecked")
public AnnotationBasedNamingStrategy(Iterable<? extends NameExtractor> extractors) {
public AnnotationBasedNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) {
checkNotNull(extractors, "means to extract names by annotations");
this.annotationToNameExtractor = Maps.uniqueIndex(extractors, new Function<NameExtractor, Class<? extends Annotation>>() {
this.annotationToNameExtractor = Maps.uniqueIndex(extractors,
new Function<NameExtractor<?>, Class<? extends Annotation>>() {
@Override
public Class<? extends Annotation> apply(NameExtractor<?> input) {
return input.annotationType();
}
});
this.forToString = Joiner.on(",").join(transform(extractors, new Function<NameExtractor<?>, String>() {
@Override
public Class<? extends Annotation> apply(NameExtractor input) {
return input.annotationType();
}
});
this.forToString = Joiner.on(",").join(Iterables.transform(extractors, new Function<NameExtractor, String>() {
@Override
public String apply(NameExtractor input) {
public String apply(NameExtractor<?> input) {
return input.annotationType().getName();
}
}));
@ -138,32 +162,30 @@ public class NamingStrategies {
/**
* Definition of field naming policy for annotation-based field
*/
public static class AnnotationFieldNamingStrategy extends AnnotationBasedNamingStrategy implements FieldNamingStrategy {
public static class AnnotationFieldNamingStrategy extends AnnotationBasedNamingStrategy implements
FieldNamingStrategy {
public AnnotationFieldNamingStrategy(Iterable<? extends NameExtractor> extractors) {
public AnnotationFieldNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) {
super(extractors);
checkArgument(extractors.iterator().hasNext(), "you must supply at least one name extractor, for example: "
+ ExtractSerializedName.class.getSimpleName());
}
@SuppressWarnings("unchecked")
@Override
public String translateName(Field f) {
for (Annotation annotation : f.getAnnotations()) {
if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
return annotationToNameExtractor.get(annotation.annotationType()).extractName(annotation);
return annotationToNameExtractor.get(annotation.annotationType()).apply(annotation);
}
}
return null;
}
}
public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements FieldNamingStrategy {
public AnnotationOrNameFieldNamingStrategy(NameExtractor... extractors) {
this(ImmutableSet.copyOf(extractors));
}
public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements
FieldNamingStrategy {
public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor> extractors) {
public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) {
super(extractors);
}
@ -174,37 +196,42 @@ public class NamingStrategies {
}
}
public static interface ConstructorFieldNamingStrategy {
public String translateName(Constructor<?> c, int index);
public <T> Constructor<? super T> getDeserializationConstructor(Class<?> raw);
}
/**
* Determines field naming from constructor annotations
*/
public static class AnnotationConstructorNamingStrategy extends AnnotationBasedNamingStrategy implements ConstructorFieldNamingStrategy {
private final Set<Class<? extends Annotation>> markers;
public final static class AnnotationConstructorNamingStrategy extends AnnotationBasedNamingStrategy {
private final Predicate<Invokable<?, ?>> hasMarker;
private final Collection<? extends Class<? extends Annotation>> markers;
public AnnotationConstructorNamingStrategy(Iterable<? extends Class<? extends Annotation>> markers, Iterable<? extends NameExtractor> extractors) {
public AnnotationConstructorNamingStrategy(Collection<? extends Class<? extends Annotation>> markers,
Iterable<? extends NameExtractor<?>> extractors) {
super(extractors);
this.markers = ImmutableSet.copyOf(checkNotNull(markers, "you must supply at least one annotation to mark deserialization constructors"));
this.markers = checkNotNull(markers,
"you must supply at least one annotation to mark deserialization constructors");
this.hasMarker = hasAnnotationIn(markers);
}
@SuppressWarnings("unchecked")
public <T> Constructor<? super T> getDeserializationConstructor(Class<?> raw) {
for (Constructor<?> ctor : raw.getDeclaredConstructors())
for (Class<? extends Annotation> deserializationCtorAnnotation : markers)
if (ctor.isAnnotationPresent(deserializationCtorAnnotation))
return (Constructor<T>) ctor;
return null;
private static Predicate<Invokable<?, ?>> hasAnnotationIn(
final Collection<? extends Class<? extends Annotation>> markers) {
return new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> input) {
return FluentIterable.from(Arrays.asList(input.getAnnotations()))
.transform(new Function<Annotation, Class<? extends Annotation>>() {
public Class<? extends Annotation> apply(Annotation input) {
return input.annotationType();
}
}).anyMatch(in(markers));
}
};
}
@SuppressWarnings("unchecked")
@Override
public String translateName(Constructor<?> c, int index) {
@VisibleForTesting
<T> Invokable<T, T> getDeserializer(TypeToken<T> token) {
return tryFind(constructors(token), hasMarker).orNull();
}
@VisibleForTesting
<T> String translateName(Invokable<T, T> c, int index) {
String name = null;
if (markers.contains(ConstructorProperties.class) && c.getAnnotation(ConstructorProperties.class) != null) {
@ -214,9 +241,9 @@ public class NamingStrategies {
}
}
for (Annotation annotation : c.getParameterAnnotations()[index]) {
for (Annotation annotation : c.getParameters().get(index).getAnnotations()) {
if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
name = annotationToNameExtractor.get(annotation.annotationType()).extractName(annotation);
name = annotationToNameExtractor.get(annotation.annotationType()).apply(annotation);
break;
}
}

View File

@ -0,0 +1,440 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import static com.google.common.base.Objects.equal;
import static com.google.common.base.Objects.toStringHelper;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.google.common.base.Objects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.gson.Gson;
import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.JsonReaderInternalAccess;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
/**
* Eliminates null values when deserializing Collections, Maps, and Multimaps
* <p/>
* Treats [null] as the empty set; [A, null] as [A]; etc.
*
* @author Adrian Cole
*/
public class NullFilteringTypeAdapterFactories {
private NullFilteringTypeAdapterFactories() {
}
static <T> TypeToken<?> resolve(TypeToken<T> ownerType, Type param) {
return TypeToken.get(com.google.gson.internal.$Gson$Types.resolve(ownerType.getType(), ownerType.getRawType(),
param));
}
public static class IterableTypeAdapterFactory implements TypeAdapterFactory {
protected final Class<?> declaring;
public IterableTypeAdapterFactory() {
this(Iterable.class);
}
protected IterableTypeAdapterFactory(Class<?> declaring) {
this.declaring = declaring;
}
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> ownerType) {
Type type = ownerType.getType();
if (ownerType.getRawType() != declaring || !(type instanceof ParameterizedType))
return null;
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) newAdapter(elementAdapter);
}
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new IterableTypeAdapter<E>(elementAdapter);
}
public static final class IterableTypeAdapter<E> extends TypeAdapter<Iterable<E>> {
private final TypeAdapter<E> elementAdapter;
public IterableTypeAdapter(TypeAdapter<E> elementAdapter) {
this.elementAdapter = elementAdapter;
nullSafe();
}
public void write(JsonWriter out, Iterable<E> value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginArray();
for (E element : value)
elementAdapter.write(out, element);
out.endArray();
}
public Iterable<E> read(JsonReader in) throws IOException {
return readAndBuild(in, ImmutableList.<E> builder());
}
@SuppressWarnings("unchecked")
protected <C extends Iterable<E>, B extends ImmutableCollection.Builder<E>> C readAndBuild(JsonReader in,
B builder) throws IOException {
in.beginArray();
while (in.hasNext()) {
E element = elementAdapter.read(in);
if (element != null)
builder.add(element);
}
in.endArray();
return (C) builder.build();
}
@Override
public int hashCode() {
return elementAdapter.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
IterableTypeAdapter<?> that = IterableTypeAdapter.class.cast(obj);
return equal(this.elementAdapter, that.elementAdapter);
}
@Override
public String toString() {
return toStringHelper(this).add("elementAdapter", elementAdapter).toString();
}
}
}
public static class CollectionTypeAdapterFactory extends IterableTypeAdapterFactory {
public CollectionTypeAdapterFactory() {
super(Collection.class);
}
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new CollectionTypeAdapter<E>(elementAdapter);
}
public static final class CollectionTypeAdapter<E> extends TypeAdapter<Collection<E>> {
private final IterableTypeAdapterFactory.IterableTypeAdapter<E> delegate;
public CollectionTypeAdapter(TypeAdapter<E> elementAdapter) {
this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter<E>(elementAdapter);
nullSafe();
}
public void write(JsonWriter out, Collection<E> value) throws IOException {
this.delegate.write(out, value);
}
public Collection<E> read(JsonReader in) throws IOException {
return delegate.readAndBuild(in, ImmutableList.<E> builder());
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
CollectionTypeAdapter<?> that = CollectionTypeAdapter.class.cast(obj);
return equal(this.delegate, that.delegate);
}
@Override
public String toString() {
return toStringHelper(this).add("elementAdapter", delegate.elementAdapter).toString();
}
}
}
public static class SetTypeAdapterFactory extends IterableTypeAdapterFactory {
public SetTypeAdapterFactory() {
super(Set.class);
}
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new SetTypeAdapter<E>(elementAdapter);
}
public static final class SetTypeAdapter<E> extends TypeAdapter<Set<E>> {
private final IterableTypeAdapterFactory.IterableTypeAdapter<E> delegate;
public SetTypeAdapter(TypeAdapter<E> elementAdapter) {
this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter<E>(elementAdapter);
nullSafe();
}
public void write(JsonWriter out, Set<E> value) throws IOException {
this.delegate.write(out, value);
}
public Set<E> read(JsonReader in) throws IOException {
return delegate.readAndBuild(in, ImmutableSet.<E> builder());
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
SetTypeAdapter<?> that = SetTypeAdapter.class.cast(obj);
return equal(this.delegate, that.delegate);
}
@Override
public String toString() {
return toStringHelper(this).add("elementAdapter", delegate.elementAdapter).toString();
}
}
}
public static class FluentIterableTypeAdapterFactory extends IterableTypeAdapterFactory {
public FluentIterableTypeAdapterFactory() {
super(FluentIterable.class);
}
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new FluentIterableTypeAdapter<E>(elementAdapter);
}
public static final class FluentIterableTypeAdapter<E> extends TypeAdapter<FluentIterable<E>> {
private final IterableTypeAdapterFactory.IterableTypeAdapter<E> delegate;
public FluentIterableTypeAdapter(TypeAdapter<E> elementAdapter) {
this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter<E>(elementAdapter);
nullSafe();
}
public void write(JsonWriter out, FluentIterable<E> value) throws IOException {
this.delegate.write(out, value.toList());
}
public FluentIterable<E> read(JsonReader in) throws IOException {
return FluentIterable.from(delegate.read(in));
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
FluentIterableTypeAdapter<?> that = FluentIterableTypeAdapter.class.cast(obj);
return equal(this.delegate, that.delegate);
}
@Override
public String toString() {
return toStringHelper(this).add("elementAdapter", delegate.elementAdapter).toString();
}
}
}
public static class MapTypeAdapterFactory implements TypeAdapterFactory {
protected final Class<?> declaring;
public MapTypeAdapterFactory() {
this(Map.class);
}
protected MapTypeAdapterFactory(Class<?> declaring) {
this.declaring = declaring;
}
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> ownerType) {
Type type = ownerType.getType();
if (ownerType.getRawType() != declaring || !(type instanceof ParameterizedType))
return null;
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return newAdapter(keyAdapter, valueAdapter);
}
@SuppressWarnings("unchecked")
protected <K, V, T> TypeAdapter<T> newAdapter(TypeAdapter<K> keyAdapter, TypeAdapter<V> valueAdapter) {
return (TypeAdapter<T>) new MapTypeAdapter<K, V>(keyAdapter, valueAdapter);
}
public static final class MapTypeAdapter<K, V> extends TypeAdapter<Map<K, V>> {
protected final TypeAdapter<K> keyAdapter;
protected final TypeAdapter<V> valueAdapter;
protected MapTypeAdapter(TypeAdapter<K> keyAdapter, TypeAdapter<V> valueAdapter) {
this.keyAdapter = keyAdapter;
this.valueAdapter = valueAdapter;
nullSafe();
}
public void write(JsonWriter out, Map<K, V> value) throws IOException {
if (value == null) {
out.nullValue();
return;
}
out.beginObject();
for (Map.Entry<K, V> element : value.entrySet()) {
out.name(String.valueOf(element.getKey()));
valueAdapter.write(out, element.getValue());
}
out.endObject();
}
public Map<K, V> read(JsonReader in) throws IOException {
ImmutableMap.Builder<K, V> result = ImmutableMap.builder();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
V value = valueAdapter.read(in);
if (value != null)
result.put(name, value);
}
in.endObject();
return result.build();
}
@Override
public int hashCode() {
return Objects.hashCode(keyAdapter, valueAdapter);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
MapTypeAdapter<?, ?> that = MapTypeAdapter.class.cast(obj);
return equal(this.keyAdapter, that.keyAdapter) && equal(this.valueAdapter, that.valueAdapter);
}
@Override
public String toString() {
return toStringHelper(this).add("keyAdapter", keyAdapter).add("valueAdapter", valueAdapter).toString();
}
}
}
public static class MultimapTypeAdapterFactory extends MapTypeAdapterFactory {
public MultimapTypeAdapterFactory() {
super(Multimap.class);
}
@SuppressWarnings("unchecked")
@Override
protected <K, V, T> TypeAdapter<T> newAdapter(TypeAdapter<K> keyAdapter, TypeAdapter<V> valueAdapter) {
return (TypeAdapter<T>) new MultimapTypeAdapter<K, V>(keyAdapter, valueAdapter);
}
public static final class MultimapTypeAdapter<K, V> extends TypeAdapter<Multimap<K, V>> {
private final MapTypeAdapterFactory.MapTypeAdapter<K, Collection<V>> delegate;
public MultimapTypeAdapter(TypeAdapter<K> keyAdapter, TypeAdapter<V> valueAdapter) {
this.delegate = new MapTypeAdapterFactory.MapTypeAdapter<K, Collection<V>>(keyAdapter,
new CollectionTypeAdapterFactory.CollectionTypeAdapter<V>(valueAdapter));
nullSafe();
}
public void write(JsonWriter out, Multimap<K, V> value) throws IOException {
this.delegate.write(out, value.asMap());
}
public Multimap<K, V> read(JsonReader in) throws IOException {
ImmutableMultimap.Builder<K, V> builder = ImmutableMultimap.<K, V> builder();
for (Entry<K, Collection<V>> entry : delegate.read(in).entrySet())
builder.putAll(entry.getKey(), entry.getValue());
return builder.build();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
MultimapTypeAdapter<?, ?> that = MultimapTypeAdapter.class.cast(obj);
return equal(this.delegate, that.delegate);
}
@Override
public String toString() {
return toStringHelper(this).add("keyAdapter", delegate.keyAdapter)
.add("valueAdapter", delegate.valueAdapter).toString();
}
}
}
}

View File

@ -1,4 +1,5 @@
package org.jclouds.json.internal;
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
@ -54,6 +55,7 @@ import com.google.gson.reflect.TypeToken;
* @author Adam Lowe
*/
@Test(testName = "DeserializationConstructorTypeAdapterFactoryTest")
@SuppressWarnings("unused")
public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest {
Gson gson = new Gson();
@ -61,13 +63,10 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory = parameterizedCtorFactory();
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(
ImmutableSet.of(new ExtractSerializedName(), new ExtractNamed())
);
NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy =
new NamingStrategies.AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, Inject.class),
ImmutableSet.of(new ExtractNamed()));
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
new ExtractSerializedName(), new ExtractNamed()));
NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = new NamingStrategies.AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
serializationPolicy, Excluder.DEFAULT, deserializationPolicy);
@ -83,25 +82,11 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
private DefaultConstructor() {
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
DefaultConstructor other = DefaultConstructor.class.cast(obj);
if (bar != other.bar)
return false;
if (foo != other.foo)
return false;
return true;
}
}
public void testRejectsIfNoConstuctorMarked() throws IOException {
TypeAdapter<DefaultConstructor> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(DefaultConstructor.class));
TypeAdapter<DefaultConstructor> adapter = parameterizedCtorFactory.create(gson,
TypeToken.get(DefaultConstructor.class));
assertNull(adapter);
}
@ -114,16 +99,10 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
}
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named")
public void testSerializedNameRequiredOnAllParameters() {
try {
parameterizedCtorFactory.create(gson, TypeToken
.get(WithDeserializationConstructorButWithoutSerializedName.class));
fail();
} catch (IllegalArgumentException actual) {
assertEquals(actual.getMessage(),
"org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactoryTest$WithDeserializationConstructorButWithoutSerializedName(int)" +
" parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named");
}
parameterizedCtorFactory
.create(gson, TypeToken.get(WithDeserializationConstructorButWithoutSerializedName.class));
}
private static class DuplicateSerializedNames {
@ -137,15 +116,9 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
}
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "duplicate key: foo")
public void testNoDuplicateSerializedNamesRequiredOnAllParameters() {
try {
parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class));
fail();
} catch (IllegalArgumentException actual) {
assertEquals(actual.getMessage(),
"org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactoryTest$DuplicateSerializedNames(int,int)" +
" declares multiple JSON parameters named foo");
}
parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class));
}
private static class ValidatedConstructor {
@ -160,34 +133,18 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
this.bar = bar;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
ValidatedConstructor other = ValidatedConstructor.class.cast(obj);
if (bar != other.bar)
return false;
if (foo != other.foo)
return false;
return true;
return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar);
}
@Override
public String toString() { return "ValidatedConstructor[foo=" + foo + ",bar=" + bar + "]"; }
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "negative!")
public void testValidatedConstructor() throws IOException {
TypeAdapter<ValidatedConstructor> adapter = parameterizedCtorFactory.create(gson, TypeToken
.get(ValidatedConstructor.class));
TypeAdapter<ValidatedConstructor> adapter = parameterizedCtorFactory.create(gson,
TypeToken.get(ValidatedConstructor.class));
assertEquals(new ValidatedConstructor(0, 1), adapter.fromJson("{\"foo\":0,\"bar\":1}"));
try {
adapter.fromJson("{\"foo\":-1,\"bar\":1}");
fail();
} catch (IllegalArgumentException expected) {
assertEquals("negative!", expected.getMessage());
}
adapter.fromJson("{\"foo\":-1,\"bar\":1}");
}
private static class GenericParamsCopiedIn {
@ -199,12 +156,11 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
this.foo = Lists.newArrayList(foo);
this.bar = Maps.newHashMap(bar);
}
}
public void testGenericParamsCopiedIn() throws IOException {
TypeAdapter<GenericParamsCopiedIn> adapter = parameterizedCtorFactory.create(gson, TypeToken
.get(GenericParamsCopiedIn.class));
TypeAdapter<GenericParamsCopiedIn> adapter = parameterizedCtorFactory.create(gson,
TypeToken.get(GenericParamsCopiedIn.class));
List<String> inputFoo = Lists.newArrayList();
inputFoo.add("one");
Map<String, String> inputBar = Maps.newHashMap();
@ -222,29 +178,27 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
@Named("_bar")
final int bar;
@ConstructorProperties({"foo", "_bar"})
@ConstructorProperties({ "foo", "_bar" })
RenamedFields(int foo, int bar) {
if (foo < 0)
throw new IllegalArgumentException("negative!");
this.foo = foo;
this.bar = bar;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
RenamedFields other = RenamedFields.class.cast(obj);
if (bar != other.bar)
return false;
if (foo != other.foo)
return false;
return true;
return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar);
}
}
public void testCanOverrideDefault() throws IOException {
Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory).create();
assertEquals(new RenamedFields(0, 1), gson.fromJson("{\"foo\":0,\"_bar\":1}", RenamedFields.class));
assertEquals(gson.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}");
}
public void testRenamedFields() throws IOException {
TypeAdapter<RenamedFields> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(RenamedFields.class));
assertEquals(new RenamedFields(0, 1), adapter.fromJson("{\"foo\":0,\"_bar\":1}"));
@ -255,44 +209,38 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
final ValidatedConstructor x;
final ValidatedConstructor y;
@ConstructorProperties({"x", "y"})
@ConstructorProperties({ "x", "y" })
ComposedObjects(ValidatedConstructor x, ValidatedConstructor y) {
this.x = checkNotNull(x);
this.y = checkNotNull(y);
}
@Override
public boolean equals(Object obj) {
ComposedObjects other = ComposedObjects.class.cast(obj);
return other != null && Objects.equal(x, other.x) && Objects.equal(y, other.y);
}
@Override
public String toString() { return "ComposedObjects[x=" + x.toString() + ";y=" + y.toString() + "]"; }
}
public void checkSimpleComposedObject() throws IOException {
ValidatedConstructor x = new ValidatedConstructor(0,1);
ValidatedConstructor y = new ValidatedConstructor(1,2);
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class));
assertEquals(new ComposedObjects(x, y), adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}"));
public void checkSimpleComposedObject() throws IOException {
ValidatedConstructor x = new ValidatedConstructor(0, 1);
ValidatedConstructor y = new ValidatedConstructor(1, 2);
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
.create(gson, TypeToken.get(ComposedObjects.class));
assertEquals(new ComposedObjects(x, y),
adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}"));
}
public void testEmptyObjectIsNull() throws IOException {
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class));
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
.create(gson, TypeToken.get(ComposedObjects.class));
assertNull(adapter.fromJson("{}"));
}
@Test(expectedExceptions = NullPointerException.class)
public void testPartialObjectStillThrows() throws IOException {
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class));
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
.create(gson, TypeToken.get(ComposedObjects.class));
assertNull(adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1}}"));
}
public void testCanOverrideDefault() throws IOException {
Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory).create();
assertEquals(new RenamedFields(0, 1), gson.fromJson("{\"foo\":0,\"_bar\":1}", RenamedFields.class));
assertEquals(gson.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}");
}
}

View File

@ -18,13 +18,13 @@ package org.jclouds.json.internal;
* under the License.
*/
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;
import java.lang.reflect.Constructor;
import javax.inject.Inject;
import javax.inject.Named;
@ -32,13 +32,13 @@ import javax.inject.Named;
import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.ConstructorFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
import org.jclouds.json.internal.NamingStrategies.NameExtractor;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Invokable;
import com.google.gson.FieldNamingStrategy;
import com.google.gson.annotations.SerializedName;
@ -51,6 +51,7 @@ public final class NamingStrategiesTest {
private static class SimpleTest {
@SerializedName("aardvark")
private String a;
@SuppressWarnings("unused")
private String b;
@Named("cat")
private String c;
@ -75,7 +76,7 @@ public final class NamingStrategiesTest {
public void testExtractSerializedName() throws Exception {
NameExtractor extractor = new ExtractSerializedName();
NameExtractor<SerializedName> extractor = new ExtractSerializedName();
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)),
"aardvark");
try {
@ -96,7 +97,7 @@ public final class NamingStrategiesTest {
}
public void testExtractNamed() throws Exception {
NameExtractor extractor = new ExtractNamed();
NameExtractor<Named> extractor = new ExtractNamed();
try {
extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(Named.class));
} catch (NullPointerException e) {
@ -131,12 +132,12 @@ public final class NamingStrategiesTest {
}
public void testAnnotationConstructorFieldNamingStrategyCPAndNamed() throws Exception {
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.of(new ExtractNamed()));
Constructor<? super SimpleTest> constructor = strategy.getDeserializationConstructor(SimpleTest.class);
Invokable<SimpleTest, SimpleTest> constructor = strategy.getDeserializer(typeToken(SimpleTest.class));
assertNotNull(constructor);
assertEquals(constructor.getParameterTypes().length, 4);
assertEquals(constructor.getParameters().size(), 4);
assertEquals(strategy.translateName(constructor, 0), "aardvark");
assertEquals(strategy.translateName(constructor, 1), "bat");
@ -144,9 +145,9 @@ public final class NamingStrategiesTest {
// Note: @Named overrides the ConstructorProperties setting
assertEquals(strategy.translateName(constructor, 3), "dingo");
Constructor<? super MixedConstructorTest> mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class);
Invokable<MixedConstructorTest, MixedConstructorTest> mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class));
assertNotNull(mixedCtor);
assertEquals(mixedCtor.getParameterTypes().length, 4);
assertEquals(mixedCtor.getParameters().size(), 4);
assertEquals(strategy.translateName(mixedCtor, 0), "aardvark");
assertEquals(strategy.translateName(mixedCtor, 1), "bat");
@ -155,21 +156,21 @@ public final class NamingStrategiesTest {
}
public void testAnnotationConstructorFieldNamingStrategyCP() throws Exception {
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.<NameExtractor>of());
AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.<NameExtractor<?>>of());
Constructor<? super SimpleTest> constructor = strategy.getDeserializationConstructor(SimpleTest.class);
Invokable<SimpleTest, SimpleTest> constructor = strategy.getDeserializer(typeToken(SimpleTest.class));
assertNotNull(constructor);
assertEquals(constructor.getParameterTypes().length, 4);
assertEquals(constructor.getParameters().size(), 4);
assertEquals(strategy.translateName(constructor, 0), "aardvark");
assertEquals(strategy.translateName(constructor, 1), "bat");
assertEquals(strategy.translateName(constructor, 2), "coyote");
assertEquals(strategy.translateName(constructor, 3), "dog");
Constructor<? super MixedConstructorTest> mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class);
Invokable<MixedConstructorTest, MixedConstructorTest> mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class));
assertNotNull(mixedCtor);
assertEquals(mixedCtor.getParameterTypes().length, 4);
assertEquals(mixedCtor.getParameters().size(), 4);
assertEquals(strategy.translateName(mixedCtor, 0), "thiscanbeoverriddenbyNamed");
assertNull(strategy.translateName(mixedCtor, 1));
@ -178,21 +179,21 @@ public final class NamingStrategiesTest {
}
public void testAnnotationConstructorFieldNamingStrategyInject() throws Exception {
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(Inject.class), ImmutableSet.of(new ExtractNamed()));
Constructor<? super SimpleTest> constructor = strategy.getDeserializationConstructor(SimpleTest.class);
Invokable<SimpleTest, SimpleTest> constructor = strategy.getDeserializer(typeToken(SimpleTest.class));
assertNotNull(constructor);
assertEquals(constructor.getParameterTypes().length, 5);
assertEquals(constructor.getParameters().size(), 5);
assertEquals(strategy.translateName(constructor, 0), "aa");
assertEquals(strategy.translateName(constructor, 1), "bb");
assertEquals(strategy.translateName(constructor, 2), "cc");
assertEquals(strategy.translateName(constructor, 3), "dd");
Constructor<? super MixedConstructorTest> mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class);
Invokable<MixedConstructorTest, MixedConstructorTest> mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class));
assertNotNull(mixedCtor);
assertEquals(mixedCtor.getParameterTypes().length, 4);
assertEquals(mixedCtor.getParameters().size(), 4);
assertEquals(strategy.translateName(mixedCtor, 0), "aardvark");
assertEquals(strategy.translateName(mixedCtor, 1), "bat");

View File

@ -0,0 +1,222 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jclouds.json.internal;
import static com.google.common.base.Objects.equal;
import static org.testng.Assert.assertEquals;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.CollectionTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.FluentIterableTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.IterableTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MapTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MultimapTypeAdapterFactory;
import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.SetTypeAdapterFactory;
import org.testng.annotations.Test;
import com.google.common.base.Objects;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
*
* @author Adrian Cole
*/
@Test(testName = "NullFilteringTypeAdapterFactoriesTest")
public class NullFilteringTypeAdapterFactoriesTest {
private static class Resource {
private final String id;
private final String name;
private Resource(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public int hashCode() {
return Objects.hashCode(id, name);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Resource that = Resource.class.cast(obj);
return equal(this.id, that.id) && equal(this.name, that.name);
}
}
private Gson fluentIterable = new GsonBuilder().registerTypeAdapterFactory(new FluentIterableTypeAdapterFactory())
.create();
private Type fluentIterableType = new TypeToken<FluentIterable<String>>() {
private static final long serialVersionUID = 1L;
}.getType();
private Type fluentIterableResourceType = new TypeToken<FluentIterable<Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testFluentIterable() {
FluentIterable<String> noNulls = fluentIterable.fromJson("[\"value\",\"a test string!\"]", fluentIterableType);
assertEquals(noNulls.toList(), ImmutableList.of("value", "a test string!"));
FluentIterable<String> withNull = fluentIterable.fromJson("[null,\"a test string!\"]", fluentIterableType);
assertEquals(withNull.toList(), ImmutableList.of("a test string!"));
FluentIterable<String> withDupes = fluentIterable.fromJson("[\"value\",\"value\"]", fluentIterableType);
assertEquals(withDupes.toList(), ImmutableList.of("value", "value"));
FluentIterable<Resource> resources = fluentIterable.fromJson(
"[{\"id\":\"i-foo\",\"name\":\"foo\"},{\"id\":\"i-bar\",\"name\":\"bar\"}]", fluentIterableResourceType);
assertEquals(resources.toList(), ImmutableList.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar")));
}
private Gson collection = new GsonBuilder().registerTypeAdapterFactory(new CollectionTypeAdapterFactory()).create();
private Type collectionType = new TypeToken<Collection<String>>() {
private static final long serialVersionUID = 1L;
}.getType();
private Type collectionResourceType = new TypeToken<Collection<Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testCollection() {
Collection<String> noNulls = collection.fromJson("[\"value\",\"a test string!\"]", collectionType);
assertEquals(noNulls, ImmutableList.of("value", "a test string!"));
Collection<String> withNull = collection.fromJson("[null,\"a test string!\"]", collectionType);
assertEquals(withNull, ImmutableList.of("a test string!"));
Collection<String> withDupes = collection.fromJson("[\"value\",\"value\"]", collectionType);
assertEquals(withDupes, ImmutableList.of("value", "value"));
Collection<Resource> resources = collection.fromJson(
"[{\"id\":\"i-foo\",\"name\":\"foo\"},{\"id\":\"i-bar\",\"name\":\"bar\"}]", collectionResourceType);
assertEquals(resources, ImmutableList.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar")));
}
private Gson iterable = new GsonBuilder().registerTypeAdapterFactory(new IterableTypeAdapterFactory()).create();
private Type iterableType = new TypeToken<Iterable<String>>() {
private static final long serialVersionUID = 1L;
}.getType();
private Type iterableResourceType = new TypeToken<Iterable<Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testIterable() {
Iterable<String> noNulls = iterable.fromJson("[\"value\",\"a test string!\"]", iterableType);
assertEquals(noNulls, ImmutableList.of("value", "a test string!"));
Iterable<String> withNull = iterable.fromJson("[null,\"a test string!\"]", iterableType);
assertEquals(withNull, ImmutableList.of("a test string!"));
Iterable<String> withDupes = iterable.fromJson("[\"value\",\"value\"]", iterableType);
assertEquals(withDupes, ImmutableList.of("value", "value"));
Iterable<Resource> resources = iterable.fromJson(
"[{\"id\":\"i-foo\",\"name\":\"foo\"},{\"id\":\"i-bar\",\"name\":\"bar\"}]", iterableResourceType);
assertEquals(resources, ImmutableList.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar")));
}
private Type iterableWildcardExtendsResourceType = new TypeToken<Iterable<? extends Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testWildcardExtends() {
Iterable<? extends Resource> wildcardExtendsResources = iterable.fromJson(
"[{\"id\":\"i-foo\",\"name\":\"foo\"},{\"id\":\"i-bar\",\"name\":\"bar\"}]",
iterableWildcardExtendsResourceType);
assertEquals(wildcardExtendsResources,
ImmutableList.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar")));
}
private Gson set = new GsonBuilder().registerTypeAdapterFactory(new SetTypeAdapterFactory()).create();
private Type setType = new TypeToken<Set<String>>() {
private static final long serialVersionUID = 1L;
}.getType();
private Type setResourceType = new TypeToken<Set<Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testSet() {
Set<String> noNulls = set.fromJson("[\"value\",\"a test string!\"]", setType);
assertEquals(noNulls, ImmutableSet.of("value", "a test string!"));
Set<String> withNull = set.fromJson("[null,\"a test string!\"]", setType);
assertEquals(withNull, ImmutableSet.of("a test string!"));
Set<String> withDupes = set.fromJson("[\"value\",\"value\"]", setType);
assertEquals(withDupes, ImmutableSet.of("value"));
Set<Resource> resources = set.fromJson(
"[{\"id\":\"i-foo\",\"name\":\"foo\"},{\"id\":\"i-bar\",\"name\":\"bar\"}]", setResourceType);
assertEquals(resources, ImmutableSet.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar")));
}
private Gson map = new GsonBuilder().registerTypeAdapterFactory(new MapTypeAdapterFactory()).create();
private Type mapType = new TypeToken<Map<String, String>>() {
private static final long serialVersionUID = 1L;
}.getType();
private Type mapResourceType = new TypeToken<Map<String, Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testMap() {
Map<String, String> noNulls = map.fromJson("{\"value\":\"a test string!\"}", mapType);
assertEquals(noNulls, ImmutableMap.of("value", "a test string!"));
Map<String, String> withNull = map.fromJson("{\"value\":null}", mapType);
assertEquals(withNull, ImmutableMap.of());
Map<String, String> withEmpty = map.fromJson("{\"value\":\"\"}", mapType);
assertEquals(withEmpty, ImmutableMap.of("value", ""));
Map<String, Resource> resources = map.fromJson(
"{\"i-foo\":{\"id\":\"i-foo\",\"name\":\"foo\"},\"i-bar\":{\"id\":\"i-bar\",\"name\":\"bar\"}}",
mapResourceType);
assertEquals(resources,
ImmutableMap.of("i-foo", new Resource("i-foo", "foo"), "i-bar", new Resource("i-bar", "bar")));
}
private Gson multimap = new GsonBuilder().registerTypeAdapterFactory(new MultimapTypeAdapterFactory()).create();
private Type multimapType = new TypeToken<Multimap<String, String>>() {
private static final long serialVersionUID = 1L;
}.getType();
private Type multimapResourceType = new TypeToken<Multimap<String, Resource>>() {
private static final long serialVersionUID = 1L;
}.getType();
public void testMultimap() {
Multimap<String, String> noNulls = multimap.fromJson("{\"value\":[\"a test string!\"]}", multimapType);
assertEquals(noNulls, ImmutableMultimap.of("value", "a test string!"));
Multimap<String, String> withNull = multimap.fromJson("{\"value\":[null]}", multimapType);
assertEquals(withNull, ImmutableMultimap.of());
Multimap<String, String> withEmpty = multimap.fromJson("{\"value\":[\"\"]}", multimapType);
assertEquals(withEmpty, ImmutableMultimap.of("value", ""));
Multimap<String, String> withDupes = multimap.fromJson("{\"key\":[\"value\",\"value\"]}", multimapType);
assertEquals(withDupes.get("key"), ImmutableList.of("value", "value"));
Multimap<String, Resource> resources = multimap.fromJson(
"{\"i-foo\":[{\"id\":\"i-foo\",\"name\":\"foo\"}],\"i-bar\":[{\"id\":\"i-bar\",\"name\":\"bar\"}]}",
multimapResourceType);
assertEquals(resources,
ImmutableMultimap.of("i-foo", new Resource("i-foo", "foo"), "i-bar", new Resource("i-bar", "bar")));
Multimap<String, Resource> resourceDupes = multimap.fromJson(
"{\"i-foo\":[{\"id\":\"i-foo\",\"name\":\"foo\"},{\"id\":\"i-bar\",\"name\":\"bar\"}]}",
multimapResourceType);
assertEquals(resourceDupes.get("i-foo"),
ImmutableList.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar")));
}
}

View File

@ -42,7 +42,7 @@ public class OptionalTypeAdapterFactoryTest {
* Simple type with an Optional field
*/
static class SimpleBean {
private Optional<String> value;
private final Optional<String> value;
private final String someOtherValue;
public SimpleBean(Optional<String> value, String someOtherValue) {
@ -51,6 +51,7 @@ public class OptionalTypeAdapterFactoryTest {
}
// Required to ensure GSON doesn't initialize our Optional to null!
@SuppressWarnings("unused")
private SimpleBean() {
this.value = Optional.absent();
this.someOtherValue = null;
@ -64,22 +65,9 @@ public class OptionalTypeAdapterFactoryTest {
return someOtherValue;
}
@Override
public int hashCode() {
return Objects.hashCode(value, someOtherValue);
}
@Override
public boolean equals(Object that) {
if (that == null || that.getClass() != getClass())
return false;
SimpleBean other = (SimpleBean) that;
return Objects.equal(value, other.value) && Objects.equal(someOtherValue, other.someOtherValue);
}
@Override
public String toString() {
return Objects.toStringHelper("SimpleBean").add("value", value).add("someOtherValue", someOtherValue).toString();
public boolean equals(Object other) {
SimpleBean that = SimpleBean.class.cast(other);
return Objects.equal(value, that.value) && Objects.equal(someOtherValue, that.someOtherValue);
}
}

View File

@ -46,8 +46,8 @@ public class ListPage<T> extends IterableWithMarker<T> {
private final Iterable<T> items;
protected ListPage(Kind kind, String id, URI selfLink, String nextPageToken, Iterable<T> items) {
this.kind = checkNotNull(kind, "kind of %id", id);
this.id = checkNotNull(id, "id");
this.kind = checkNotNull(kind, "kind of %id", id);
this.selfLink = checkNotNull(selfLink, "selfLink of %id", id);
this.nextPageToken = nextPageToken;
this.items = items != null ? ImmutableSet.copyOf(items) : ImmutableSet.<T>of();
@ -90,7 +90,7 @@ public class ListPage<T> extends IterableWithMarker<T> {
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ListPage that = ListPage.class.cast(obj);
ListPage<?> that = ListPage.class.cast(obj);
return equal(this.kind, that.kind)
&& equal(this.id, that.id);
}

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseImageDetails extends ParseJson<Images<? extends ImageDetails>> {
static class Images<T extends ImageDetails> extends PaginatedCollection<T> {
public class ParseImageDetails extends ParseJson<Images> {
static class Images extends PaginatedCollection<ImageDetails> {
@ConstructorProperties({ "images", "images_links" })
protected Images(Iterable<T> images, Iterable<Link> images_links) {
protected Images(Iterable<ImageDetails> images, Iterable<Link> images_links) {
super(images, images_links);
}
@ -60,8 +60,7 @@ public class ParseImageDetails extends ParseJson<Images<? extends ImageDetails>>
@Inject
public ParseImageDetails(Json json) {
super(json, new TypeLiteral<Images<? extends ImageDetails>>() {
});
super(json, TypeLiteral.get(Images.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<ImageDetails, ToPagedIterable> {

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/
@Beta
@Singleton
public class ParseImages extends ParseJson<Images<? extends Image>> {
static class Images<T extends Image> extends PaginatedCollection<T> {
public class ParseImages extends ParseJson<Images> {
static class Images extends PaginatedCollection<Image> {
@ConstructorProperties({ "images", "images_links" })
protected Images(Iterable<T> images, Iterable<Link> images_links) {
protected Images(Iterable<Image> images, Iterable<Link> images_links) {
super(images, images_links);
}
@ -60,8 +60,7 @@ public class ParseImages extends ParseJson<Images<? extends Image>> {
@Inject
public ParseImages(Json json) {
super(json, new TypeLiteral<Images<? extends Image>>() {
});
super(json, TypeLiteral.get(Images.class));
}
public static class ToPagedIterable extends CallerArg0ToPagedIterable<Image, ToPagedIterable> {