Merge pull request #1210 from jclouds/gson-typetoken

Gson internals refactor
This commit is contained in:
Adrian Cole 2013-01-20 23:52:24 -08:00
commit ad70dae25c
37 changed files with 1517 additions and 1030 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);
@SuppressWarnings("unchecked")
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new Adapter<E>(elementAdapter);
}
out.endArray();
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 {
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();
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 {
public static class ValuesSetTypeAdapterFactory extends SetTypeAdapterFactory {
@Override
@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;
protected <E, I> TypeAdapter<I> newAdapter(TypeAdapter<E> elementAdapter) {
return (TypeAdapter<I>) new Adapter<E>(elementAdapter);
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return TypeAdapter.class.cast(newSetAdapter(elementAdapter));
public static final class Adapter<E> extends TypeAdapter<Set<E>> {
private final SetTypeAdapterFactory.SetTypeAdapter<E> delegate;
public Adapter(TypeAdapter<E> elementAdapter) {
this.delegate = new SetTypeAdapterFactory.SetTypeAdapter<E>(elementAdapter);
nullSafe();
}
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();
this.delegate.write(out, value);
}
@Override
public Set<E> read(JsonReader in) throws IOException {
Set<E> result = Sets.newLinkedHashSet();
if (in.peek() == JsonToken.BEGIN_OBJECT) {
Builder<E> builder = ImmutableSet.<E>builder();
boolean foundValues = false;
in.beginObject();
while (in.hasNext()) {
String name = in.nextName();
if (Objects.equal("values", name)) {
foundValues = true;
readArray(in, result);
builder.addAll(delegate.read(in));
} else {
in.skipValue();
}
}
checkState(foundValues, "Expected BEGIN_ARRAY or the object to contain an array called 'values'");
in.endObject();
return builder.build();
} else {
readArray(in, result);
}
return result;
}
private void readArray(JsonReader in, Set<E> result) throws IOException {
in.beginArray();
while (in.hasNext()) {
E element = elementAdapter.read(in);
result.add(element);
}
in.endArray();
}
}.nullSafe();
return delegate.read(in);
}
}
}
}
}

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

