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

View File

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

View File

@ -44,10 +44,6 @@ import com.google.common.collect.Iterables;
*/ */
@Beta @Beta
public class PaginatedCollection<T> extends IterableWithMarker<T> { 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<T> resources;
private Iterable<Link> links; private Iterable<Link> links;

View File

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

View File

@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral;
*/ */
@Beta @Beta
@Singleton @Singleton
public class ParseUsers extends ParseJson<Users<? extends User>> { public class ParseUsers extends ParseJson<Users> {
static class Users<T extends User> extends PaginatedCollection<T> { static class Users extends PaginatedCollection<User> {
@ConstructorProperties({ "users", "users_links" }) @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); super(users, users_links);
} }
@ -60,8 +60,7 @@ public class ParseUsers extends ParseJson<Users<? extends User>> {
@Inject @Inject
public ParseUsers(Json json) { 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> { 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 @Beta
@Singleton @Singleton
public class ParseFlavorDetails extends ParseJson<Flavors<? extends Flavor>> { public class ParseFlavorDetails extends ParseJson<Flavors> {
static class Flavors<T extends Flavor> extends PaginatedCollection<T> { static class Flavors extends PaginatedCollection<Flavor> {
@ConstructorProperties({ "flavors", "flavors_links" }) @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); super(flavors, flavors_links);
} }
@ -60,8 +60,7 @@ public class ParseFlavorDetails extends ParseJson<Flavors<? extends Flavor>> {
@Inject @Inject
public ParseFlavorDetails(Json json) { 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> { public static class ToPagedIterable extends CallerArg0ToPagedIterable<Flavor, ToPagedIterable> {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,8 +53,7 @@ public class FilterStringsBoundToInjectorByName implements Function<Predicate<St
@Override @Override
public Map<String, String> apply(Predicate<String> filter) { 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>>() { Iterable<Binding<String>> annotatedWithName = Iterables.filter(stringBindings, new Predicate<Binding<String>>() {
@Override @Override

View File

@ -40,15 +40,16 @@ import org.jclouds.json.Json;
import org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactory; import org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactory;
import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue; import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue;
import org.jclouds.json.internal.GsonWrapper; 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.AnnotationConstructorNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy; import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.ExtractNamed; import org.jclouds.json.internal.NamingStrategies.ExtractNamed;
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName; 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.NullHackJsonLiteralAdapter;
import org.jclouds.json.internal.OptionalTypeAdapterFactory; import org.jclouds.json.internal.OptionalTypeAdapterFactory;
@ -85,13 +86,12 @@ public class GsonModule extends AbstractModule {
@Singleton @Singleton
Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter, Gson provideGson(TypeAdapter<JsonBall> jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter,
ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings, ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings,
OptionalTypeAdapterFactory optional, IgnoreNullSetTypeAdapterFactory set, OptionalTypeAdapterFactory optional, SetTypeAdapterFactory set, MapTypeAdapterFactory map,
IgnoreNullMapTypeAdapterFactory map, IgnoreNullMultimapTypeAdapterFactory multimap, MultimapTypeAdapterFactory multimap, IterableTypeAdapterFactory iterable,
IgnoreNullIterableTypeAdapterFactory iterable, IgnoreNullFluentIterableTypeAdapterFactory fluentIterable) CollectionTypeAdapterFactory collection, FluentIterableTypeAdapterFactory fluentIterable) throws Exception {
throws Exception {
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(new ExtractSerializedName(), FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
new ExtractNamed()); new ExtractSerializedName(), new ExtractNamed()));
GsonBuilder builder = new GsonBuilder().setFieldNamingStrategy(serializationPolicy); GsonBuilder builder = new GsonBuilder().setFieldNamingStrategy(serializationPolicy);
@ -104,18 +104,17 @@ public class GsonModule extends AbstractModule {
builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe()); builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe());
builder.registerTypeAdapterFactory(optional); builder.registerTypeAdapterFactory(optional);
builder.registerTypeAdapterFactory(iterable); builder.registerTypeAdapterFactory(iterable);
builder.registerTypeAdapterFactory(collection);
builder.registerTypeAdapterFactory(set); builder.registerTypeAdapterFactory(set);
builder.registerTypeAdapterFactory(map); builder.registerTypeAdapterFactory(map);
builder.registerTypeAdapterFactory(multimap); builder.registerTypeAdapterFactory(multimap);
builder.registerTypeAdapterFactory(fluentIterable); builder.registerTypeAdapterFactory(fluentIterable);
AnnotationConstructorNamingStrategy deserializationPolicy = AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy(
new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed())); ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
builder.registerTypeAdapterFactory( builder.registerTypeAdapterFactory(new DeserializationConstructorAndReflectiveTypeAdapterFactory(
new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(), new ConstructorConstructor(), serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
serializationPolicy, Excluder.DEFAULT, deserializationPolicy));
// complicated (serializers/deserializers as they need context to operate) // complicated (serializers/deserializers as they need context to operate)
builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue()); 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static org.jclouds.reflect.Reflection2.typeToken;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type; import java.util.List;
import java.util.Map; 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.FieldNamingStrategy;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.TypeAdapterFactory; import com.google.gson.TypeAdapterFactory;
import com.google.gson.internal.$Gson$Types;
import com.google.gson.internal.ConstructorConstructor; import com.google.gson.internal.ConstructorConstructor;
import com.google.gson.internal.Excluder; import com.google.gson.internal.Excluder;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
@ -49,17 +52,16 @@ import com.google.gson.stream.JsonWriter;
* <p/> * <p/>
* <ul> * <ul>
* <li>Deserialization</li> * <li>Deserialization</li>
* If there's an annotation designating a parameterized constructor, invoke that for fields * If there's an annotation designating a parameterized constructor, invoke that for fields correlating to named
* correlating to named parameter annotations. Otherwise, use {@link ConstructorConstructor}, and * parameter annotations. Otherwise, use {@link ConstructorConstructor}, and set fields via reflection.
* set fields via reflection.
* <p/> * <p/>
* Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and * Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and the empty object
* the empty object ({}) is treated as a null if the constructor for the object throws an NPE. * ({}) is treated as a null if the constructor for the object throws an NPE.
* <li>Serialization</li> * <li>Serialization</li> Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor.
* Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor.
* </ul> * </ul>
* <h3>Example: Using javax inject to select a constructor and corresponding named parameters</h3> * <h3>Example: Using javax inject to select a constructor and corresponding named parameters</h3>
* <p/> * <p/>
*
* <pre> * <pre>
* *
* import NamingStrategies.*; * import NamingStrategies.*;
@ -80,6 +82,7 @@ import com.google.gson.stream.JsonWriter;
* <p/> * <p/>
* The above would work fine on the following class, which has no gson-specific annotations: * The above would work fine on the following class, which has no gson-specific annotations:
* <p/> * <p/>
*
* <pre> * <pre>
* private static class ImmutableAndVerifiedInCtor { * private static class ImmutableAndVerifiedInCtor {
* final int foo; * final int foo;
@ -102,49 +105,42 @@ import com.google.gson.stream.JsonWriter;
* @author Adam Lowe * @author Adam Lowe
*/ */
public final class DeserializationConstructorAndReflectiveTypeAdapterFactory implements TypeAdapterFactory { public final class DeserializationConstructorAndReflectiveTypeAdapterFactory implements TypeAdapterFactory {
private final ConstructorFieldNamingStrategy constructorFieldNamingPolicy; private final AnnotationConstructorNamingStrategy constructorFieldNamingPolicy;
private final ReflectiveTypeAdapterFactory delegateFactory; 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 * @see ReflectiveTypeAdapterFactory
*/ */
public DeserializationConstructorAndReflectiveTypeAdapterFactory( public DeserializationConstructorAndReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor,
ConstructorConstructor constructorConstructor, FieldNamingStrategy serializationFieldNamingPolicy, Excluder excluder,
FieldNamingStrategy serializationFieldNamingPolicy, AnnotationConstructorNamingStrategy deserializationFieldNamingPolicy) {
Excluder excluder, this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy,
ConstructorFieldNamingStrategy deserializationFieldNamingPolicy) { "deserializationFieldNamingPolicy");
this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy, "deserializationFieldNamingPolicy"); this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull(
this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull(serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder")); serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder"));
} }
public <T> TypeAdapter<T> create(Gson gson, final TypeToken<T> type) { public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<? super T> raw = type.getRawType(); com.google.common.reflect.TypeToken<T> token = typeToken(type.getType());
Constructor<? super T> deserializationCtor = constructorFieldNamingPolicy.getDeserializationConstructor(raw); Invokable<T, T> deserializationCtor = constructorFieldNamingPolicy.getDeserializer(token);
if (deserializationCtor == null) { if (deserializationCtor == null) {
return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create()) return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create())
} else { } else {
deserializationCtor.setAccessible(true); return new DeserializeIntoParameterizedConstructor<T>(delegateFactory.create(gson, type), deserializationCtor,
return new DeserializeWithParameterizedConstructorSerializeWithDelegate<T>(delegateFactory.create(gson, type), deserializationCtor, getParameterReaders(gson, deserializationCtor));
getParameterReaders(gson, type, deserializationCtor));
} }
} }
private final class DeserializeWithParameterizedConstructorSerializeWithDelegate<T> extends TypeAdapter<T> { private final class DeserializeIntoParameterizedConstructor<T> extends TypeAdapter<T> {
private final Constructor<? super T> parameterizedCtor; private final TypeAdapter<T> serializer;
private final Invokable<T, T> parameterizedCtor;
private final Map<String, ParameterReader<?>> parameterReaders; private final Map<String, ParameterReader<?>> parameterReaders;
private final TypeAdapter<T> delegate;
private DeserializeWithParameterizedConstructorSerializeWithDelegate(TypeAdapter<T> delegate, private DeserializeIntoParameterizedConstructor(TypeAdapter<T> serializer, Invokable<T, T> deserializationCtor,
Constructor<? super T> parameterizedCtor, Map<String, ParameterReader<?>> parameterReaders) { Map<String, ParameterReader<?>> parameterReaders) {
this.delegate = delegate; this.serializer = serializer;
this.parameterizedCtor = parameterizedCtor; this.parameterizedCtor = deserializationCtor;
this.parameterReaders = parameterReaders; this.parameterReaders = parameterReaders;
} }
@ -155,16 +151,16 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
return null; return null;
} }
Class<?>[] paramTypes = parameterizedCtor.getParameterTypes(); List<Parameter> params = parameterizedCtor.getParameters();
Object[] ctorParams = new Object[paramTypes.length]; Object[] values = new Object[params.size()];
boolean empty = true; boolean empty = true;
// Set all primitive constructor params to defaults // Set all primitive constructor params to defaults
for (int i = 0; i < paramTypes.length; i++) { for (Parameter param : params) {
if (paramTypes[i] == boolean.class) { if (param.getType().getRawType() == boolean.class) {
ctorParams[i] = Boolean.FALSE; values[param.hashCode()] = Boolean.FALSE;
} else if (paramTypes[i].isPrimitive()) { } else if (param.getType().getRawType().isPrimitive()) {
ctorParams[i] = 0; values[param.hashCode()] = 0;
} }
} }
@ -178,26 +174,27 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
in.skipValue(); in.skipValue();
} else { } else {
Object value = parameter.read(in); Object value = parameter.read(in);
if (value != null) ctorParams[parameter.index] = value; if (value != null)
values[parameter.position] = value;
} }
} }
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
throw new JsonSyntaxException(e); throw new JsonSyntaxException(e);
} }
for (int i = 0; i < paramTypes.length; i++) { for (Parameter param : params) {
if (paramTypes[i].isPrimitive()) { if (param.getType().getRawType().isPrimitive()) {
checkArgument(ctorParams[i] != null, "Primitive param[" + i + "] in constructor " + parameterizedCtor checkArgument(values[param.hashCode()] != null, "Primitive param[" + param.hashCode()
+ " cannot be absent!"); + "] in constructor " + parameterizedCtor + " cannot be absent!");
} }
} }
in.endObject(); in.endObject();
try { try {
return newInstance(ctorParams); return newInstance(values);
} catch (NullPointerException ex) { } catch (NullPointerException ex) {
// If {} was found and constructor threw NPE, we treat the field as null // 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; return null;
} }
throw ex; throw ex;
@ -209,15 +206,12 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
*/ */
@Override @Override
public void write(JsonWriter out, T value) throws IOException { 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 { private T newInstance(Object[] ctorParams) throws AssertionError {
try { try {
return (T) parameterizedCtor.newInstance(ctorParams); return (T) parameterizedCtor.invoke(null, ctorParams);
} catch (InstantiationException e) {
throw new AssertionError(e);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
throw new AssertionError(e); throw new AssertionError(e);
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
@ -226,43 +220,79 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp
throw new AssertionError(e); 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 // logic borrowed from ReflectiveTypeAdapterFactory
static class ParameterReader<T> { static class ParameterReader<T> {
final String name; final String name;
final int index; final int position;
final TypeAdapter<T> typeAdapter; final TypeAdapter<T> typeAdapter;
ParameterReader(String name, int index, TypeAdapter<T> typeAdapter) { ParameterReader(int position, String name, TypeAdapter<T> typeAdapter) {
this.name = name; this.name = name;
this.index = index; this.position = position;
this.typeAdapter = typeAdapter; this.typeAdapter = typeAdapter;
} }
public Object read(JsonReader reader) throws IOException { public Object read(JsonReader reader) throws IOException {
return typeAdapter.read(reader); 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) { @Override
Map<String, ParameterReader<?>> result = Maps.newLinkedHashMap(); public int hashCode() {
return Objects.hashCode(position, name);
}
for (int index = 0; index < constructor.getGenericParameterTypes().length; index++) { @Override
Type parameterType = getTypeOfConstructorParameter(declaring, constructor, index); public String toString() {
TypeAdapter<?> adapter = context.getAdapter(TypeToken.get(parameterType)); return typeAdapter + " arg" + position;
String parameterName = constructorFieldNamingPolicy.translateName(constructor, index); }
checkArgument(parameterName != null, constructor + " parameter " + 0 + " failed to be named by " + constructorFieldNamingPolicy); }
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" }) @SuppressWarnings({ "rawtypes", "unchecked" })
ParameterReader<?> parameterReader = new ParameterReader(parameterName, index, adapter); ParameterReader<?> parameterReader = new ParameterReader(param.hashCode(), parameterName, adapter);
ParameterReader<?> previous = result.put(parameterReader.name, parameterReader); result.put(parameterReader.name, parameterReader);
checkArgument(previous == null, constructor + " declares multiple JSON parameters named " + parameterReader.name);
} }
return result; return result.build();
}
private Type getTypeOfConstructorParameter(TypeToken<?> declaring, Constructor<?> constructor, int index) {
Type genericParameter = constructor.getGenericParameterTypes()[index];
return $Gson$Types.resolve(declaring.getType(), declaring.getRawType(), genericParameter);
} }
} }

View File

@ -18,13 +18,11 @@
*/ */
package org.jclouds.json.internal; package org.jclouds.json.internal;
import java.lang.reflect.Method; import static org.jclouds.reflect.Reflection2.method;
import java.lang.reflect.Type;
import java.util.concurrent.ExecutionException;
import com.google.common.cache.CacheBuilder; import java.lang.reflect.Type;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.reflect.Invokable;
import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer; import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; 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()); return (T) Enum.valueOf((Class<T>) classOfT, json.getAsString());
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
try { try {
Method converter = classToConvert.get((Class<?>) classOfT); Invokable<?, Object> converter = method((Class<?>) classOfT, "fromValue", String.class);
return (T) converter.invoke(null, json.getAsString()); return (T) converter.invoke(null, json.getAsString());
} catch (Exception e1) { } catch (Exception e1) {
throw e; 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; 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.beans.ConstructorProperties;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map; import java.util.Map;
import java.util.Set;
import javax.inject.Named; import javax.inject.Named;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet; import com.google.common.base.Predicate;
import com.google.common.collect.Iterables; import com.google.common.base.Supplier;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps; 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.FieldNamingStrategy;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -46,23 +54,40 @@ import com.google.gson.annotations.SerializedName;
*/ */
public class NamingStrategies { public class NamingStrategies {
/** /**
* Specifies how to extract the name from an annotation for use in determining the serialized * Specifies how to extract the name from an annotation for use in determining the serialized name.
* name.
* *
* @see com.google.gson.annotations.SerializedName * @see com.google.gson.annotations.SerializedName
* @see ExtractSerializedName * @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 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.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() { @Override
return annotationType; public String apply(Annotation in) {
return extractName(annotationType.cast(in));
}
protected abstract String extractName(A cast);
@Override
public Predicate<Annotation> get() {
return predicate;
} }
@Override @Override
@ -90,7 +115,6 @@ public class NamingStrategies {
super(SerializedName.class); super(SerializedName.class);
} }
@Override
public String extractName(SerializedName in) { public String extractName(SerializedName in) {
return checkNotNull(in, "input annotation").value(); return checkNotNull(in, "input annotation").value();
} }
@ -108,22 +132,22 @@ public class NamingStrategies {
} }
public abstract static class AnnotationBasedNamingStrategy { public abstract static class AnnotationBasedNamingStrategy {
protected final Map<Class<? extends Annotation>, ? extends NameExtractor> annotationToNameExtractor; protected final Map<Class<? extends Annotation>, ? extends NameExtractor<?>> annotationToNameExtractor;
private String forToString; 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"); 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 @Override
public Class<? extends Annotation> apply(NameExtractor input) { public Class<? extends Annotation> apply(NameExtractor<?> input) {
return input.annotationType(); 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 @Override
public String apply(NameExtractor input) { public String apply(NameExtractor<?> input) {
return input.annotationType().getName(); return input.annotationType().getName();
} }
})); }));
@ -138,32 +162,30 @@ public class NamingStrategies {
/** /**
* Definition of field naming policy for annotation-based field * 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); super(extractors);
checkArgument(extractors.iterator().hasNext(), "you must supply at least one name extractor, for example: " checkArgument(extractors.iterator().hasNext(), "you must supply at least one name extractor, for example: "
+ ExtractSerializedName.class.getSimpleName()); + ExtractSerializedName.class.getSimpleName());
} }
@SuppressWarnings("unchecked")
@Override @Override
public String translateName(Field f) { public String translateName(Field f) {
for (Annotation annotation : f.getAnnotations()) { for (Annotation annotation : f.getAnnotations()) {
if (annotationToNameExtractor.containsKey(annotation.annotationType())) { if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
return annotationToNameExtractor.get(annotation.annotationType()).extractName(annotation); return annotationToNameExtractor.get(annotation.annotationType()).apply(annotation);
} }
} }
return null; return null;
} }
} }
public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements FieldNamingStrategy { public static class AnnotationOrNameFieldNamingStrategy extends AnnotationFieldNamingStrategy implements
public AnnotationOrNameFieldNamingStrategy(NameExtractor... extractors) { FieldNamingStrategy {
this(ImmutableSet.copyOf(extractors));
}
public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor> extractors) { public AnnotationOrNameFieldNamingStrategy(Iterable<? extends NameExtractor<?>> extractors) {
super(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 * Determines field naming from constructor annotations
*/ */
public static class AnnotationConstructorNamingStrategy extends AnnotationBasedNamingStrategy implements ConstructorFieldNamingStrategy { public final static class AnnotationConstructorNamingStrategy extends AnnotationBasedNamingStrategy {
private final Set<Class<? extends Annotation>> markers; 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); 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") private static Predicate<Invokable<?, ?>> hasAnnotationIn(
public <T> Constructor<? super T> getDeserializationConstructor(Class<?> raw) { final Collection<? extends Class<? extends Annotation>> markers) {
for (Constructor<?> ctor : raw.getDeclaredConstructors()) return new Predicate<Invokable<?, ?>>() {
for (Class<? extends Annotation> deserializationCtorAnnotation : markers) public boolean apply(Invokable<?, ?> input) {
if (ctor.isAnnotationPresent(deserializationCtorAnnotation)) return FluentIterable.from(Arrays.asList(input.getAnnotations()))
return (Constructor<T>) ctor; .transform(new Function<Annotation, Class<? extends Annotation>>() {
public Class<? extends Annotation> apply(Annotation input) {
return null; return input.annotationType();
}
}).anyMatch(in(markers));
}
};
} }
@SuppressWarnings("unchecked") @VisibleForTesting
@Override <T> Invokable<T, T> getDeserializer(TypeToken<T> token) {
public String translateName(Constructor<?> c, int index) { return tryFind(constructors(token), hasMarker).orNull();
}
@VisibleForTesting
<T> String translateName(Invokable<T, T> c, int index) {
String name = null; String name = null;
if (markers.contains(ConstructorProperties.class) && c.getAnnotation(ConstructorProperties.class) != 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())) { if (annotationToNameExtractor.containsKey(annotation.annotationType())) {
name = annotationToNameExtractor.get(annotation.annotationType()).extractName(annotation); name = annotationToNameExtractor.get(annotation.annotationType()).apply(annotation);
break; 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; package org.jclouds.lifecycle.config;
import static com.google.common.base.Throwables.propagate; 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.common.util.concurrent.MoreExecutors.sameThreadExecutor;
import static com.google.inject.matcher.Matchers.any; 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_IO_WORKER_THREADS;
import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS; import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS;
import static org.jclouds.Constants.PROPERTY_USER_THREADS; import static org.jclouds.Constants.PROPERTY_USER_THREADS;
import static org.jclouds.reflect.Reflection2.methods;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -40,6 +39,8 @@ import javax.inject.Named;
import org.jclouds.lifecycle.Closer; 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.ExecutionList;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@ -51,15 +52,15 @@ import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener; import com.google.inject.spi.TypeListener;
/** /**
* This associates java lifecycle annotations with guice hooks. For example, we invoke * This associates java lifecycle annotations with guice hooks. For example, we invoke {@link PostConstruct} after
* {@link PostConstruct} after injection, and Associate {@link PreDestroy} with a global * injection, and Associate {@link PreDestroy} with a global {@link Closer} object.
* {@link Closer} object.
* *
* <h3>Important</h3> Make sure you create your injector with {@link Stage#PRODUCTION} and execute * <h3>Important</h3> Make sure you create your injector with {@link Stage#PRODUCTION} and execute the bound
* the bound {@link ExecutionList} prior to using any other objects. * {@link ExecutionList} prior to using any other objects.
* *
* <p/> * <p/>
* Ex. * Ex.
*
* <pre> * <pre>
* *
* </pre> * </pre>
@ -103,67 +104,57 @@ public class LifeCycleModule extends AbstractModule {
bind(ExecutionList.class).toInstance(list); 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) { protected void bindPostInjectionInvoke(final Closer closer, final ExecutionList list) {
bindListener(any(), new TypeListener() { bindListener(any(), new TypeListener() {
public <I> void hear(TypeLiteral<I> injectableType, TypeEncounter<I> encounter) { public <I> void hear(TypeLiteral<I> injectableType, TypeEncounter<I> encounter) {
Set<Method> methods = newHashSet(); Collection<? extends Invokable<? super I, Object>> methods = methods(injectableType.getRawType());
Class<? super I> type = injectableType.getRawType(); for (final Invokable<? super I, Object> method : filter(methods, isPostConstruct)) {
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) {
encounter.register(new InjectionListener<I>() { encounter.register(new InjectionListener<I>() {
public void afterInjection(final I injectee) { public void afterInjection(final I injectee) {
list.add(new Runnable() { list.add(new Runnable() {
public void run() { public void run() {
try { invokeOnInjectee(method, injectee);
method.invoke(injectee);
} catch (InvocationTargetException ie) {
Throwable e = ie.getTargetException();
throw propagate(e);
} catch (IllegalAccessException e) {
throw propagate(e);
}
} }
}, sameThreadExecutor()); }, 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; package org.jclouds.reflect;
import static com.google.common.base.Preconditions.checkNotNull; 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.Method;
import java.lang.reflect.Type;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.annotations.Beta;
import com.google.common.base.Function;
import com.google.common.base.Objects; 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.CacheBuilder;
import com.google.common.cache.CacheLoader; import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache; import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap.Builder; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable; import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken; 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}. * 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 * @since 1.6
*/ */
@Beta @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. * gets a {@link TypeToken} for the given class.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> TypeToken<T> typeToken(Class<T> in) { 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") @SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(TypeToken<T> ownerType, Method method) { 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") @SuppressWarnings("unchecked")
public static <T, R> Invokable<T, R> method(Class<T> ownerType, String name, Class<?>... parameterTypes) { 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)); parameterTypes));
} }
@ -91,51 +137,65 @@ public final class Reflection2 {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> Collection<Invokable<T, Object>> methods(Class<T> ownerType) { 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<?, ?>>() { * this gets all declared constructors, not just public ones. makes them accessible, as well.
public Invokable<?, ?> load(TypeTokenAndMethod key) { */
return key.type.method(key.method); 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 static List<Class<?>> toClasses(ImmutableList<Parameter> params) {
return Lists.transform(params, new Function<Parameter, Class<?>>() {
protected final TypeToken<?> type; public Class<?> apply(Parameter input) {
protected final Method method; return input.getType().getRawType();
}
public TypeTokenAndMethod(TypeToken<?> type, Method method) { });
this.type = checkNotNull(type, "type");
this.method = checkNotNull(method, "method");
} }
public int hashCode() { private static LoadingCache<Type, TypeToken<?>> typeTokenForType = CacheBuilder.newBuilder().build(
return Objects.hashCode(type, method); new CacheLoader<Type, TypeToken<?>>() {
} public TypeToken<?> load(Type key) {
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) {
return TypeToken.of(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 { private static class TypeTokenAndParameterTypes {
protected final TypeToken<?> type; protected TypeToken<?> type;
protected final List<Class<?>> parameterTypes; protected List<Class<?>> parameterTypes;
public TypeTokenAndParameterTypes(TypeToken<?> type, Class<?>... parameterTypes) { public TypeTokenAndParameterTypes(TypeToken<?> type, Class<?>... parameterTypes) {
this.type = checkNotNull(type, "type"); 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<?, ?>>() { .newBuilder().build(new CacheLoader<TypeTokenNameAndParameterTypes, Invokable<?, ?>>() {
public Invokable<?, ?> load(final TypeTokenNameAndParameterTypes key) { public Invokable<?, ?> load(final TypeTokenNameAndParameterTypes key) {
try { Set<Invokable<?, ?>> methods = get(methodsForTypeToken, key.type);
Method method = key.type.getRawType().getMethod(key.name, toArray(key.parameterTypes, Class.class)); Optional<Invokable<?, ?>> method = tryFind(methods, new Predicate<Invokable<?, ?>>() {
return methods.apply(new TypeTokenAndMethod(key.type, method)); public boolean apply(Invokable<?, ?> input) {
} catch (SecurityException e) { return Objects.equal(input.getName(), key.name)
throw new IllegalArgumentException(e.getMessage() + " getting method " + key.toString(), e); && Objects.equal(toClasses(input.getParameters()), key.parameterTypes);
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException("no such method " + key.toString(), e);
} }
});
if (method.isPresent())
return method.get();
throw new IllegalArgumentException("no such method " + key.toString() + "in: " + methods);
} }
}); });
private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes { private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes {
private final String name; private String name;
public TypeTokenNameAndParameterTypes(TypeToken<?> type, String name, Class<?>... parameterTypes) { public TypeTokenNameAndParameterTypes(TypeToken<?> type, String name, Class<?>... parameterTypes) {
super(type, 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<?, ?>>>() { * this gets all declared methods, not just public ones. makes them accessible. Does not include Object methods.
public Map<Method, Invokable<?, ?>> load(final TypeToken<?> key) { */
Builder<Method, Invokable<?, ?>> builder = ImmutableMap.<Method, Invokable<?, ?>> builder(); private static LoadingCache<TypeToken<?>, Set<Invokable<?, ?>>> methodsForTypeToken = CacheBuilder
for (Method method : key.getRawType().getMethods()) .newBuilder().build(new CacheLoader<TypeToken<?>, Set<Invokable<?, ?>>>() {
builder.put(method, method(key, method)); 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(); 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; 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.toArray;
import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.transform;
import static com.google.common.collect.Maps.transformValues; 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.Maps2.transformKeys;
import static org.jclouds.util.Predicates2.startsWith; import static org.jclouds.util.Predicates2.startsWith;
import java.lang.reflect.Method;
import java.net.Proxy; import java.net.Proxy;
import java.net.URI; import java.net.URI;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Named; import javax.inject.Named;
@ -47,7 +44,6 @@ import org.jclouds.http.functions.config.SaxParserModule;
import org.jclouds.internal.FilterStringsBoundToInjectorByName; import org.jclouds.internal.FilterStringsBoundToInjectorByName;
import org.jclouds.json.config.GsonModule; import org.jclouds.json.config.GsonModule;
import org.jclouds.location.config.LocationModule; import org.jclouds.location.config.LocationModule;
import com.google.common.reflect.Invokable;
import org.jclouds.proxy.ProxyForURI; import org.jclouds.proxy.ProxyForURI;
import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.AuthorizationException;
import org.jclouds.rest.HttpAsyncClient; import org.jclouds.rest.HttpAsyncClient;
@ -55,13 +51,14 @@ import org.jclouds.rest.HttpClient;
import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith; import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
import org.jclouds.rest.internal.BlockOnFuture; import org.jclouds.rest.internal.BlockOnFuture;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.base.Supplier; import com.google.common.base.Supplier;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap; 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.common.reflect.Parameter;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Provides; import com.google.inject.Provides;
@ -80,8 +77,6 @@ public class RestModule extends AbstractModule {
this(ImmutableMap.<Class<?>, Class<?>> of()); this(ImmutableMap.<Class<?>, Class<?>> of());
} }
private static final Set<Method> objectMethods = ImmutableSet.copyOf(Object.class.getMethods());
public RestModule(Map<Class<?>, Class<?>> sync2Async) { public RestModule(Map<Class<?>, Class<?>> sync2Async) {
this.sync2Async = sync2Async; this.sync2Async = sync2Async;
} }
@ -92,6 +87,11 @@ public class RestModule extends AbstractModule {
@Provides @Provides
@Singleton @Singleton
protected Cache<Invokable<?, ?>, Invokable<?, ?>> seedKnownSync2AsyncInvokables() { 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(); Cache<Invokable<?, ?>, Invokable<?, ?>> sync2AsyncBuilder = CacheBuilder.newBuilder().build();
putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder); putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder);
for (Class<?> s : sync2Async.keySet()) { for (Class<?> s : sync2Async.keySet()) {
@ -103,18 +103,10 @@ public class RestModule extends AbstractModule {
// accessible for ClientProvider // accessible for ClientProvider
public static void putInvokables(Class<?> sync, Class<?> async, Cache<Invokable<?, ?>, Invokable<?, ?>> cache) { public static void putInvokables(Class<?> sync, Class<?> async, Cache<Invokable<?, ?>, Invokable<?, ?>> cache) {
for (Invokable<?, ?> invoked : methods(sync)) { for (Invokable<?, ?> invoked : methods(sync)) {
if (!objectMethods.contains(invoked)) {
try {
Invokable<?, ?> delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked)); Invokable<?, ?> delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked));
checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()), checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()),
"invoked %s has different typed exceptions than delegated invoked %s", invoked, delegatedMethod); "invoked %s has different typed exceptions than target %s", invoked, delegatedMethod);
invoked.setAccessible(true);
delegatedMethod.setAccessible(true);
cache.put(invoked, delegatedMethod); cache.put(invoked, delegatedMethod);
} catch (SecurityException e) {
throw propagate(e);
}
}
} }
} }

View File

@ -19,14 +19,13 @@
package org.jclouds.rest.config; package org.jclouds.rest.config;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import static com.google.inject.name.Names.named;
import org.jclouds.reflect.Invocation; import org.jclouds.reflect.Invocation;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Key; import com.google.inject.Key;
import com.google.inject.Provider; 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. * 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>() { private static final Key<Invocation> CALLER_INVOCATION = Key.get(Invocation.class, named("caller"));
}, Names.named("caller"));
class CallerInvocationProvider implements Provider<Invocation> { class CallerInvocationProvider implements Provider<Invocation> {
@Override @Override

View File

@ -1,4 +1,5 @@
package org.jclouds.json.internal; package org.jclouds.json.internal;
/** /**
* Licensed to jclouds, Inc. (jclouds) under one or more * Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file * contributor license agreements. See the NOTICE file
@ -54,6 +55,7 @@ import com.google.gson.reflect.TypeToken;
* @author Adam Lowe * @author Adam Lowe
*/ */
@Test(testName = "DeserializationConstructorTypeAdapterFactoryTest") @Test(testName = "DeserializationConstructorTypeAdapterFactoryTest")
@SuppressWarnings("unused")
public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest { public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest {
Gson gson = new Gson(); Gson gson = new Gson();
@ -61,13 +63,10 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory = parameterizedCtorFactory(); DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory = parameterizedCtorFactory();
static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() { static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() {
FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy( FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of(
ImmutableSet.of(new ExtractSerializedName(), new ExtractNamed()) new ExtractSerializedName(), new ExtractNamed()));
); NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = new NamingStrategies.AnnotationConstructorNamingStrategy(
NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed()));
new NamingStrategies.AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class, Inject.class),
ImmutableSet.of(new ExtractNamed()));
return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(), return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
serializationPolicy, Excluder.DEFAULT, deserializationPolicy); serializationPolicy, Excluder.DEFAULT, deserializationPolicy);
@ -83,25 +82,11 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
private DefaultConstructor() { 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 { 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); 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() { public void testSerializedNameRequiredOnAllParameters() {
try { parameterizedCtorFactory
parameterizedCtorFactory.create(gson, TypeToken .create(gson, TypeToken.get(WithDeserializationConstructorButWithoutSerializedName.class));
.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");
}
} }
private static class DuplicateSerializedNames { private static class DuplicateSerializedNames {
@ -137,15 +116,9 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
} }
} }
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "duplicate key: foo")
public void testNoDuplicateSerializedNamesRequiredOnAllParameters() { public void testNoDuplicateSerializedNamesRequiredOnAllParameters() {
try {
parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class)); 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 { private static class ValidatedConstructor {
@ -160,34 +133,18 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
this.bar = bar; this.bar = bar;
} }
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
ValidatedConstructor other = ValidatedConstructor.class.cast(obj); ValidatedConstructor other = ValidatedConstructor.class.cast(obj);
if (bar != other.bar) return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar);
return false; }
if (foo != other.foo)
return false;
return true;
}
@Override
public String toString() { return "ValidatedConstructor[foo=" + foo + ",bar=" + bar + "]"; }
} }
@Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "negative!")
public void testValidatedConstructor() throws IOException { public void testValidatedConstructor() throws IOException {
TypeAdapter<ValidatedConstructor> adapter = parameterizedCtorFactory.create(gson, TypeToken TypeAdapter<ValidatedConstructor> adapter = parameterizedCtorFactory.create(gson,
.get(ValidatedConstructor.class)); TypeToken.get(ValidatedConstructor.class));
assertEquals(new ValidatedConstructor(0, 1), adapter.fromJson("{\"foo\":0,\"bar\":1}")); assertEquals(new ValidatedConstructor(0, 1), adapter.fromJson("{\"foo\":0,\"bar\":1}"));
try {
adapter.fromJson("{\"foo\":-1,\"bar\":1}"); adapter.fromJson("{\"foo\":-1,\"bar\":1}");
fail();
} catch (IllegalArgumentException expected) {
assertEquals("negative!", expected.getMessage());
}
} }
private static class GenericParamsCopiedIn { private static class GenericParamsCopiedIn {
@ -199,12 +156,11 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
this.foo = Lists.newArrayList(foo); this.foo = Lists.newArrayList(foo);
this.bar = Maps.newHashMap(bar); this.bar = Maps.newHashMap(bar);
} }
} }
public void testGenericParamsCopiedIn() throws IOException { public void testGenericParamsCopiedIn() throws IOException {
TypeAdapter<GenericParamsCopiedIn> adapter = parameterizedCtorFactory.create(gson, TypeToken TypeAdapter<GenericParamsCopiedIn> adapter = parameterizedCtorFactory.create(gson,
.get(GenericParamsCopiedIn.class)); TypeToken.get(GenericParamsCopiedIn.class));
List<String> inputFoo = Lists.newArrayList(); List<String> inputFoo = Lists.newArrayList();
inputFoo.add("one"); inputFoo.add("one");
Map<String, String> inputBar = Maps.newHashMap(); Map<String, String> inputBar = Maps.newHashMap();
@ -230,21 +186,19 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
this.bar = bar; this.bar = bar;
} }
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
RenamedFields other = RenamedFields.class.cast(obj); RenamedFields other = RenamedFields.class.cast(obj);
if (bar != other.bar) return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar);
return false;
if (foo != other.foo)
return false;
return true;
} }
} }
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 { public void testRenamedFields() throws IOException {
TypeAdapter<RenamedFields> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(RenamedFields.class)); TypeAdapter<RenamedFields> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(RenamedFields.class));
assertEquals(new RenamedFields(0, 1), adapter.fromJson("{\"foo\":0,\"_bar\":1}")); assertEquals(new RenamedFields(0, 1), adapter.fromJson("{\"foo\":0,\"_bar\":1}"));
@ -261,38 +215,32 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest
this.y = checkNotNull(y); this.y = checkNotNull(y);
} }
@Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
ComposedObjects other = ComposedObjects.class.cast(obj); ComposedObjects other = ComposedObjects.class.cast(obj);
return other != null && Objects.equal(x, other.x) && Objects.equal(y, other.y); 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 { public void checkSimpleComposedObject() throws IOException {
ValidatedConstructor x = new ValidatedConstructor(0, 1); ValidatedConstructor x = new ValidatedConstructor(0, 1);
ValidatedConstructor y = new ValidatedConstructor(1, 2); ValidatedConstructor y = new ValidatedConstructor(1, 2);
TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class)); TypeAdapter<ComposedObjects> adapter = parameterizedCtorFactory
assertEquals(new ComposedObjects(x, y), adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}")); .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 { 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("{}")); assertNull(adapter.fromJson("{}"));
} }
@Test(expectedExceptions = NullPointerException.class) @Test(expectedExceptions = NullPointerException.class)
public void testPartialObjectStillThrows() throws IOException { 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}}")); 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. * under the License.
*/ */
import static org.jclouds.reflect.Reflection2.typeToken;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNull; import static org.testng.Assert.assertNull;
import static org.testng.Assert.fail; import static org.testng.Assert.fail;
import java.beans.ConstructorProperties; import java.beans.ConstructorProperties;
import java.lang.reflect.Constructor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Named; 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.AnnotationConstructorNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationFieldNamingStrategy; import org.jclouds.json.internal.NamingStrategies.AnnotationFieldNamingStrategy;
import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy; 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.ExtractNamed;
import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName; import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName;
import org.jclouds.json.internal.NamingStrategies.NameExtractor; import org.jclouds.json.internal.NamingStrategies.NameExtractor;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Invokable;
import com.google.gson.FieldNamingStrategy; import com.google.gson.FieldNamingStrategy;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
@ -51,6 +51,7 @@ public final class NamingStrategiesTest {
private static class SimpleTest { private static class SimpleTest {
@SerializedName("aardvark") @SerializedName("aardvark")
private String a; private String a;
@SuppressWarnings("unused")
private String b; private String b;
@Named("cat") @Named("cat")
private String c; private String c;
@ -75,7 +76,7 @@ public final class NamingStrategiesTest {
public void testExtractSerializedName() throws Exception { public void testExtractSerializedName() throws Exception {
NameExtractor extractor = new ExtractSerializedName(); NameExtractor<SerializedName> extractor = new ExtractSerializedName();
assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)), assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)),
"aardvark"); "aardvark");
try { try {
@ -96,7 +97,7 @@ public final class NamingStrategiesTest {
} }
public void testExtractNamed() throws Exception { public void testExtractNamed() throws Exception {
NameExtractor extractor = new ExtractNamed(); NameExtractor<Named> extractor = new ExtractNamed();
try { try {
extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(Named.class)); extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(Named.class));
} catch (NullPointerException e) { } catch (NullPointerException e) {
@ -131,12 +132,12 @@ public final class NamingStrategiesTest {
} }
public void testAnnotationConstructorFieldNamingStrategyCPAndNamed() throws Exception { public void testAnnotationConstructorFieldNamingStrategyCPAndNamed() throws Exception {
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy( AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.of(new ExtractNamed())); 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); assertNotNull(constructor);
assertEquals(constructor.getParameterTypes().length, 4); assertEquals(constructor.getParameters().size(), 4);
assertEquals(strategy.translateName(constructor, 0), "aardvark"); assertEquals(strategy.translateName(constructor, 0), "aardvark");
assertEquals(strategy.translateName(constructor, 1), "bat"); assertEquals(strategy.translateName(constructor, 1), "bat");
@ -144,9 +145,9 @@ public final class NamingStrategiesTest {
// Note: @Named overrides the ConstructorProperties setting // Note: @Named overrides the ConstructorProperties setting
assertEquals(strategy.translateName(constructor, 3), "dingo"); 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); assertNotNull(mixedCtor);
assertEquals(mixedCtor.getParameterTypes().length, 4); assertEquals(mixedCtor.getParameters().size(), 4);
assertEquals(strategy.translateName(mixedCtor, 0), "aardvark"); assertEquals(strategy.translateName(mixedCtor, 0), "aardvark");
assertEquals(strategy.translateName(mixedCtor, 1), "bat"); assertEquals(strategy.translateName(mixedCtor, 1), "bat");
@ -155,21 +156,21 @@ public final class NamingStrategiesTest {
} }
public void testAnnotationConstructorFieldNamingStrategyCP() throws Exception { public void testAnnotationConstructorFieldNamingStrategyCP() throws Exception {
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy( AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(ConstructorProperties.class), ImmutableSet.<NameExtractor>of()); 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); assertNotNull(constructor);
assertEquals(constructor.getParameterTypes().length, 4); assertEquals(constructor.getParameters().size(), 4);
assertEquals(strategy.translateName(constructor, 0), "aardvark"); assertEquals(strategy.translateName(constructor, 0), "aardvark");
assertEquals(strategy.translateName(constructor, 1), "bat"); assertEquals(strategy.translateName(constructor, 1), "bat");
assertEquals(strategy.translateName(constructor, 2), "coyote"); assertEquals(strategy.translateName(constructor, 2), "coyote");
assertEquals(strategy.translateName(constructor, 3), "dog"); 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); assertNotNull(mixedCtor);
assertEquals(mixedCtor.getParameterTypes().length, 4); assertEquals(mixedCtor.getParameters().size(), 4);
assertEquals(strategy.translateName(mixedCtor, 0), "thiscanbeoverriddenbyNamed"); assertEquals(strategy.translateName(mixedCtor, 0), "thiscanbeoverriddenbyNamed");
assertNull(strategy.translateName(mixedCtor, 1)); assertNull(strategy.translateName(mixedCtor, 1));
@ -178,21 +179,21 @@ public final class NamingStrategiesTest {
} }
public void testAnnotationConstructorFieldNamingStrategyInject() throws Exception { public void testAnnotationConstructorFieldNamingStrategyInject() throws Exception {
ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy( AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy(
ImmutableSet.of(Inject.class), ImmutableSet.of(new ExtractNamed())); 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); assertNotNull(constructor);
assertEquals(constructor.getParameterTypes().length, 5); assertEquals(constructor.getParameters().size(), 5);
assertEquals(strategy.translateName(constructor, 0), "aa"); assertEquals(strategy.translateName(constructor, 0), "aa");
assertEquals(strategy.translateName(constructor, 1), "bb"); assertEquals(strategy.translateName(constructor, 1), "bb");
assertEquals(strategy.translateName(constructor, 2), "cc"); assertEquals(strategy.translateName(constructor, 2), "cc");
assertEquals(strategy.translateName(constructor, 3), "dd"); 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); assertNotNull(mixedCtor);
assertEquals(mixedCtor.getParameterTypes().length, 4); assertEquals(mixedCtor.getParameters().size(), 4);
assertEquals(strategy.translateName(mixedCtor, 0), "aardvark"); assertEquals(strategy.translateName(mixedCtor, 0), "aardvark");
assertEquals(strategy.translateName(mixedCtor, 1), "bat"); 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 * Simple type with an Optional field
*/ */
static class SimpleBean { static class SimpleBean {
private Optional<String> value; private final Optional<String> value;
private final String someOtherValue; private final String someOtherValue;
public SimpleBean(Optional<String> value, 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! // Required to ensure GSON doesn't initialize our Optional to null!
@SuppressWarnings("unused")
private SimpleBean() { private SimpleBean() {
this.value = Optional.absent(); this.value = Optional.absent();
this.someOtherValue = null; this.someOtherValue = null;
@ -64,22 +65,9 @@ public class OptionalTypeAdapterFactoryTest {
return someOtherValue; return someOtherValue;
} }
@Override public boolean equals(Object other) {
public int hashCode() { SimpleBean that = SimpleBean.class.cast(other);
return Objects.hashCode(value, someOtherValue); return Objects.equal(value, that.value) && Objects.equal(someOtherValue, that.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();
} }
} }

View File

@ -19,12 +19,15 @@
package org.jclouds.reflect; package org.jclouds.reflect;
import static com.google.common.base.Functions.toStringFunction; 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.method;
import static org.jclouds.reflect.Reflection2.methods; import static org.jclouds.reflect.Reflection2.methods;
import static org.jclouds.reflect.Reflection2.typeToken; import static org.jclouds.reflect.Reflection2.typeToken;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.SortedSet;
import org.testng.annotations.Test; 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.FluentIterable;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.Invokable; import com.google.common.reflect.Invokable;
import com.google.common.reflect.Parameter;
import com.google.common.reflect.TypeToken; import com.google.common.reflect.TypeToken;
import com.google.inject.TypeLiteral;
/** /**
* *
@ -41,7 +46,31 @@ import com.google.common.reflect.TypeToken;
@Test @Test
public class Reflection2Test { 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)); assertEquals(typeToken(String.class), TypeToken.of(String.class));
} }
@ -65,15 +94,28 @@ public class Reflection2Test {
assertEquals(methodInSuper.getParameters().get(0).getType().getRawType(), Object.class); 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() { public void testMethods() {
Set<String> methodNames = FluentIterable.from(methods(Set.class)) Set<String> methodNames = FluentIterable.from(methods(Set.class)).transform(invokableToName)
.transform(new Function<Invokable<?, ?>, String>() { .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) { public String apply(Invokable<?, ?> input) {
return input.getName(); 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.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.concat;
import static com.google.common.collect.Iterables.transform; 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.net.URI;
import java.util.Collection; import java.util.Collection;
import java.util.List; 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.base.Function;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.reflect.Invokable;
/** /**
* This class is used to decorate transport objects with high level * This class is used to decorate transport objects with high level
@ -103,13 +105,12 @@ public abstract class DomainWrapper<T extends SingleResourceTransportDto> {
} }
try { try {
Constructor<W> cons = wrapperClass.getDeclaredConstructor(RestContext.class, target.getClass()); Invokable<W, W> cons = constructor(wrapperClass, RestContext.class, target.getClass());
if (!cons.isAccessible()) { return cons.invoke(null, context, target);
cons.setAccessible(true); } catch (InvocationTargetException e) {
} throw new WrapperException(wrapperClass, target, e.getTargetException());
return cons.newInstance(context, target); } catch (IllegalAccessException e) {
} catch (Exception ex) { throw new WrapperException(wrapperClass, target, e);
throw new WrapperException(wrapperClass, target, ex);
} }
} }

View File

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

View File

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

View File

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