@ -53,8 +53,7 @@ public class FilterStringsBoundToInjectorByName implements Function<Predicate<St
@Override
public Map<String, String> apply(Predicate<String> filter) {
List<Binding<String>> stringBindings = injector.findBindingsByType(new TypeLiteral<String>() {
});
List<Binding<String>> stringBindings = injector.findBindingsByType(TypeLiteral.get(String.class));
Iterable<Binding<String>> annotatedWithName = Iterables.filter(stringBindings, new Predicate<Binding<String>>() {
@Override

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;
@ -85,13 +86,12 @@ public class GsonModule extends AbstractModule {
@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 {
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(
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,17 +52,16 @@ 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.*;
@ -80,6 +82,7 @@ import com.google.gson.stream.JsonWriter;
* <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;
@ -102,49 +105,42 @@ import com.google.gson.stream.JsonWriter;
* @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);
}
@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;
}
private Map<String, ParameterReader<?>> getParameterReaders(Gson context, TypeToken<?> declaring, Constructor<?> constructor) {
Map<String, ParameterReader<?>> result = Maps.newLinkedHashMap();
@Override
public int hashCode() {
return Objects.hashCode(position, name);
}
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);
@Override
public String toString() {
return typeAdapter + " arg" + position;
}
}
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(parameterName, index, adapter);
ParameterReader<?> previous = result.put(parameterReader.name, parameterReader);
checkArgument(previous == null, constructor + " declares multiple JSON parameters named " + parameterReader.name);
ParameterReader<?> parameterReader = new ParameterReader(param.hashCode(), parameterName, adapter);
result.put(parameterReader.name, parameterReader);
}
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);
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,21 +20,29 @@ 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;
@ -46,23 +54,40 @@ import com.google.gson.annotations.SerializedName;
*/
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) {
public Class<? extends Annotation> apply(NameExtractor<?> input) {
return input.annotationType();
}
});
this.forToString = Joiner.on(",").join(Iterables.transform(extractors, new Function<NameExtractor, String>() {
this.forToString = Joiner.on(",").join(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

@ -19,19 +19,18 @@
package org.jclouds.lifecycle.config;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Sets.newHashSet;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static com.google.inject.matcher.Matchers.any;
import static java.util.Arrays.asList;
import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS;
import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS;
import static org.jclouds.Constants.PROPERTY_USER_THREADS;
import static org.jclouds.reflect.Reflection2.methods;
import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.Collection;
import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.PostConstruct;
@ -40,6 +39,8 @@ import javax.inject.Named;
import org.jclouds.lifecycle.Closer;
import com.google.common.base.Predicate;
import com.google.common.reflect.Invokable;
import com.google.common.util.concurrent.ExecutionList;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.inject.AbstractModule;
@ -51,15 +52,15 @@ import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
/**
* This associates java lifecycle annotations with guice hooks. For example, we invoke
* {@link PostConstruct} after injection, and Associate {@link PreDestroy} with a global
* {@link Closer} object.
* This associates java lifecycle annotations with guice hooks. For example, we invoke {@link PostConstruct} after
* injection, and Associate {@link PreDestroy} with a global {@link Closer} object.
*
* <h3>Important</h3> Make sure you create your injector with {@link Stage#PRODUCTION} and execute
* the bound {@link ExecutionList} prior to using any other objects.
* <h3>Important</h3> Make sure you create your injector with {@link Stage#PRODUCTION} and execute the bound
* {@link ExecutionList} prior to using any other objects.
*
* <p/>
* Ex.
*
* <pre>
*
* </pre>
@ -103,67 +104,57 @@ public class LifeCycleModule extends AbstractModule {
bind(ExecutionList.class).toInstance(list);
}
private static final Predicate<Invokable<?, ?>> isPreDestroy = new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> in) {
return in.isAnnotationPresent(PreDestroy.class);
}
};
private static final Predicate<Invokable<?, ?>> isPostConstruct = new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> in) {
return in.isAnnotationPresent(PostConstruct.class);
}
};
protected void bindPostInjectionInvoke(final Closer closer, final ExecutionList list) {
bindListener(any(), new TypeListener() {
public <I> void hear(TypeLiteral<I> injectableType, TypeEncounter<I> encounter) {
Set<Method> methods = newHashSet();
Class<? super I> type = injectableType.getRawType();
while (type != null) {
methods.addAll(asList(type.getDeclaredMethods()));
type = type.getSuperclass();
}
for (final Method method : methods) {
invokePostConstructMethodAfterInjection(encounter, method);
associatePreDestroyWithCloser(closer, encounter, method);
}
}
private <I> void associatePreDestroyWithCloser(final Closer closer, TypeEncounter<I> encounter,
final Method method) {
PreDestroy preDestroy = method.getAnnotation(PreDestroy.class);
if (preDestroy != null) {
encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) {
closer.addToClose(new Closeable() {
public void close() throws IOException {
try {
method.invoke(injectee);
} catch (InvocationTargetException ie) {
Throwable e = ie.getTargetException();
throw new IOException(e.getMessage());
} catch (IllegalAccessException e) {
throw new IOException(e.getMessage());
}
}
});
}
});
}
}
private <I> void invokePostConstructMethodAfterInjection(TypeEncounter<I> encounter, final Method method) {
PostConstruct postConstruct = method.getAnnotation(PostConstruct.class);
if (postConstruct != null) {
Collection<? extends Invokable<? super I, Object>> methods = methods(injectableType.getRawType());
for (final Invokable<? super I, Object> method : filter(methods, isPostConstruct)) {
encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) {
list.add(new Runnable() {
public void run() {
try {
method.invoke(injectee);
} catch (InvocationTargetException ie) {
Throwable e = ie.getTargetException();
throw propagate(e);
} catch (IllegalAccessException e) {
throw propagate(e);
}
invokeOnInjectee(method, injectee);
}
}, sameThreadExecutor());
}
});
}
for (final Invokable<? super I, Object> method : filter(methods, isPreDestroy)) {
encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) {
closer.addToClose(new Closeable() {
public void close() throws IOException {
invokeOnInjectee(method, injectee);
}
});
}
});
}
}
});
}
private static <I> void invokeOnInjectee(Invokable<? super I, Object> method, I injectee) {
try {
method.invoke(injectee);
} catch (InvocationTargetException ie) {
throw propagate(ie.getTargetException());
} catch (IllegalAccessException e) {
throw propagate(e);
}
}
}

View File

@ -19,23 +19,33 @@
package org.jclouds.reflect;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.tryFind;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import com.google.common.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.UncheckedExecutionException;
/**
* Utilities that allow access to {@link Invokable}s with {@link Invokable#getOwnerType() owner types}.
@ -43,14 +53,50 @@ import com.google.common.reflect.TypeToken;
* @since 1.6
*/
@Beta
public final class Reflection2 {
public class Reflection2 {
/**
* gets a {@link TypeToken} for the given type.
*/
@SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Type in) {
return (TypeToken<T>) get(typeTokenForType, checkNotNull(in, "class"));
}
/**
* gets a {@link TypeToken} for the given class.
*/
@SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Class<T> in) {
return (TypeToken<T>) typeTokenForClass.apply(checkNotNull(in, "class"));
return (TypeToken<T>) get(typeTokenForClass, checkNotNull(in, "class"));
}
/**
* returns an {@link Invokable} object that reflects a constructor present in the {@link TypeToken} type.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
* @param parameterTypes
* corresponds to {@link Constructor#getParameterTypes()}
*
* @throws IllegalArgumentException
* if the constructor doesn't exist or a security exception occurred
*/
@SuppressWarnings("unchecked")
public static <T> Invokable<T, T> constructor(Class<T> ownerType, Class<?>... parameterTypes) {
return (Invokable<T, T>) get(constructorForParams, new TypeTokenAndParameterTypes(typeToken(ownerType),
parameterTypes));
}
/**
* return all constructors present in the class as {@link Invokable}s.
*
* @param ownerType
* corresponds to {@link Invokable#getOwnerType()}
*/
@SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, T>> constructors(TypeToken<T> ownerType) {
return Collection.class.cast(get(constructorsForTypeToken, ownerType));
}
/**
@ -63,7 +109,7 @@ public final class Reflection2 {
*/
@SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(TypeToken<T> ownerType, Method method) {
return (Invokable<T, R>) methods.apply(new TypeTokenAndMethod(ownerType, method));
return (Invokable<T, R>) method(ownerType.getRawType(), method.getName(), method.getParameterTypes());
}
/**
@ -79,7 +125,7 @@ public final class Reflection2 {
*/
@SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(Class<T> ownerType, String name, Class<?>... parameterTypes) {
return (Invokable<T, R>) methodForArgs.apply(new TypeTokenNameAndParameterTypes(typeToken(ownerType), name,
return (Invokable<T, R>) get(methodForParams, new TypeTokenNameAndParameterTypes(typeToken(ownerType), name,
parameterTypes));
}
@ -91,51 +137,65 @@ public final class Reflection2 {
*/
@SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, Object>> methods(Class<T> ownerType) {
return Collection.class.cast(methodsForTypeToken.apply(typeToken(ownerType)).values());
return Collection.class.cast(get(methodsForTypeToken, typeToken(ownerType)));
}
private static final LoadingCache<TypeTokenAndMethod, Invokable<?, ?>> methods = CacheBuilder.newBuilder().build(
new CacheLoader<TypeTokenAndMethod, Invokable<?, ?>>() {
public Invokable<?, ?> load(TypeTokenAndMethod key) {
return key.type.method(key.method);
/**
* this gets all declared constructors, not just public ones. makes them accessible, as well.
*/
private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> constructorsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
public Set<Invokable<?, ?>> load(TypeToken<?> key) {
ImmutableSet.Builder<Invokable<?, ?>> builder = ImmutableSet.<Invokable<?, ?>> builder();
for (Constructor<?> ctor : key.getRawType().getDeclaredConstructors()) {
ctor.setAccessible(true);
builder.add(key.constructor(ctor));
}
return builder.build();
}
});
private static class TypeTokenAndMethod {
protected final TypeToken<?> type;
protected final Method method;
public TypeTokenAndMethod(TypeToken<?> type, Method method) {
this.type = checkNotNull(type, "type");
this.method = checkNotNull(method, "method");
protected static List<Class<?>> toClasses(ImmutableList<Parameter> params) {
return Lists.transform(params, new Function<Parameter, Class<?>>() {
public Class<?> apply(Parameter input) {
return input.getType().getRawType();
}
});
}
public int hashCode() {
return Objects.hashCode(type, method);
}
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
TypeTokenAndMethod that = TypeTokenAndMethod.class.cast(obj);
return Objects.equal(this.type, that.type) && Objects.equal(this.method, that.method);
}
}
private static final LoadingCache<Class<?>, TypeToken<?>> typeTokenForClass = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, TypeToken<?>>() {
public TypeToken<?> load(final Class<?> key) {
private static LoadingCache<Type, TypeToken<?>> typeTokenForType = CacheBuilder.newBuilder().build(
new CacheLoader<Type, TypeToken<?>>() {
public TypeToken<?> load(Type key) {
return TypeToken.of(key);
}
});
private static LoadingCache<Class<?>, TypeToken<?>> typeTokenForClass = CacheBuilder.newBuilder().build(
new CacheLoader<Class<?>, TypeToken<?>>() {
public TypeToken<?> load(Class<?> key) {
return TypeToken.of(key);
}
});
private static LoadingCache<TypeTokenAndParameterTypes, Invokable<?, ?>> constructorForParams = CacheBuilder
.newBuilder().build(new CacheLoader<TypeTokenAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenAndParameterTypes key) {
Set<Invokable<?, ?>> constructors = get(constructorsForTypeToken, key.type);
Optional<Invokable<?, ?>> constructor = tryFind(constructors, new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> input) {
return Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
}
});
if (constructor.isPresent())
return constructor.get();
throw new IllegalArgumentException("no such constructor " + key.toString() + "in: " + constructors);
}
});
private static class TypeTokenAndParameterTypes {
protected final TypeToken<?> type;
protected final List<Class<?>> parameterTypes;
protected TypeToken<?> type;
protected List<Class<?>> parameterTypes;
public TypeTokenAndParameterTypes(TypeToken<?> type, Class<?>... parameterTypes) {
this.type = checkNotNull(type, "type");
@ -160,23 +220,25 @@ public final class Reflection2 {
}
}
private static final LoadingCache<TypeTokenNameAndParameterTypes, Invokable<?, ?>> methodForArgs = CacheBuilder
private static LoadingCache<TypeTokenNameAndParameterTypes, Invokable<?, ?>> methodForParams = CacheBuilder
.newBuilder().build(new CacheLoader<TypeTokenNameAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenNameAndParameterTypes key) {
try {
Method method = key.type.getRawType().getMethod(key.name, toArray(key.parameterTypes, Class.class));
return methods.apply(new TypeTokenAndMethod(key.type, method));
} catch (SecurityException e) {
throw new IllegalArgumentException(e.getMessage() + " getting method " + key.toString(), e);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("no such method " + key.toString(), e);
Set<Invokable<?, ?>> methods = get(methodsForTypeToken, key.type);
Optional<Invokable<?, ?>> method = tryFind(methods, new Predicate<Invokable<?, ?>>() {
public boolean apply(Invokable<?, ?> input) {
return Objects.equal(input.getName(), key.name)
&& Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
}
});
if (method.isPresent())
return method.get();
throw new IllegalArgumentException("no such method " + key.toString() + "in: " + methods);
}
});
private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes {
private final String name;
private String name;
public TypeTokenNameAndParameterTypes(TypeToken<?> type, String name, Class<?>... parameterTypes) {
super(type, parameterTypes);
@ -201,14 +263,36 @@ public final class Reflection2 {
}
}
private static final LoadingCache<TypeToken<?>, Map<Method, Invokable<?, ?>>> methodsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader<TypeToken<?>, Map<Method, Invokable<?, ?>>>() {
public Map<Method, Invokable<?, ?>> load(final TypeToken<?> key) {
Builder<Method, Invokable<?, ?>> builder = ImmutableMap.<Method, Invokable<?, ?>> builder();
for (Method method : key.getRawType().getMethods())
builder.put(method, method(key, method));
/**
* this gets all declared methods, not just public ones. makes them accessible. Does not include Object methods.
*/
private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> methodsForTypeToken = CacheBuilder
.newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
public Set<Invokable<?, ?>> load(TypeToken<?> key) {
ImmutableSet.Builder<Invokable<?, ?>> builder = ImmutableSet.<Invokable<?, ?>> builder();
for (TypeToken<?> token : key.getTypes()) {
Class<?> raw = token.getRawType();
if (raw == Object.class)
continue;
for (Method method : raw.getDeclaredMethods()) {
method.setAccessible(true);
builder.add(key.method(method));
}
}
return builder.build();
}
});
/**
* ensures that exceptions are not doubly-wrapped
*/
private static <K, V> V get(LoadingCache<K, V> cache, K key) {
try {
return cache.get(key);
} catch (UncheckedExecutionException e) {
throw propagate(e.getCause());
} catch (ExecutionException e) {
throw propagate(e.getCause());
}
}
}

View File

@ -20,7 +20,6 @@ package org.jclouds.rest.config;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.collect.Iterables.toArray;
import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Maps.transformValues;
@ -32,11 +31,9 @@ import static org.jclouds.rest.config.BinderUtils.bindHttpApi;
import static org.jclouds.util.Maps2.transformKeys;
import static org.jclouds.util.Predicates2.startsWith;
import java.lang.reflect.Method;
import java.net.Proxy;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Named;
@ -47,7 +44,6 @@ import org.jclouds.http.functions.config.SaxParserModule;
import org.jclouds.internal.FilterStringsBoundToInjectorByName;
import org.jclouds.json.config.GsonModule;
import org.jclouds.location.config.LocationModule;
import com.google.common.reflect.Invokable;
import org.jclouds.proxy.ProxyForURI;
import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.HttpAsyncClient;
@ -55,13 +51,14 @@ import org.jclouds.rest.HttpClient;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.internal.BlockOnFuture;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
@ -80,8 +77,6 @@ public class RestModule extends AbstractModule {
this(ImmutableMap.<Class<?>, Class<?>> of());
}
private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods());
public RestModule(Map<Class<?>, Class<?>> sync2Async) {
this.sync2Async = sync2Async;
}
@ -92,6 +87,11 @@ public class RestModule extends AbstractModule {
@Provides
@Singleton
protected Cache<Invokable<?, ?>, Invokable<?, ?>> seedKnownSync2AsyncInvokables() {
return seedKnownSync2AsyncInvokables(sync2Async);
}
@VisibleForTesting
static Cache<Invokable<?, ?>, Invokable<?, ?>> seedKnownSync2AsyncInvokables(Map<Class<?>, Class<?>> sync2Async) {
Cache<Invokable<?, ?>, Invokable<?, ?>> sync2AsyncBuilder = CacheBuilder.newBuilder().build();
putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder);
for (Class<?> s : sync2Async.keySet()) {
@ -103,18 +103,10 @@ public class RestModule extends AbstractModule {
// accessible for ClientProvider
public static void putInvokables(Class<?> sync, Class<?> async, Cache<Invokable<?, ?>, Invokable<?, ?>> cache) {
for (Invokable<?, ?> invoked : methods(sync)) {
if (!objectMethods.contains(invoked)) {
try {
Invokable<?, ?> delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked));
checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()),
"invoked %s has different typed exceptions than delegated invoked %s", invoked, delegatedMethod);
invoked.setAccessible(true);
delegatedMethod.setAccessible(true);
"invoked %s has different typed exceptions than target %s", invoked, delegatedMethod);
cache.put(invoked, delegatedMethod);
} catch (SecurityException e) {
throw propagate(e);
}
}
}
}

View File

@ -19,14 +19,13 @@
package org.jclouds.rest.config;
import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.name.Names.named;
import org.jclouds.reflect.Invocation;
import com.google.inject.AbstractModule;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
/**
* Allows the provider to supply a value set in a threadlocal.
@ -55,8 +54,7 @@ public class SetCaller {
}
}
private static final Key<Invocation> CALLER_INVOCATION = Key.get(new TypeLiteral<Invocation>() {
}, Names.named("caller"));
private static final Key<Invocation> CALLER_INVOCATION = Key.get(Invocation.class, named("caller"));
class CallerInvocationProvider implements Provider<Invocation> {
@Override

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");
}
}
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;
}
@Override
public String toString() { return "ValidatedConstructor[foo=" + foo + ",bar=" + bar + "]"; }
return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.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());
}
}
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();
@ -230,21 +186,19 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
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}"));
@ -261,38 +215,32 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
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}}"));
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

@ -19,12 +19,15 @@
package org.jclouds.reflect;
import static com.google.common.base.Functions.toStringFunction;
import static org.jclouds.reflect.Reflection2.constructors;
import static org.jclouds.reflect.Reflection2.method;
import static org.jclouds.reflect.Reflection2.methods;
import static org.jclouds.reflect.Reflection2.typeToken;
import static org.testng.Assert.assertEquals;
import java.util.HashSet;
import java.util.Set;
import java.util.SortedSet;
import org.testng.annotations.Test;
@ -32,7 +35,9 @@ import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken;
import com.google.inject.TypeLiteral;
/**
*
@ -41,7 +46,31 @@ import com.google.common.reflect.TypeToken;
@Test
public class Reflection2Test {
public void testTypeToken() {
/**
* useful when converting to and from type literals from other libraries such as guice and gson.
*/
public void testTypeTokenForType() {
TypeLiteral<Set<String>> guice = new TypeLiteral<Set<String>>() {
};
assertEquals(typeToken(guice.getType()), new TypeToken<Set<String>>() {
private static final long serialVersionUID = 1L;
});
}
public void testConstructors() {
Set<String> ctorParams = FluentIterable.from(constructors(TypeToken.of(HashSet.class)))
.transform(new Function<Invokable<?, ?>, Iterable<Parameter>>() {
public Iterable<Parameter> apply(Invokable<?, ?> input) {
return input.getParameters();
}
}).transform(toStringFunction()).toSet();
assertEquals(ctorParams, ImmutableSet.of("[]", "[java.util.Collection<? extends E> arg0]",
"[int arg0, float arg1]", "[int arg0]", "[int arg0, float arg1, boolean arg2]"));
}
public void testTypeTokenForClass() {
assertEquals(typeToken(String.class), TypeToken.of(String.class));
}
@ -65,15 +94,28 @@ public class Reflection2Test {
assertEquals(methodInSuper.getParameters().get(0).getType().getRawType(), Object.class);
}
ImmutableSet<String> setMethods = ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains",
"addAll", "size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll");
public void testMethods() {
Set<String> methodNames = FluentIterable.from(methods(Set.class))
.transform(new Function<Invokable<?, ?>, String>() {
Set<String> methodNames = FluentIterable.from(methods(Set.class)).transform(invokableToName)
.transform(toStringFunction()).toSet();
assertEquals(methodNames, setMethods);
}
public void testMethodsSubClass() {
Set<String> methodNames = FluentIterable.from(methods(SortedSet.class)).transform(invokableToName)
.transform(toStringFunction()).toSet();
assertEquals(methodNames,
ImmutableSet.builder().add("comparator", "last", "first", "subSet", "headSet", "tailSet")
.addAll(setMethods).build());
}
static final Function<Invokable<?, ?>, String> invokableToName = new Function<Invokable<?, ?>, String>() {
public String apply(Invokable<?, ?> input) {
return input.getName();
}
}).transform(toStringFunction()).toSet();
assertEquals(methodNames, ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains", "addAll",
"size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll"));
}
};
}

View File

@ -0,0 +1,120 @@
/**
* 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.rest.config;
import static com.google.common.base.Predicates.not;
import static com.google.common.collect.Maps.filterEntries;
import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import org.jclouds.rest.HttpAsyncClient;
import org.jclouds.rest.HttpClient;
import org.testng.annotations.Test;
import com.google.common.base.Predicate;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.reflect.Invokable;
import com.google.common.reflect.TypeToken;
import com.google.common.util.concurrent.ListenableFuture;
/**
*
* @author Adrian Cole
*/
@Test(groups = "unit")
public class RestModuleTest {
static interface Sync {
String get();
}
private static interface Async {
ListenableFuture<String> get();
}
public void testPutInvokablesWhenInterfacesMatch() {
Cache<Invokable<?, ?>, Invokable<?, ?>> cache = CacheBuilder.newBuilder().build();
RestModule.putInvokables(Sync.class, Async.class, cache);
assertEquals(cache.size(), 1);
Invokable<?, ?> sync = cache.asMap().keySet().iterator().next();
assertEquals(sync.getOwnerType().getRawType(), Sync.class);
assertEquals(sync.getName(), "get");
assertEquals(sync.getReturnType(), TypeToken.of(String.class));
Invokable<?, ?> async = cache.getIfPresent(sync);
assertEquals(async.getOwnerType().getRawType(), Async.class);
assertEquals(async.getName(), "get");
assertEquals(async.getReturnType(), new TypeToken<ListenableFuture<String>>() {
private static final long serialVersionUID = 1L;
});
}
private static interface AsyncWithException {
ListenableFuture<String> get() throws IOException;
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* has different typed exceptions than target .*")
public void testPutInvokablesWhenInterfacesMatchExceptExceptions() {
Cache<Invokable<?, ?>, Invokable<?, ?>> cache = CacheBuilder.newBuilder().build();
RestModule.putInvokables(Sync.class, AsyncWithException.class, cache);
}
private static interface AsyncWithMisnamedMethod {
ListenableFuture<String> got();
}
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "no such method .*")
public void testPutInvokablesWhenTargetMethodNotFound() {
Cache<Invokable<?, ?>, Invokable<?, ?>> cache = CacheBuilder.newBuilder().build();
RestModule.putInvokables(Sync.class, AsyncWithMisnamedMethod.class, cache);
}
static final Predicate<Entry<Invokable<?, ?>, Invokable<?, ?>>> isHttpInvokable = new Predicate<Map.Entry<Invokable<?, ?>, Invokable<?, ?>>>() {
public boolean apply(Map.Entry<Invokable<?, ?>, Invokable<?, ?>> in) {
return in.getKey().getOwnerType().getRawType().equals(HttpClient.class)
&& in.getValue().getOwnerType().getRawType().equals(HttpAsyncClient.class);
}
};
public void testSeedKnownSync2AsyncIncludesHttpClientByDefault() {
Map<Invokable<?, ?>, Invokable<?, ?>> cache = RestModule.seedKnownSync2AsyncInvokables(
ImmutableMap.<Class<?>, Class<?>> of()).asMap();
assertEquals(cache.size(), 6);
assertEquals(filterEntries(cache, isHttpInvokable), cache);
}
public void testSeedKnownSync2AsyncInvokablesInterfacesMatch() {
Map<Invokable<?, ?>, Invokable<?, ?>> cache = RestModule.seedKnownSync2AsyncInvokables(
ImmutableMap.<Class<?>, Class<?>> of(Sync.class, Async.class)).asMap();
assertEquals(cache.size(), 7);
cache = filterEntries(cache, not(isHttpInvokable));
assertEquals(cache.size(), 1);
}
}

View File

@ -22,8 +22,9 @@ package org.jclouds.abiquo.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.transform;
import static org.jclouds.reflect.Reflection2.constructor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.Collection;
import java.util.List;
@ -44,6 +45,7 @@ import com.abiquo.server.core.task.TaskDto;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
/**
* This class is used to decorate transport objects with high level
@ -103,13 +105,12 @@ public abstract class DomainWrapper<T extends SingleResourceTransportDto> {
}
try {
Constructor<W> cons = wrapperClass.getDeclaredConstructor(RestContext.class, target.getClass());
if (!cons.isAccessible()) {
cons.setAccessible(true);
}
return cons.newInstance(context, target);
} catch (Exception ex) {
throw new WrapperException(wrapperClass, target, ex);
Invokable<W, W> cons = constructor(wrapperClass, RestContext.class, target.getClass());
return cons.invoke(null, context, target);
} catch (InvocationTargetException e) {
throw new WrapperException(wrapperClass, target, e.getTargetException());
} catch (IllegalAccessException e) {
throw new WrapperException(wrapperClass, target, e);
}
}

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> {