diff --git a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackParserModule.java b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackParserModule.java index 086529fc30..a8126db3af 100644 --- a/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackParserModule.java +++ b/apis/cloudstack/src/main/java/org/jclouds/cloudstack/config/CloudStackParserModule.java @@ -26,12 +26,10 @@ import javax.inject.Inject; import org.jclouds.date.DateService; import org.jclouds.json.config.GsonModule.DateAdapter; import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; -import org.jclouds.json.internal.IgnoreNullIterableTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.IterableTypeAdapterFactory; import com.google.common.base.Splitter; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableSet; import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; @@ -47,15 +45,14 @@ public class CloudStackParserModule extends AbstractModule { @Override protected void configure() { bind(DateAdapter.class).to(CloudStackDateAdapter.class); - bind(IgnoreNullIterableTypeAdapterFactory.class).to(CommaDelimitedOKIgnoreNullIterableTypeAdapterFactory.class); + bind(IterableTypeAdapterFactory.class).to(CommaDelimitedOKIterableTypeAdapterFactory.class); } /** * Data adapter for the date formats used by CloudStack. * - * Essentially this is a workaround for the CloudStack getUsage() API call returning a corrupted - * form of ISO-8601 dates, which have an unexpected pair of apostrophes, like - * 2011-12-12'T'00:00:00+00:00 + * Essentially this is a workaround for the CloudStack getUsage() API call returning a corrupted form of ISO-8601 + * dates, which have an unexpected pair of apostrophes, like 2011-12-12'T'00:00:00+00:00 * * @author Richard Downer */ @@ -77,39 +74,38 @@ public class CloudStackParserModule extends AbstractModule { * * @author Adrian Cole */ - public static class CommaDelimitedOKIgnoreNullIterableTypeAdapterFactory extends IgnoreNullIterableTypeAdapterFactory { + public static class CommaDelimitedOKIterableTypeAdapterFactory extends IterableTypeAdapterFactory { @Override - protected TypeAdapter> newIterableAdapter(final TypeAdapter elementAdapter) { - return new TypeAdapter>() { - public void write(JsonWriter out, Iterable value) throws IOException { - out.beginArray(); - for (E element : value) { - elementAdapter.write(out, element); - } - out.endArray(); - } + @SuppressWarnings("unchecked") + protected TypeAdapter newAdapter(TypeAdapter elementAdapter) { + return (TypeAdapter) new Adapter(elementAdapter); + } - @SuppressWarnings("unchecked") - public Iterable read(JsonReader in) throws IOException { - // HACK as cloudstack changed a field from String to Set! - if (in.peek() == JsonToken.STRING) { - String val = Strings.emptyToNull(in.nextString()); - return (Iterable) (val != null ? Splitter.on(',').split(val) : ImmutableSet.of()); - } else { - Builder builder = ImmutableList. builder(); - in.beginArray(); - while (in.hasNext()) { - E element = elementAdapter.read(in); - if (element != null) - builder.add(element); - } - in.endArray(); - return builder.build(); - } + public static final class Adapter extends TypeAdapter> { + + private final IterableTypeAdapterFactory.IterableTypeAdapter delegate; + + public Adapter(TypeAdapter elementAdapter) { + this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter(elementAdapter); + nullSafe(); + } + + public void write(JsonWriter out, Iterable value) throws IOException { + this.delegate.write(out, value); + } + + @SuppressWarnings("unchecked") + @Override + public Iterable read(JsonReader in) throws IOException { + // HACK as cloudstack changed a field from String to Set! + if (in.peek() == JsonToken.STRING) { + String val = Strings.emptyToNull(in.nextString()); + return (Iterable) (val != null ? Splitter.on(',').split(val) : ImmutableSet.of()); + } else { + return delegate.read(in); } - }.nullSafe(); + } } } - } diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneParserModule.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneParserModule.java index 55b2508759..70e6616b02 100644 --- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneParserModule.java +++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/config/KeystoneParserModule.java @@ -21,37 +21,30 @@ package org.jclouds.openstack.keystone.v2_0.config; import static com.google.common.base.Preconditions.checkState; import java.io.IOException; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.Set; import org.jclouds.json.config.GsonModule; import org.jclouds.json.config.GsonModule.DateAdapter; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.SetTypeAdapterFactory; import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import com.google.gson.Gson; +import com.google.common.collect.ImmutableSet.Builder; import com.google.gson.TypeAdapter; -import com.google.gson.TypeAdapterFactory; -import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import com.google.inject.AbstractModule; -import com.google.inject.TypeLiteral; /** * @author Adam Lowe */ public class KeystoneParserModule extends AbstractModule { - @Override protected void configure() { bind(DateAdapter.class).to(GsonModule.Iso8601DateAdapter.class); - bind(new TypeLiteral>() { - }).toInstance(ImmutableSet.of(new SetTypeAdapterFactory())); + bind(SetTypeAdapterFactory.class).to(ValuesSetTypeAdapterFactory.class); } /** @@ -60,61 +53,49 @@ public class KeystoneParserModule extends AbstractModule { *

* Treats [A,B,C] and {"values"=[A,B,C], "someotherstuff"=...} as the same Set */ - public static class SetTypeAdapterFactory implements TypeAdapterFactory { - @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken typeToken) { - Type type = typeToken.getType(); - if (typeToken.getRawType() != Set.class || !(type instanceof ParameterizedType)) { - return null; - } + public static class ValuesSetTypeAdapterFactory extends SetTypeAdapterFactory { - Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; - TypeAdapter elementAdapter = gson.getAdapter(TypeToken.get(elementType)); - return TypeAdapter.class.cast(newSetAdapter(elementAdapter)); + @Override + @SuppressWarnings("unchecked") + protected TypeAdapter newAdapter(TypeAdapter elementAdapter) { + return (TypeAdapter) new Adapter(elementAdapter); } - private TypeAdapter> newSetAdapter(final TypeAdapter elementAdapter) { - return new TypeAdapter>() { - public void write(JsonWriter out, Set value) throws IOException { - out.beginArray(); - for (E element : value) { - elementAdapter.write(out, element); - } - out.endArray(); - } + public static final class Adapter extends TypeAdapter> { - public Set read(JsonReader in) throws IOException { - Set result = Sets.newLinkedHashSet(); - if (in.peek() == JsonToken.BEGIN_OBJECT) { - boolean foundValues = false; - in.beginObject(); - while (in.hasNext()) { - String name = in.nextName(); - if (Objects.equal("values", name)) { - foundValues = true; - readArray(in, result); - } else { - in.skipValue(); - } - } - checkState(foundValues, "Expected BEGIN_ARRAY or the object to contain an array called 'values'"); - in.endObject(); - } else { - readArray(in, result); - } + private final SetTypeAdapterFactory.SetTypeAdapter delegate; - return result; - } + public Adapter(TypeAdapter elementAdapter) { + this.delegate = new SetTypeAdapterFactory.SetTypeAdapter(elementAdapter); + nullSafe(); + } - private void readArray(JsonReader in, Set result) throws IOException { - in.beginArray(); + public void write(JsonWriter out, Set value) throws IOException { + this.delegate.write(out, value); + } + + @Override + public Set read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.BEGIN_OBJECT) { + Builder builder = ImmutableSet.builder(); + boolean foundValues = false; + in.beginObject(); while (in.hasNext()) { - E element = elementAdapter.read(in); - result.add(element); + String name = in.nextName(); + if (Objects.equal("values", name)) { + foundValues = true; + builder.addAll(delegate.read(in)); + } else { + in.skipValue(); + } } - in.endArray(); + checkState(foundValues, "Expected BEGIN_ARRAY or the object to contain an array called 'values'"); + in.endObject(); + return builder.build(); + } else { + return delegate.read(in); } - }.nullSafe(); + } } } } diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/PaginatedCollection.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/PaginatedCollection.java index e53ce36e2e..54f14bda80 100644 --- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/PaginatedCollection.java +++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/domain/PaginatedCollection.java @@ -44,10 +44,6 @@ import com.google.common.collect.Iterables; */ @Beta public class PaginatedCollection extends IterableWithMarker { - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public static final PaginatedCollection EMPTY = new PaginatedCollection(ImmutableSet.of(), ImmutableSet.of()); - private Iterable resources; private Iterable links; diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseTenants.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseTenants.java index bd1f867961..875894bc8a 100644 --- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseTenants.java +++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseTenants.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseTenants extends ParseJson> { - static class Tenants extends PaginatedCollection { +public class ParseTenants extends ParseJson { + static class Tenants extends PaginatedCollection { @ConstructorProperties({ "tenants", "tenants_links" }) - protected Tenants(Iterable tenants, Iterable tenants_links) { + protected Tenants(Iterable tenants, Iterable tenants_links) { super(tenants, tenants_links); } @@ -60,8 +60,7 @@ public class ParseTenants extends ParseJson> { @Inject public ParseTenants(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Tenants.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsers.java b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsers.java index ad4dfb3041..c4ad3193b3 100644 --- a/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsers.java +++ b/apis/openstack-keystone/src/main/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsers.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseUsers extends ParseJson> { - static class Users extends PaginatedCollection { +public class ParseUsers extends ParseJson { + static class Users extends PaginatedCollection { @ConstructorProperties({ "users", "users_links" }) - protected Users(Iterable users, Iterable users_links) { + protected Users(Iterable users, Iterable users_links) { super(users, users_links); } @@ -60,8 +60,7 @@ public class ParseUsers extends ParseJson> { @Inject public ParseUsers(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Users.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java new file mode 100644 index 0000000000..73464402f0 --- /dev/null +++ b/apis/openstack-keystone/src/test/java/org/jclouds/openstack/keystone/v2_0/functions/internal/ParseUsersTest.java @@ -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>>() { + private static final long serialVersionUID = 1L; + }.getType(); + + Set 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> 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); + } +} diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavorDetails.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavorDetails.java index 45354e057b..6e4071da39 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavorDetails.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavorDetails.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseFlavorDetails extends ParseJson> { - static class Flavors extends PaginatedCollection { +public class ParseFlavorDetails extends ParseJson { + static class Flavors extends PaginatedCollection { @ConstructorProperties({ "flavors", "flavors_links" }) - protected Flavors(Iterable flavors, Iterable flavors_links) { + protected Flavors(Iterable flavors, Iterable flavors_links) { super(flavors, flavors_links); } @@ -60,8 +60,7 @@ public class ParseFlavorDetails extends ParseJson> { @Inject public ParseFlavorDetails(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Flavors.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavors.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavors.java index fd28914d88..ccdcffa0bb 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavors.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseFlavors.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseFlavors extends ParseJson> { - static class Flavors extends PaginatedCollection { +public class ParseFlavors extends ParseJson { + static class Flavors extends PaginatedCollection { @ConstructorProperties({ "flavors", "flavors_links" }) - protected Flavors(Iterable flavors, Iterable flavors_links) { + protected Flavors(Iterable flavors, Iterable flavors_links) { super(flavors, flavors_links); } @@ -60,8 +60,7 @@ public class ParseFlavors extends ParseJson> { @Inject public ParseFlavors(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Flavors.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImageDetails.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImageDetails.java index 225e9f15e7..0ca231b2f3 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImageDetails.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImageDetails.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseImageDetails extends ParseJson> { - static class Images extends PaginatedCollection { +public class ParseImageDetails extends ParseJson { + static class Images extends PaginatedCollection { @ConstructorProperties({ "images", "images_links" }) - protected Images(Iterable images, Iterable images_links) { + protected Images(Iterable images, Iterable images_links) { super(images, images_links); } @@ -60,8 +60,7 @@ public class ParseImageDetails extends ParseJson> { @Inject public ParseImageDetails(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Images.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImages.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImages.java index 51680f72c4..793a193eeb 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImages.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseImages.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseImages extends ParseJson> { - static class Images extends PaginatedCollection { +public class ParseImages extends ParseJson { + static class Images extends PaginatedCollection { @ConstructorProperties({ "images", "images_links" }) - protected Images(Iterable images, Iterable images_links) { + protected Images(Iterable images, Iterable images_links) { super(images, images_links); } @@ -60,8 +60,7 @@ public class ParseImages extends ParseJson> { @Inject public ParseImages(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Images.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServerDetails.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServerDetails.java index 588d99d4e3..4dafdbaf00 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServerDetails.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServerDetails.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseServerDetails extends ParseJson> { - static class Servers extends PaginatedCollection { +public class ParseServerDetails extends ParseJson { + static class Servers extends PaginatedCollection { @ConstructorProperties({ "servers", "servers_links" }) - protected Servers(Iterable servers, Iterable servers_links) { + protected Servers(Iterable servers, Iterable servers_links) { super(servers, servers_links); } @@ -60,8 +60,7 @@ public class ParseServerDetails extends ParseJson> { @Inject public ParseServerDetails(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Servers.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServers.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServers.java index fb8d05d69f..a1bf7ceeb2 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServers.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/functions/internal/ParseServers.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseServers extends ParseJson> { - static class Servers extends PaginatedCollection { +public class ParseServers extends ParseJson { + static class Servers extends PaginatedCollection { @ConstructorProperties({ "servers", "servers_links" }) - protected Servers(Iterable servers, Iterable servers_links) { + protected Servers(Iterable servers, Iterable servers_links) { super(servers, servers_links); } @@ -60,8 +60,7 @@ public class ParseServers extends ParseJson> { @Inject public ParseServers(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Servers.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java b/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java index b5e71c5aba..9154777968 100644 --- a/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java +++ b/core/src/main/java/org/jclouds/internal/FilterStringsBoundToInjectorByName.java @@ -53,8 +53,7 @@ public class FilterStringsBoundToInjectorByName implements Function apply(Predicate filter) { - List> stringBindings = injector.findBindingsByType(new TypeLiteral() { - }); + List> stringBindings = injector.findBindingsByType(TypeLiteral.get(String.class)); Iterable> annotatedWithName = Iterables.filter(stringBindings, new Predicate>() { @Override diff --git a/core/src/main/java/org/jclouds/json/config/GsonModule.java b/core/src/main/java/org/jclouds/json/config/GsonModule.java index 63b7a865ac..608a0e0a49 100644 --- a/core/src/main/java/org/jclouds/json/config/GsonModule.java +++ b/core/src/main/java/org/jclouds/json/config/GsonModule.java @@ -40,15 +40,16 @@ import org.jclouds.json.Json; import org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactory; import org.jclouds.json.internal.EnumTypeAdapterThatReturnsFromValue; import org.jclouds.json.internal.GsonWrapper; -import org.jclouds.json.internal.IgnoreNullFluentIterableTypeAdapterFactory; -import org.jclouds.json.internal.IgnoreNullIterableTypeAdapterFactory; -import org.jclouds.json.internal.IgnoreNullMapTypeAdapterFactory; -import org.jclouds.json.internal.IgnoreNullMultimapTypeAdapterFactory; -import org.jclouds.json.internal.IgnoreNullSetTypeAdapterFactory; import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy; import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy; import org.jclouds.json.internal.NamingStrategies.ExtractNamed; import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.CollectionTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.FluentIterableTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.IterableTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MapTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MultimapTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.SetTypeAdapterFactory; import org.jclouds.json.internal.NullHackJsonLiteralAdapter; import org.jclouds.json.internal.OptionalTypeAdapterFactory; @@ -84,14 +85,13 @@ public class GsonModule extends AbstractModule { @Provides @Singleton Gson provideGson(TypeAdapter jsonAdapter, DateAdapter adapter, ByteListAdapter byteListAdapter, - ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings, - OptionalTypeAdapterFactory optional, IgnoreNullSetTypeAdapterFactory set, - IgnoreNullMapTypeAdapterFactory map, IgnoreNullMultimapTypeAdapterFactory multimap, - IgnoreNullIterableTypeAdapterFactory iterable, IgnoreNullFluentIterableTypeAdapterFactory fluentIterable) - throws Exception { + ByteArrayAdapter byteArrayAdapter, PropertiesAdapter propertiesAdapter, JsonAdapterBindings bindings, + OptionalTypeAdapterFactory optional, SetTypeAdapterFactory set, MapTypeAdapterFactory map, + MultimapTypeAdapterFactory multimap, IterableTypeAdapterFactory iterable, + CollectionTypeAdapterFactory collection, FluentIterableTypeAdapterFactory fluentIterable) throws Exception { - FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(new ExtractSerializedName(), - new ExtractNamed()); + FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of( + new ExtractSerializedName(), new ExtractNamed())); GsonBuilder builder = new GsonBuilder().setFieldNamingStrategy(serializationPolicy); @@ -104,18 +104,17 @@ public class GsonModule extends AbstractModule { builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe()); builder.registerTypeAdapterFactory(optional); builder.registerTypeAdapterFactory(iterable); + builder.registerTypeAdapterFactory(collection); builder.registerTypeAdapterFactory(set); builder.registerTypeAdapterFactory(map); builder.registerTypeAdapterFactory(multimap); builder.registerTypeAdapterFactory(fluentIterable); - AnnotationConstructorNamingStrategy deserializationPolicy = - new AnnotationConstructorNamingStrategy( - ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed())); + AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy( + ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed())); - builder.registerTypeAdapterFactory( - new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(), - serializationPolicy, Excluder.DEFAULT, deserializationPolicy)); + builder.registerTypeAdapterFactory(new DeserializationConstructorAndReflectiveTypeAdapterFactory( + new ConstructorConstructor(), serializationPolicy, Excluder.DEFAULT, deserializationPolicy)); // complicated (serializers/deserializers as they need context to operate) builder.registerTypeHierarchyAdapter(Enum.class, new EnumTypeAdapterThatReturnsFromValue()); diff --git a/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java index 157de5dd69..295c6ba94b 100644 --- a/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java +++ b/core/src/main/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactory.java @@ -20,22 +20,25 @@ package org.jclouds.json.internal; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import static org.jclouds.reflect.Reflection2.typeToken; import java.io.IOException; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Type; +import java.util.List; import java.util.Map; -import org.jclouds.json.internal.NamingStrategies.ConstructorFieldNamingStrategy; +import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy; -import com.google.common.collect.Maps; +import com.google.common.base.Objects; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.reflect.Invokable; +import com.google.common.reflect.Parameter; import com.google.gson.FieldNamingStrategy; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.$Gson$Types; import com.google.gson.internal.ConstructorConstructor; import com.google.gson.internal.Excluder; import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; @@ -49,21 +52,20 @@ import com.google.gson.stream.JsonWriter; *

*

    *
  • Deserialization
  • - * If there's an annotation designating a parameterized constructor, invoke that for fields - * correlating to named parameter annotations. Otherwise, use {@link ConstructorConstructor}, and - * set fields via reflection. + * If there's an annotation designating a parameterized constructor, invoke that for fields correlating to named + * parameter annotations. Otherwise, use {@link ConstructorConstructor}, and set fields via reflection. *

    - * Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and - * the empty object ({}) is treated as a null if the constructor for the object throws an NPE. - *

  • Serialization
  • - * Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor. + * Notes: primitive constructor params are set to the Java defaults (0 or false) if not present; and the empty object + * ({}) is treated as a null if the constructor for the object throws an NPE. + *
  • Serialization
  • Serialize based on reflective access to fields, delegating to ReflectiveTypeAdaptor. *
*

Example: Using javax inject to select a constructor and corresponding named parameters

*

+ * *

- *    
+ * 
  * import NamingStrategies.*;
- *
+ * 
  * serializationStrategy = new AnnotationOrNameFieldNamingStrategy(
  *    new ExtractSerializedName(), new ExtractNamed());
  * 
@@ -73,19 +75,20 @@ import com.google.gson.stream.JsonWriter;
  *    
  * factory = new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(),
  *      serializationStrategy, Excluder.DEFAULT, deserializationStrategy);
- *
+ * 
  * gson = new GsonBuilder(serializationStrategy).registerTypeAdapterFactory(factory).create();
- *
+ * 
  * 
*

* The above would work fine on the following class, which has no gson-specific annotations: *

+ * *

  * private static class ImmutableAndVerifiedInCtor {
  *    final int foo;
  *    @Named("_bar")
  *    final int bar;
- *
+ * 
  *    @Inject
  *    ImmutableAndVerifiedInCtor(@Named("foo") int foo, @Named("_bar") int bar) {
  *       if (foo < 0)
@@ -97,54 +100,47 @@ import com.google.gson.stream.JsonWriter;
  * 
*

*
- * + * * @author Adrian Cole * @author Adam Lowe */ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory implements TypeAdapterFactory { - private final ConstructorFieldNamingStrategy constructorFieldNamingPolicy; + private final AnnotationConstructorNamingStrategy constructorFieldNamingPolicy; private final ReflectiveTypeAdapterFactory delegateFactory; /** - * @param constructorConstructor passed through to delegate ReflectiveTypeAdapterFactory for serialization - * @param serializationFieldNamingPolicy passed through to delegate ReflectiveTypeAdapterFactory for serialization - * @param excluder passed through to delegate ReflectiveTypeAdapterFactory for serialization - * @param deserializationFieldNamingPolicy - * determines which constructor to use and how to determine field names for - * deserialization * @see ReflectiveTypeAdapterFactory */ - public DeserializationConstructorAndReflectiveTypeAdapterFactory( - ConstructorConstructor constructorConstructor, - FieldNamingStrategy serializationFieldNamingPolicy, - Excluder excluder, - ConstructorFieldNamingStrategy deserializationFieldNamingPolicy) { - this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy, "deserializationFieldNamingPolicy"); - this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull(serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder")); + public DeserializationConstructorAndReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, + FieldNamingStrategy serializationFieldNamingPolicy, Excluder excluder, + AnnotationConstructorNamingStrategy deserializationFieldNamingPolicy) { + this.constructorFieldNamingPolicy = checkNotNull(deserializationFieldNamingPolicy, + "deserializationFieldNamingPolicy"); + this.delegateFactory = new ReflectiveTypeAdapterFactory(constructorConstructor, checkNotNull( + serializationFieldNamingPolicy, "fieldNamingPolicy"), checkNotNull(excluder, "excluder")); } - public TypeAdapter create(Gson gson, final TypeToken type) { - Class raw = type.getRawType(); - Constructor deserializationCtor = constructorFieldNamingPolicy.getDeserializationConstructor(raw); + public TypeAdapter create(Gson gson, TypeToken type) { + com.google.common.reflect.TypeToken token = typeToken(type.getType()); + Invokable deserializationCtor = constructorFieldNamingPolicy.getDeserializer(token); if (deserializationCtor == null) { return null; // allow GSON to choose the correct Adapter (can't simply return delegateFactory.create()) } else { - deserializationCtor.setAccessible(true); - return new DeserializeWithParameterizedConstructorSerializeWithDelegate(delegateFactory.create(gson, type), deserializationCtor, - getParameterReaders(gson, type, deserializationCtor)); + return new DeserializeIntoParameterizedConstructor(delegateFactory.create(gson, type), deserializationCtor, + getParameterReaders(gson, deserializationCtor)); } } - private final class DeserializeWithParameterizedConstructorSerializeWithDelegate extends TypeAdapter { - private final Constructor parameterizedCtor; + private final class DeserializeIntoParameterizedConstructor extends TypeAdapter { + private final TypeAdapter serializer; + private final Invokable parameterizedCtor; private final Map> parameterReaders; - private final TypeAdapter delegate; - private DeserializeWithParameterizedConstructorSerializeWithDelegate(TypeAdapter delegate, - Constructor parameterizedCtor, Map> parameterReaders) { - this.delegate = delegate; - this.parameterizedCtor = parameterizedCtor; + private DeserializeIntoParameterizedConstructor(TypeAdapter serializer, Invokable deserializationCtor, + Map> parameterReaders) { + this.serializer = serializer; + this.parameterizedCtor = deserializationCtor; this.parameterReaders = parameterReaders; } @@ -155,16 +151,16 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp return null; } - Class[] paramTypes = parameterizedCtor.getParameterTypes(); - Object[] ctorParams = new Object[paramTypes.length]; + List params = parameterizedCtor.getParameters(); + Object[] values = new Object[params.size()]; boolean empty = true; // Set all primitive constructor params to defaults - for (int i = 0; i < paramTypes.length; i++) { - if (paramTypes[i] == boolean.class) { - ctorParams[i] = Boolean.FALSE; - } else if (paramTypes[i].isPrimitive()) { - ctorParams[i] = 0; + for (Parameter param : params) { + if (param.getType().getRawType() == boolean.class) { + values[param.hashCode()] = Boolean.FALSE; + } else if (param.getType().getRawType().isPrimitive()) { + values[param.hashCode()] = 0; } } @@ -178,26 +174,27 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp in.skipValue(); } else { Object value = parameter.read(in); - if (value != null) ctorParams[parameter.index] = value; + if (value != null) + values[parameter.position] = value; } } } catch (IllegalStateException e) { throw new JsonSyntaxException(e); } - for (int i = 0; i < paramTypes.length; i++) { - if (paramTypes[i].isPrimitive()) { - checkArgument(ctorParams[i] != null, "Primitive param[" + i + "] in constructor " + parameterizedCtor - + " cannot be absent!"); + for (Parameter param : params) { + if (param.getType().getRawType().isPrimitive()) { + checkArgument(values[param.hashCode()] != null, "Primitive param[" + param.hashCode() + + "] in constructor " + parameterizedCtor + " cannot be absent!"); } } in.endObject(); try { - return newInstance(ctorParams); + return newInstance(values); } catch (NullPointerException ex) { // If {} was found and constructor threw NPE, we treat the field as null - if (empty && paramTypes.length > 0) { + if (empty && values.length > 0) { return null; } throw ex; @@ -209,15 +206,12 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp */ @Override public void write(JsonWriter out, T value) throws IOException { - delegate.write(out, value); + serializer.write(out, value); } - @SuppressWarnings("unchecked") private T newInstance(Object[] ctorParams) throws AssertionError { try { - return (T) parameterizedCtor.newInstance(ctorParams); - } catch (InstantiationException e) { - throw new AssertionError(e); + return (T) parameterizedCtor.invoke(null, ctorParams); } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { @@ -226,43 +220,79 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactory imp throw new AssertionError(e); } } + + @Override + public int hashCode() { + return Objects.hashCode(serializer, parameterizedCtor, parameterReaders); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + DeserializeIntoParameterizedConstructor that = DeserializeIntoParameterizedConstructor.class.cast(obj); + return Objects.equal(this.serializer, that.serializer) + && Objects.equal(this.parameterizedCtor, that.parameterizedCtor) + && Objects.equal(this.parameterReaders, that.parameterReaders); + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("parameterizedCtor", parameterizedCtor) + .add("parameterReaders", parameterReaders).add("serializer", serializer).toString(); + } + } // logic borrowed from ReflectiveTypeAdapterFactory static class ParameterReader { final String name; - final int index; + final int position; final TypeAdapter typeAdapter; - ParameterReader(String name, int index, TypeAdapter typeAdapter) { + ParameterReader(int position, String name, TypeAdapter typeAdapter) { this.name = name; - this.index = index; + this.position = position; this.typeAdapter = typeAdapter; } public Object read(JsonReader reader) throws IOException { return typeAdapter.read(reader); } - } - private Map> getParameterReaders(Gson context, TypeToken declaring, Constructor constructor) { - Map> result = Maps.newLinkedHashMap(); - - for (int index = 0; index < constructor.getGenericParameterTypes().length; index++) { - Type parameterType = getTypeOfConstructorParameter(declaring, constructor, index); - TypeAdapter adapter = context.getAdapter(TypeToken.get(parameterType)); - String parameterName = constructorFieldNamingPolicy.translateName(constructor, index); - checkArgument(parameterName != null, constructor + " parameter " + 0 + " failed to be named by " + constructorFieldNamingPolicy); - @SuppressWarnings({ "rawtypes", "unchecked" }) - ParameterReader parameterReader = new ParameterReader(parameterName, index, adapter); - ParameterReader previous = result.put(parameterReader.name, parameterReader); - checkArgument(previous == null, constructor + " declares multiple JSON parameters named " + parameterReader.name); + @Override + public boolean equals(Object obj) { + if (obj instanceof ParameterReader) { + ParameterReader that = ParameterReader.class.cast(obj); + return position == that.position && name.equals(that.name); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hashCode(position, name); + } + + @Override + public String toString() { + return typeAdapter + " arg" + position; } - return result; } - private Type getTypeOfConstructorParameter(TypeToken declaring, Constructor constructor, int index) { - Type genericParameter = constructor.getGenericParameterTypes()[index]; - return $Gson$Types.resolve(declaring.getType(), declaring.getRawType(), genericParameter); + private Map> getParameterReaders(Gson context, Invokable deserializationCtor) { + Builder> result = ImmutableMap.builder(); + for (Parameter param : deserializationCtor.getParameters()) { + TypeAdapter adapter = context.getAdapter(TypeToken.get(param.getType().getType())); + String parameterName = constructorFieldNamingPolicy.translateName(deserializationCtor, param.hashCode()); + checkArgument(parameterName != null, deserializationCtor + " parameter " + 0 + " failed to be named by " + + constructorFieldNamingPolicy); + @SuppressWarnings({ "rawtypes", "unchecked" }) + ParameterReader parameterReader = new ParameterReader(param.hashCode(), parameterName, adapter); + result.put(parameterReader.name, parameterReader); + } + return result.build(); } } diff --git a/core/src/main/java/org/jclouds/json/internal/EnumTypeAdapterThatReturnsFromValue.java b/core/src/main/java/org/jclouds/json/internal/EnumTypeAdapterThatReturnsFromValue.java index 75282228cc..08bef93053 100644 --- a/core/src/main/java/org/jclouds/json/internal/EnumTypeAdapterThatReturnsFromValue.java +++ b/core/src/main/java/org/jclouds/json/internal/EnumTypeAdapterThatReturnsFromValue.java @@ -18,13 +18,11 @@ */ package org.jclouds.json.internal; -import java.lang.reflect.Method; -import java.lang.reflect.Type; -import java.util.concurrent.ExecutionException; +import static org.jclouds.reflect.Reflection2.method; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; +import java.lang.reflect.Type; + +import com.google.common.reflect.Invokable; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; @@ -48,32 +46,11 @@ public class EnumTypeAdapterThatReturnsFromValue> implements J return (T) Enum.valueOf((Class) classOfT, json.getAsString()); } catch (IllegalArgumentException e) { try { - Method converter = classToConvert.get((Class) classOfT); + Invokable converter = method((Class) classOfT, "fromValue", String.class); return (T) converter.invoke(null, json.getAsString()); } catch (Exception e1) { throw e; } } } - - private static final LoadingCache, Method> classToConvert = CacheBuilder.newBuilder() - .build(new CacheLoader, 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(); - } } diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullFluentIterableTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullFluentIterableTypeAdapterFactory.java deleted file mode 100644 index 42d31e28e1..0000000000 --- a/core/src/main/java/org/jclouds/json/internal/IgnoreNullFluentIterableTypeAdapterFactory.java +++ /dev/null @@ -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 - *

- * Treats [null] as the empty set; [A, null] as [A]; etc. - * - * @author Adam Lowe - */ -public class IgnoreNullFluentIterableTypeAdapterFactory implements TypeAdapterFactory { - - @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken 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) newFluentIterableAdapter(elementAdapter); - } - - protected TypeAdapter> newFluentIterableAdapter(final TypeAdapter elementAdapter) { - return new TypeAdapter>() { - public void write(JsonWriter out, FluentIterable value) throws IOException { - out.beginArray(); - for (E element : value) { - elementAdapter.write(out, element); - } - out.endArray(); - } - - public FluentIterable read(JsonReader in) throws IOException { - in.beginArray(); - Builder builder = ImmutableList.builder(); - while (in.hasNext()) { - E element = elementAdapter.read(in); - if (element != null) builder.add(element); - } - in.endArray(); - return FluentIterable.from(builder.build()); - } - }.nullSafe(); - } -} diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullIterableTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullIterableTypeAdapterFactory.java deleted file mode 100644 index f4c88fbadb..0000000000 --- a/core/src/main/java/org/jclouds/json/internal/IgnoreNullIterableTypeAdapterFactory.java +++ /dev/null @@ -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 - *

- * Treats [null] as the empty set; [A, null] as [A]; etc. - * - * @author Adam Lowe - */ -public class IgnoreNullIterableTypeAdapterFactory implements TypeAdapterFactory { - - @SuppressWarnings("unchecked") - public TypeAdapter create(Gson gson, TypeToken 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) newIterableAdapter(elementAdapter); - } - - protected TypeAdapter> newIterableAdapter(final TypeAdapter elementAdapter) { - return new TypeAdapter>() { - public void write(JsonWriter out, Iterable value) throws IOException { - out.beginArray(); - for (E element : value) { - elementAdapter.write(out, element); - } - out.endArray(); - } - - public Iterable read(JsonReader in) throws IOException { - in.beginArray(); - Builder builder = ImmutableList.builder(); - while (in.hasNext()) { - E element = elementAdapter.read(in); - if (element != null) builder.add(element); - } - in.endArray(); - return builder.build(); - } - }.nullSafe(); - } -} diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java deleted file mode 100644 index c508c9c6f2..0000000000 --- a/core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java +++ /dev/null @@ -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 - *

- * 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 TypeAdapter create(Gson gson, TypeToken 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) newMapAdapter(keyAdapter, valueAdapter); - } - - protected TypeAdapter> newMapAdapter(final TypeAdapter keyAdapter, final TypeAdapter valueAdapter) { - return new TypeAdapter>() { - public void write(JsonWriter out, Map value) throws IOException { - out.beginObject(); - for (Map.Entry element : value.entrySet()) { - out.name(String.valueOf(element.getKey())); - valueAdapter.write(out, element.getValue()); - } - out.endObject(); - } - - public Map read(JsonReader in) throws IOException { - Map 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(); - } -} diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java deleted file mode 100644 index f8ec4032b1..0000000000 --- a/core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java +++ /dev/null @@ -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 TypeAdapter create(Gson gson, TypeToken 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) newMultimapAdapter(keyAdapter, valueAdapter); - } - - protected TypeAdapter> newMultimapAdapter(final TypeAdapter keyAdapter, final TypeAdapter valueAdapter) { - return new TypeAdapter>() { - public void write(JsonWriter out, Multimap 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 read(JsonReader in) throws IOException { - ImmutableMultimap.Builder 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(); - } -} diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java deleted file mode 100644 index 1ae22c367f..0000000000 --- a/core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java +++ /dev/null @@ -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. - *

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

+ * Treats [null] as the empty set; [A, null] as [A]; etc. + * + * @author Adrian Cole + */ +public class NullFilteringTypeAdapterFactories { + private NullFilteringTypeAdapterFactories() { + } + + static TypeToken resolve(TypeToken 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 TypeAdapter create(Gson gson, TypeToken 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) newAdapter(elementAdapter); + } + + @SuppressWarnings("unchecked") + protected TypeAdapter newAdapter(TypeAdapter elementAdapter) { + return (TypeAdapter) new IterableTypeAdapter(elementAdapter); + } + + public static final class IterableTypeAdapter extends TypeAdapter> { + + private final TypeAdapter elementAdapter; + + public IterableTypeAdapter(TypeAdapter elementAdapter) { + this.elementAdapter = elementAdapter; + nullSafe(); + } + + public void write(JsonWriter out, Iterable value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + out.beginArray(); + for (E element : value) + elementAdapter.write(out, element); + out.endArray(); + } + + public Iterable read(JsonReader in) throws IOException { + return readAndBuild(in, ImmutableList. builder()); + } + + @SuppressWarnings("unchecked") + protected , B extends ImmutableCollection.Builder> 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 TypeAdapter newAdapter(TypeAdapter elementAdapter) { + return (TypeAdapter) new CollectionTypeAdapter(elementAdapter); + } + + public static final class CollectionTypeAdapter extends TypeAdapter> { + + private final IterableTypeAdapterFactory.IterableTypeAdapter delegate; + + public CollectionTypeAdapter(TypeAdapter elementAdapter) { + this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter(elementAdapter); + nullSafe(); + } + + public void write(JsonWriter out, Collection value) throws IOException { + this.delegate.write(out, value); + } + + public Collection read(JsonReader in) throws IOException { + return delegate.readAndBuild(in, ImmutableList. 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 TypeAdapter newAdapter(TypeAdapter elementAdapter) { + return (TypeAdapter) new SetTypeAdapter(elementAdapter); + } + + public static final class SetTypeAdapter extends TypeAdapter> { + + private final IterableTypeAdapterFactory.IterableTypeAdapter delegate; + + public SetTypeAdapter(TypeAdapter elementAdapter) { + this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter(elementAdapter); + nullSafe(); + } + + public void write(JsonWriter out, Set value) throws IOException { + this.delegate.write(out, value); + } + + public Set read(JsonReader in) throws IOException { + return delegate.readAndBuild(in, ImmutableSet. 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 TypeAdapter newAdapter(TypeAdapter elementAdapter) { + return (TypeAdapter) new FluentIterableTypeAdapter(elementAdapter); + } + + public static final class FluentIterableTypeAdapter extends TypeAdapter> { + + private final IterableTypeAdapterFactory.IterableTypeAdapter delegate; + + public FluentIterableTypeAdapter(TypeAdapter elementAdapter) { + this.delegate = new IterableTypeAdapterFactory.IterableTypeAdapter(elementAdapter); + nullSafe(); + } + + public void write(JsonWriter out, FluentIterable value) throws IOException { + this.delegate.write(out, value.toList()); + } + + public FluentIterable 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 TypeAdapter create(Gson gson, TypeToken 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 TypeAdapter newAdapter(TypeAdapter keyAdapter, TypeAdapter valueAdapter) { + return (TypeAdapter) new MapTypeAdapter(keyAdapter, valueAdapter); + } + + public static final class MapTypeAdapter extends TypeAdapter> { + + protected final TypeAdapter keyAdapter; + protected final TypeAdapter valueAdapter; + + protected MapTypeAdapter(TypeAdapter keyAdapter, TypeAdapter valueAdapter) { + this.keyAdapter = keyAdapter; + this.valueAdapter = valueAdapter; + nullSafe(); + } + + public void write(JsonWriter out, Map value) throws IOException { + if (value == null) { + out.nullValue(); + return; + } + out.beginObject(); + for (Map.Entry element : value.entrySet()) { + out.name(String.valueOf(element.getKey())); + valueAdapter.write(out, element.getValue()); + } + out.endObject(); + } + + public Map read(JsonReader in) throws IOException { + ImmutableMap.Builder 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 TypeAdapter newAdapter(TypeAdapter keyAdapter, TypeAdapter valueAdapter) { + return (TypeAdapter) new MultimapTypeAdapter(keyAdapter, valueAdapter); + } + + public static final class MultimapTypeAdapter extends TypeAdapter> { + + private final MapTypeAdapterFactory.MapTypeAdapter> delegate; + + public MultimapTypeAdapter(TypeAdapter keyAdapter, TypeAdapter valueAdapter) { + this.delegate = new MapTypeAdapterFactory.MapTypeAdapter>(keyAdapter, + new CollectionTypeAdapterFactory.CollectionTypeAdapter(valueAdapter)); + nullSafe(); + } + + public void write(JsonWriter out, Multimap value) throws IOException { + this.delegate.write(out, value.asMap()); + } + + public Multimap read(JsonReader in) throws IOException { + ImmutableMultimap.Builder builder = ImmutableMultimap. builder(); + for (Entry> 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(); + } + } + } +} diff --git a/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java b/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java index d5eff87de7..213a59ed8a 100644 --- a/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java +++ b/core/src/main/java/org/jclouds/lifecycle/config/LifeCycleModule.java @@ -19,19 +19,18 @@ package org.jclouds.lifecycle.config; import static com.google.common.base.Throwables.propagate; -import static com.google.common.collect.Sets.newHashSet; +import static com.google.common.collect.Iterables.filter; import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; import static com.google.inject.matcher.Matchers.any; -import static java.util.Arrays.asList; import static org.jclouds.Constants.PROPERTY_IO_WORKER_THREADS; import static org.jclouds.Constants.PROPERTY_SCHEDULER_THREADS; import static org.jclouds.Constants.PROPERTY_USER_THREADS; +import static org.jclouds.reflect.Reflection2.methods; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Set; +import java.util.Collection; import java.util.concurrent.ScheduledExecutorService; import javax.annotation.PostConstruct; @@ -40,6 +39,8 @@ import javax.inject.Named; import org.jclouds.lifecycle.Closer; +import com.google.common.base.Predicate; +import com.google.common.reflect.Invokable; import com.google.common.util.concurrent.ExecutionList; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.inject.AbstractModule; @@ -51,15 +52,15 @@ import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; /** - * This associates java lifecycle annotations with guice hooks. For example, we invoke - * {@link PostConstruct} after injection, and Associate {@link PreDestroy} with a global - * {@link Closer} object. + * This associates java lifecycle annotations with guice hooks. For example, we invoke {@link PostConstruct} after + * injection, and Associate {@link PreDestroy} with a global {@link Closer} object. * - *

Important

Make sure you create your injector with {@link Stage#PRODUCTION} and execute - * the bound {@link ExecutionList} prior to using any other objects. + *

Important

Make sure you create your injector with {@link Stage#PRODUCTION} and execute the bound + * {@link ExecutionList} prior to using any other objects. * *

* Ex. + * *

  * 
  * 
@@ -89,7 +90,7 @@ public class LifeCycleModule extends AbstractModule { ioExecutor.shutdownNow(); // ScheduledExecutor is defined in an optional module if (scheduledExecutor != null) - scheduledExecutor.shutdownNow(); + scheduledExecutor.shutdownNow(); } }; @@ -103,67 +104,57 @@ public class LifeCycleModule extends AbstractModule { bind(ExecutionList.class).toInstance(list); } + private static final Predicate> isPreDestroy = new Predicate>() { + public boolean apply(Invokable in) { + return in.isAnnotationPresent(PreDestroy.class); + } + }; + + private static final Predicate> isPostConstruct = new Predicate>() { + public boolean apply(Invokable in) { + return in.isAnnotationPresent(PostConstruct.class); + } + }; + protected void bindPostInjectionInvoke(final Closer closer, final ExecutionList list) { bindListener(any(), new TypeListener() { public void hear(TypeLiteral injectableType, TypeEncounter encounter) { - Set methods = newHashSet(); - Class type = injectableType.getRawType(); - while (type != null) { - methods.addAll(asList(type.getDeclaredMethods())); - type = type.getSuperclass(); - } - for (final Method method : methods) { - invokePostConstructMethodAfterInjection(encounter, method); - associatePreDestroyWithCloser(closer, encounter, method); - } - } - - private void associatePreDestroyWithCloser(final Closer closer, TypeEncounter encounter, - final Method method) { - PreDestroy preDestroy = method.getAnnotation(PreDestroy.class); - if (preDestroy != null) { - encounter.register(new InjectionListener() { - 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 void invokePostConstructMethodAfterInjection(TypeEncounter encounter, final Method method) { - PostConstruct postConstruct = method.getAnnotation(PostConstruct.class); - if (postConstruct != null) { + Collection> methods = methods(injectableType.getRawType()); + for (final Invokable method : filter(methods, isPostConstruct)) { encounter.register(new InjectionListener() { public void afterInjection(final I injectee) { list.add(new Runnable() { public void run() { - try { - method.invoke(injectee); - } catch (InvocationTargetException ie) { - Throwable e = ie.getTargetException(); - throw propagate(e); - } catch (IllegalAccessException e) { - throw propagate(e); - } + invokeOnInjectee(method, injectee); } + }, sameThreadExecutor()); } }); } + for (final Invokable method : filter(methods, isPreDestroy)) { + encounter.register(new InjectionListener() { + public void afterInjection(final I injectee) { + closer.addToClose(new Closeable() { + public void close() throws IOException { + invokeOnInjectee(method, injectee); + } + }); + } + }); + } } + }); } + private static void invokeOnInjectee(Invokable method, I injectee) { + try { + method.invoke(injectee); + } catch (InvocationTargetException ie) { + throw propagate(ie.getTargetException()); + } catch (IllegalAccessException e) { + throw propagate(e); + } + } } diff --git a/core/src/main/java/org/jclouds/reflect/Reflection2.java b/core/src/main/java/org/jclouds/reflect/Reflection2.java index 7c603abe60..3fca72167e 100644 --- a/core/src/main/java/org/jclouds/reflect/Reflection2.java +++ b/core/src/main/java/org/jclouds/reflect/Reflection2.java @@ -19,23 +19,33 @@ package org.jclouds.reflect; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.collect.Iterables.toArray; +import static com.google.common.base.Throwables.propagate; +import static com.google.common.collect.Iterables.tryFind; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.lang.reflect.Type; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; import com.google.common.annotations.Beta; +import com.google.common.base.Function; import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; import com.google.common.reflect.Invokable; +import com.google.common.reflect.Parameter; import com.google.common.reflect.TypeToken; +import com.google.common.util.concurrent.UncheckedExecutionException; /** * Utilities that allow access to {@link Invokable}s with {@link Invokable#getOwnerType() owner types}. @@ -43,14 +53,50 @@ import com.google.common.reflect.TypeToken; * @since 1.6 */ @Beta -public final class Reflection2 { +public class Reflection2 { + + /** + * gets a {@link TypeToken} for the given type. + */ + @SuppressWarnings("unchecked") + public static TypeToken typeToken(Type in) { + return (TypeToken) get(typeTokenForType, checkNotNull(in, "class")); + } /** * gets a {@link TypeToken} for the given class. */ @SuppressWarnings("unchecked") public static TypeToken typeToken(Class in) { - return (TypeToken) typeTokenForClass.apply(checkNotNull(in, "class")); + return (TypeToken) 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 Invokable constructor(Class ownerType, Class... parameterTypes) { + return (Invokable) 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 Collection> constructors(TypeToken ownerType) { + return Collection.class.cast(get(constructorsForTypeToken, ownerType)); } /** @@ -63,7 +109,7 @@ public final class Reflection2 { */ @SuppressWarnings("unchecked") public static Invokable method(TypeToken ownerType, Method method) { - return (Invokable) methods.apply(new TypeTokenAndMethod(ownerType, method)); + return (Invokable) method(ownerType.getRawType(), method.getName(), method.getParameterTypes()); } /** @@ -79,7 +125,7 @@ public final class Reflection2 { */ @SuppressWarnings("unchecked") public static Invokable method(Class ownerType, String name, Class... parameterTypes) { - return (Invokable) methodForArgs.apply(new TypeTokenNameAndParameterTypes(typeToken(ownerType), name, + return (Invokable) get(methodForParams, new TypeTokenNameAndParameterTypes(typeToken(ownerType), name, parameterTypes)); } @@ -91,51 +137,65 @@ public final class Reflection2 { */ @SuppressWarnings("unchecked") public static Collection> methods(Class ownerType) { - return Collection.class.cast(methodsForTypeToken.apply(typeToken(ownerType)).values()); + return Collection.class.cast(get(methodsForTypeToken, typeToken(ownerType))); } - private static final LoadingCache> methods = CacheBuilder.newBuilder().build( - new CacheLoader>() { - public Invokable load(TypeTokenAndMethod key) { - return key.type.method(key.method); + /** + * this gets all declared constructors, not just public ones. makes them accessible, as well. + */ + private static LoadingCache, Set>> constructorsForTypeToken = CacheBuilder + .newBuilder().build(new CacheLoader, Set>>() { + public Set> load(TypeToken key) { + ImmutableSet.Builder> builder = ImmutableSet.> builder(); + for (Constructor ctor : key.getRawType().getDeclaredConstructors()) { + ctor.setAccessible(true); + builder.add(key.constructor(ctor)); + } + return builder.build(); } }); - private static class TypeTokenAndMethod { - - protected final TypeToken type; - protected final Method method; - - public TypeTokenAndMethod(TypeToken type, Method method) { - this.type = checkNotNull(type, "type"); - this.method = checkNotNull(method, "method"); - } - - public int hashCode() { - return Objects.hashCode(type, method); - } - - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - TypeTokenAndMethod that = TypeTokenAndMethod.class.cast(obj); - return Objects.equal(this.type, that.type) && Objects.equal(this.method, that.method); - } + protected static List> toClasses(ImmutableList params) { + return Lists.transform(params, new Function>() { + public Class apply(Parameter input) { + return input.getType().getRawType(); + } + }); } - private static final LoadingCache, TypeToken> typeTokenForClass = CacheBuilder.newBuilder().build( - new CacheLoader, TypeToken>() { - public TypeToken load(final Class key) { + private static LoadingCache> typeTokenForType = CacheBuilder.newBuilder().build( + new CacheLoader>() { + public TypeToken load(Type key) { return TypeToken.of(key); } }); + private static LoadingCache, TypeToken> typeTokenForClass = CacheBuilder.newBuilder().build( + new CacheLoader, TypeToken>() { + public TypeToken load(Class key) { + return TypeToken.of(key); + } + }); + + private static LoadingCache> constructorForParams = CacheBuilder + .newBuilder().build(new CacheLoader>() { + public Invokable load(final TypeTokenAndParameterTypes key) { + Set> constructors = get(constructorsForTypeToken, key.type); + Optional> constructor = tryFind(constructors, new Predicate>() { + public boolean apply(Invokable input) { + return Objects.equal(toClasses(input.getParameters()), key.parameterTypes); + } + }); + if (constructor.isPresent()) + return constructor.get(); + throw new IllegalArgumentException("no such constructor " + key.toString() + "in: " + constructors); + } + }); + private static class TypeTokenAndParameterTypes { - protected final TypeToken type; - protected final List> parameterTypes; + protected TypeToken type; + protected List> parameterTypes; public TypeTokenAndParameterTypes(TypeToken type, Class... parameterTypes) { this.type = checkNotNull(type, "type"); @@ -160,23 +220,25 @@ public final class Reflection2 { } } - private static final LoadingCache> methodForArgs = CacheBuilder + private static LoadingCache> methodForParams = CacheBuilder .newBuilder().build(new CacheLoader>() { public Invokable load(final TypeTokenNameAndParameterTypes key) { - try { - Method method = key.type.getRawType().getMethod(key.name, toArray(key.parameterTypes, Class.class)); - return methods.apply(new TypeTokenAndMethod(key.type, method)); - } catch (SecurityException e) { - throw new IllegalArgumentException(e.getMessage() + " getting method " + key.toString(), e); - } catch (NoSuchMethodException e) { - throw new IllegalArgumentException("no such method " + key.toString(), e); - } + Set> methods = get(methodsForTypeToken, key.type); + Optional> method = tryFind(methods, new Predicate>() { + public boolean apply(Invokable input) { + return Objects.equal(input.getName(), key.name) + && Objects.equal(toClasses(input.getParameters()), key.parameterTypes); + } + }); + if (method.isPresent()) + return method.get(); + throw new IllegalArgumentException("no such method " + key.toString() + "in: " + methods); } }); private static class TypeTokenNameAndParameterTypes extends TypeTokenAndParameterTypes { - private final String name; + private String name; public TypeTokenNameAndParameterTypes(TypeToken type, String name, Class... parameterTypes) { super(type, parameterTypes); @@ -201,14 +263,36 @@ public final class Reflection2 { } } - private static final LoadingCache, Map>> methodsForTypeToken = CacheBuilder - .newBuilder().build(new CacheLoader, Map>>() { - public Map> load(final TypeToken key) { - Builder> builder = ImmutableMap.> builder(); - for (Method method : key.getRawType().getMethods()) - builder.put(method, method(key, method)); + /** + * this gets all declared methods, not just public ones. makes them accessible. Does not include Object methods. + */ + private static LoadingCache, Set>> methodsForTypeToken = CacheBuilder + .newBuilder().build(new CacheLoader, Set>>() { + public Set> load(TypeToken key) { + ImmutableSet.Builder> builder = ImmutableSet.> builder(); + for (TypeToken token : key.getTypes()) { + Class raw = token.getRawType(); + if (raw == Object.class) + continue; + for (Method method : raw.getDeclaredMethods()) { + method.setAccessible(true); + builder.add(key.method(method)); + } + } return builder.build(); } }); + /** + * ensures that exceptions are not doubly-wrapped + */ + private static V get(LoadingCache cache, K key) { + try { + return cache.get(key); + } catch (UncheckedExecutionException e) { + throw propagate(e.getCause()); + } catch (ExecutionException e) { + throw propagate(e.getCause()); + } + } } \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/rest/config/RestModule.java b/core/src/main/java/org/jclouds/rest/config/RestModule.java index 5112d57891..3ce5c87a19 100644 --- a/core/src/main/java/org/jclouds/rest/config/RestModule.java +++ b/core/src/main/java/org/jclouds/rest/config/RestModule.java @@ -20,7 +20,6 @@ package org.jclouds.rest.config; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.propagate; import static com.google.common.collect.Iterables.toArray; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Maps.transformValues; @@ -32,11 +31,9 @@ import static org.jclouds.rest.config.BinderUtils.bindHttpApi; import static org.jclouds.util.Maps2.transformKeys; import static org.jclouds.util.Predicates2.startsWith; -import java.lang.reflect.Method; import java.net.Proxy; import java.net.URI; import java.util.Map; -import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import javax.inject.Named; @@ -47,7 +44,6 @@ import org.jclouds.http.functions.config.SaxParserModule; import org.jclouds.internal.FilterStringsBoundToInjectorByName; import org.jclouds.json.config.GsonModule; import org.jclouds.location.config.LocationModule; -import com.google.common.reflect.Invokable; import org.jclouds.proxy.ProxyForURI; import org.jclouds.rest.AuthorizationException; import org.jclouds.rest.HttpAsyncClient; @@ -55,13 +51,14 @@ import org.jclouds.rest.HttpClient; import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith; import org.jclouds.rest.internal.BlockOnFuture; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.base.Supplier; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.Invokable; import com.google.common.reflect.Parameter; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -80,8 +77,6 @@ public class RestModule extends AbstractModule { this(ImmutableMap., Class> of()); } - private static final Set objectMethods = ImmutableSet.copyOf(Object.class.getMethods()); - public RestModule(Map, Class> sync2Async) { this.sync2Async = sync2Async; } @@ -92,6 +87,11 @@ public class RestModule extends AbstractModule { @Provides @Singleton protected Cache, Invokable> seedKnownSync2AsyncInvokables() { + return seedKnownSync2AsyncInvokables(sync2Async); + } + + @VisibleForTesting + static Cache, Invokable> seedKnownSync2AsyncInvokables(Map, Class> sync2Async) { Cache, Invokable> sync2AsyncBuilder = CacheBuilder.newBuilder().build(); putInvokables(HttpClient.class, HttpAsyncClient.class, sync2AsyncBuilder); for (Class s : sync2Async.keySet()) { @@ -103,18 +103,10 @@ public class RestModule extends AbstractModule { // accessible for ClientProvider public static void putInvokables(Class sync, Class async, Cache, Invokable> cache) { for (Invokable invoked : methods(sync)) { - if (!objectMethods.contains(invoked)) { - try { - Invokable delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked)); - checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()), - "invoked %s has different typed exceptions than delegated invoked %s", invoked, delegatedMethod); - invoked.setAccessible(true); - delegatedMethod.setAccessible(true); - cache.put(invoked, delegatedMethod); - } catch (SecurityException e) { - throw propagate(e); - } - } + Invokable delegatedMethod = method(async, invoked.getName(), getParameterTypes(invoked)); + checkArgument(delegatedMethod.getExceptionTypes().equals(invoked.getExceptionTypes()), + "invoked %s has different typed exceptions than target %s", invoked, delegatedMethod); + cache.put(invoked, delegatedMethod); } } diff --git a/core/src/main/java/org/jclouds/rest/config/SetCaller.java b/core/src/main/java/org/jclouds/rest/config/SetCaller.java index 65acb46789..ebb94f0e35 100644 --- a/core/src/main/java/org/jclouds/rest/config/SetCaller.java +++ b/core/src/main/java/org/jclouds/rest/config/SetCaller.java @@ -19,14 +19,13 @@ package org.jclouds.rest.config; import static com.google.common.base.Preconditions.checkState; +import static com.google.inject.name.Names.named; import org.jclouds.reflect.Invocation; import com.google.inject.AbstractModule; import com.google.inject.Key; import com.google.inject.Provider; -import com.google.inject.TypeLiteral; -import com.google.inject.name.Names; /** * Allows the provider to supply a value set in a threadlocal. @@ -55,8 +54,7 @@ public class SetCaller { } } - private static final Key CALLER_INVOCATION = Key.get(new TypeLiteral() { - }, Names.named("caller")); + private static final Key CALLER_INVOCATION = Key.get(Invocation.class, named("caller")); class CallerInvocationProvider implements Provider { @Override diff --git a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java index 048c2a58b0..a0c983a908 100644 --- a/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java +++ b/core/src/test/java/org/jclouds/json/internal/DeserializationConstructorAndReflectiveTypeAdapterFactoryTest.java @@ -1,4 +1,5 @@ package org.jclouds.json.internal; + /** * Licensed to jclouds, Inc. (jclouds) under one or more * contributor license agreements. See the NOTICE file @@ -54,6 +55,7 @@ import com.google.gson.reflect.TypeToken; * @author Adam Lowe */ @Test(testName = "DeserializationConstructorTypeAdapterFactoryTest") +@SuppressWarnings("unused") public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest { Gson gson = new Gson(); @@ -61,13 +63,10 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory = parameterizedCtorFactory(); static DeserializationConstructorAndReflectiveTypeAdapterFactory parameterizedCtorFactory() { - FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy( - ImmutableSet.of(new ExtractSerializedName(), new ExtractNamed()) - ); - NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = - new NamingStrategies.AnnotationConstructorNamingStrategy( - ImmutableSet.of(ConstructorProperties.class, Inject.class), - ImmutableSet.of(new ExtractNamed())); + FieldNamingStrategy serializationPolicy = new AnnotationOrNameFieldNamingStrategy(ImmutableSet.of( + new ExtractSerializedName(), new ExtractNamed())); + NamingStrategies.AnnotationConstructorNamingStrategy deserializationPolicy = new NamingStrategies.AnnotationConstructorNamingStrategy( + ImmutableSet.of(ConstructorProperties.class, Inject.class), ImmutableSet.of(new ExtractNamed())); return new DeserializationConstructorAndReflectiveTypeAdapterFactory(new ConstructorConstructor(), serializationPolicy, Excluder.DEFAULT, deserializationPolicy); @@ -83,25 +82,11 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest private DefaultConstructor() { } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; - DefaultConstructor other = DefaultConstructor.class.cast(obj); - if (bar != other.bar) - return false; - if (foo != other.foo) - return false; - return true; - } - } public void testRejectsIfNoConstuctorMarked() throws IOException { - TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken.get(DefaultConstructor.class)); + TypeAdapter adapter = parameterizedCtorFactory.create(gson, + TypeToken.get(DefaultConstructor.class)); assertNull(adapter); } @@ -114,16 +99,10 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest } } + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named") public void testSerializedNameRequiredOnAllParameters() { - try { - parameterizedCtorFactory.create(gson, TypeToken - .get(WithDeserializationConstructorButWithoutSerializedName.class)); - fail(); - } catch (IllegalArgumentException actual) { - assertEquals(actual.getMessage(), - "org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactoryTest$WithDeserializationConstructorButWithoutSerializedName(int)" + - " parameter 0 failed to be named by AnnotationBasedNamingStrategy requiring one of javax.inject.Named"); - } + parameterizedCtorFactory + .create(gson, TypeToken.get(WithDeserializationConstructorButWithoutSerializedName.class)); } private static class DuplicateSerializedNames { @@ -137,15 +116,9 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest } } + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "duplicate key: foo") public void testNoDuplicateSerializedNamesRequiredOnAllParameters() { - try { - parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class)); - fail(); - } catch (IllegalArgumentException actual) { - assertEquals(actual.getMessage(), - "org.jclouds.json.internal.DeserializationConstructorAndReflectiveTypeAdapterFactoryTest$DuplicateSerializedNames(int,int)" + - " declares multiple JSON parameters named foo"); - } + parameterizedCtorFactory.create(gson, TypeToken.get(DuplicateSerializedNames.class)); } private static class ValidatedConstructor { @@ -160,34 +133,18 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest this.bar = bar; } - @Override public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; ValidatedConstructor other = ValidatedConstructor.class.cast(obj); - if (bar != other.bar) - return false; - if (foo != other.foo) - return false; - return true; + return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar); } - - @Override - public String toString() { return "ValidatedConstructor[foo=" + foo + ",bar=" + bar + "]"; } } + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "negative!") public void testValidatedConstructor() throws IOException { - TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken - .get(ValidatedConstructor.class)); + TypeAdapter adapter = parameterizedCtorFactory.create(gson, + TypeToken.get(ValidatedConstructor.class)); assertEquals(new ValidatedConstructor(0, 1), adapter.fromJson("{\"foo\":0,\"bar\":1}")); - try { - adapter.fromJson("{\"foo\":-1,\"bar\":1}"); - fail(); - } catch (IllegalArgumentException expected) { - assertEquals("negative!", expected.getMessage()); - } + adapter.fromJson("{\"foo\":-1,\"bar\":1}"); } private static class GenericParamsCopiedIn { @@ -199,12 +156,11 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest this.foo = Lists.newArrayList(foo); this.bar = Maps.newHashMap(bar); } - } public void testGenericParamsCopiedIn() throws IOException { - TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken - .get(GenericParamsCopiedIn.class)); + TypeAdapter adapter = parameterizedCtorFactory.create(gson, + TypeToken.get(GenericParamsCopiedIn.class)); List inputFoo = Lists.newArrayList(); inputFoo.add("one"); Map inputBar = Maps.newHashMap(); @@ -222,29 +178,27 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest @Named("_bar") final int bar; - @ConstructorProperties({"foo", "_bar"}) + @ConstructorProperties({ "foo", "_bar" }) RenamedFields(int foo, int bar) { if (foo < 0) throw new IllegalArgumentException("negative!"); this.foo = foo; this.bar = bar; } - - @Override + public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; RenamedFields other = RenamedFields.class.cast(obj); - if (bar != other.bar) - return false; - if (foo != other.foo) - return false; - return true; + return other != null && Objects.equal(foo, other.foo) && Objects.equal(bar, other.bar); } } + public void testCanOverrideDefault() throws IOException { + Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory).create(); + + assertEquals(new RenamedFields(0, 1), gson.fromJson("{\"foo\":0,\"_bar\":1}", RenamedFields.class)); + assertEquals(gson.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}"); + } + public void testRenamedFields() throws IOException { TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken.get(RenamedFields.class)); assertEquals(new RenamedFields(0, 1), adapter.fromJson("{\"foo\":0,\"_bar\":1}")); @@ -255,44 +209,38 @@ public final class DeserializationConstructorAndReflectiveTypeAdapterFactoryTest final ValidatedConstructor x; final ValidatedConstructor y; - @ConstructorProperties({"x", "y"}) + @ConstructorProperties({ "x", "y" }) ComposedObjects(ValidatedConstructor x, ValidatedConstructor y) { this.x = checkNotNull(x); this.y = checkNotNull(y); } - @Override public boolean equals(Object obj) { ComposedObjects other = ComposedObjects.class.cast(obj); return other != null && Objects.equal(x, other.x) && Objects.equal(y, other.y); } - - @Override - public String toString() { return "ComposedObjects[x=" + x.toString() + ";y=" + y.toString() + "]"; } } - - public void checkSimpleComposedObject() throws IOException { - ValidatedConstructor x = new ValidatedConstructor(0,1); - ValidatedConstructor y = new ValidatedConstructor(1,2); - TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class)); - assertEquals(new ComposedObjects(x, y), adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}")); + + public void checkSimpleComposedObject() throws IOException { + ValidatedConstructor x = new ValidatedConstructor(0, 1); + ValidatedConstructor y = new ValidatedConstructor(1, 2); + TypeAdapter adapter = parameterizedCtorFactory + .create(gson, TypeToken.get(ComposedObjects.class)); + assertEquals(new ComposedObjects(x, y), + adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1},\"y\":{\"foo\":1,\"bar\":2}}")); } public void testEmptyObjectIsNull() throws IOException { - TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class)); + TypeAdapter adapter = parameterizedCtorFactory + .create(gson, TypeToken.get(ComposedObjects.class)); assertNull(adapter.fromJson("{}")); } @Test(expectedExceptions = NullPointerException.class) public void testPartialObjectStillThrows() throws IOException { - TypeAdapter adapter = parameterizedCtorFactory.create(gson, TypeToken.get(ComposedObjects.class)); + TypeAdapter adapter = parameterizedCtorFactory + .create(gson, TypeToken.get(ComposedObjects.class)); assertNull(adapter.fromJson("{\"x\":{\"foo\":0,\"bar\":1}}")); } - public void testCanOverrideDefault() throws IOException { - Gson gson = new GsonBuilder().registerTypeAdapterFactory(parameterizedCtorFactory).create(); - - assertEquals(new RenamedFields(0, 1), gson.fromJson("{\"foo\":0,\"_bar\":1}", RenamedFields.class)); - assertEquals(gson.toJson(new RenamedFields(0, 1)), "{\"foo\":0,\"_bar\":1}"); - } } diff --git a/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java b/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java index 1397775bef..ef2077754a 100644 --- a/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java +++ b/core/src/test/java/org/jclouds/json/internal/NamingStrategiesTest.java @@ -18,13 +18,13 @@ package org.jclouds.json.internal; * under the License. */ +import static org.jclouds.reflect.Reflection2.typeToken; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.fail; import java.beans.ConstructorProperties; -import java.lang.reflect.Constructor; import javax.inject.Inject; import javax.inject.Named; @@ -32,13 +32,13 @@ import javax.inject.Named; import org.jclouds.json.internal.NamingStrategies.AnnotationConstructorNamingStrategy; import org.jclouds.json.internal.NamingStrategies.AnnotationFieldNamingStrategy; import org.jclouds.json.internal.NamingStrategies.AnnotationOrNameFieldNamingStrategy; -import org.jclouds.json.internal.NamingStrategies.ConstructorFieldNamingStrategy; import org.jclouds.json.internal.NamingStrategies.ExtractNamed; import org.jclouds.json.internal.NamingStrategies.ExtractSerializedName; import org.jclouds.json.internal.NamingStrategies.NameExtractor; import org.testng.annotations.Test; import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.Invokable; import com.google.gson.FieldNamingStrategy; import com.google.gson.annotations.SerializedName; @@ -51,6 +51,7 @@ public final class NamingStrategiesTest { private static class SimpleTest { @SerializedName("aardvark") private String a; + @SuppressWarnings("unused") private String b; @Named("cat") private String c; @@ -75,7 +76,7 @@ public final class NamingStrategiesTest { public void testExtractSerializedName() throws Exception { - NameExtractor extractor = new ExtractSerializedName(); + NameExtractor extractor = new ExtractSerializedName(); assertEquals(extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(SerializedName.class)), "aardvark"); try { @@ -96,7 +97,7 @@ public final class NamingStrategiesTest { } public void testExtractNamed() throws Exception { - NameExtractor extractor = new ExtractNamed(); + NameExtractor extractor = new ExtractNamed(); try { extractor.extractName(SimpleTest.class.getDeclaredField("a").getAnnotation(Named.class)); } catch (NullPointerException e) { @@ -131,12 +132,12 @@ public final class NamingStrategiesTest { } public void testAnnotationConstructorFieldNamingStrategyCPAndNamed() throws Exception { - ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy( + AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy( ImmutableSet.of(ConstructorProperties.class), ImmutableSet.of(new ExtractNamed())); - Constructor constructor = strategy.getDeserializationConstructor(SimpleTest.class); + Invokable constructor = strategy.getDeserializer(typeToken(SimpleTest.class)); assertNotNull(constructor); - assertEquals(constructor.getParameterTypes().length, 4); + assertEquals(constructor.getParameters().size(), 4); assertEquals(strategy.translateName(constructor, 0), "aardvark"); assertEquals(strategy.translateName(constructor, 1), "bat"); @@ -144,9 +145,9 @@ public final class NamingStrategiesTest { // Note: @Named overrides the ConstructorProperties setting assertEquals(strategy.translateName(constructor, 3), "dingo"); - Constructor mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class); + Invokable mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class)); assertNotNull(mixedCtor); - assertEquals(mixedCtor.getParameterTypes().length, 4); + assertEquals(mixedCtor.getParameters().size(), 4); assertEquals(strategy.translateName(mixedCtor, 0), "aardvark"); assertEquals(strategy.translateName(mixedCtor, 1), "bat"); @@ -155,21 +156,21 @@ public final class NamingStrategiesTest { } public void testAnnotationConstructorFieldNamingStrategyCP() throws Exception { - ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy( - ImmutableSet.of(ConstructorProperties.class), ImmutableSet.of()); + AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy( + ImmutableSet.of(ConstructorProperties.class), ImmutableSet.>of()); - Constructor constructor = strategy.getDeserializationConstructor(SimpleTest.class); + Invokable constructor = strategy.getDeserializer(typeToken(SimpleTest.class)); assertNotNull(constructor); - assertEquals(constructor.getParameterTypes().length, 4); + assertEquals(constructor.getParameters().size(), 4); assertEquals(strategy.translateName(constructor, 0), "aardvark"); assertEquals(strategy.translateName(constructor, 1), "bat"); assertEquals(strategy.translateName(constructor, 2), "coyote"); assertEquals(strategy.translateName(constructor, 3), "dog"); - Constructor mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class); + Invokable mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class)); assertNotNull(mixedCtor); - assertEquals(mixedCtor.getParameterTypes().length, 4); + assertEquals(mixedCtor.getParameters().size(), 4); assertEquals(strategy.translateName(mixedCtor, 0), "thiscanbeoverriddenbyNamed"); assertNull(strategy.translateName(mixedCtor, 1)); @@ -178,21 +179,21 @@ public final class NamingStrategiesTest { } public void testAnnotationConstructorFieldNamingStrategyInject() throws Exception { - ConstructorFieldNamingStrategy strategy = new AnnotationConstructorNamingStrategy( + AnnotationConstructorNamingStrategy strategy = new AnnotationConstructorNamingStrategy( ImmutableSet.of(Inject.class), ImmutableSet.of(new ExtractNamed())); - Constructor constructor = strategy.getDeserializationConstructor(SimpleTest.class); + Invokable constructor = strategy.getDeserializer(typeToken(SimpleTest.class)); assertNotNull(constructor); - assertEquals(constructor.getParameterTypes().length, 5); + assertEquals(constructor.getParameters().size(), 5); assertEquals(strategy.translateName(constructor, 0), "aa"); assertEquals(strategy.translateName(constructor, 1), "bb"); assertEquals(strategy.translateName(constructor, 2), "cc"); assertEquals(strategy.translateName(constructor, 3), "dd"); - Constructor mixedCtor = strategy.getDeserializationConstructor(MixedConstructorTest.class); + Invokable mixedCtor = strategy.getDeserializer(typeToken(MixedConstructorTest.class)); assertNotNull(mixedCtor); - assertEquals(mixedCtor.getParameterTypes().length, 4); + assertEquals(mixedCtor.getParameters().size(), 4); assertEquals(strategy.translateName(mixedCtor, 0), "aardvark"); assertEquals(strategy.translateName(mixedCtor, 1), "bat"); diff --git a/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java b/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java new file mode 100644 index 0000000000..33def24290 --- /dev/null +++ b/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java @@ -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>() { + private static final long serialVersionUID = 1L; + }.getType(); + private Type fluentIterableResourceType = new TypeToken>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testFluentIterable() { + FluentIterable noNulls = fluentIterable.fromJson("[\"value\",\"a test string!\"]", fluentIterableType); + assertEquals(noNulls.toList(), ImmutableList.of("value", "a test string!")); + FluentIterable withNull = fluentIterable.fromJson("[null,\"a test string!\"]", fluentIterableType); + assertEquals(withNull.toList(), ImmutableList.of("a test string!")); + FluentIterable withDupes = fluentIterable.fromJson("[\"value\",\"value\"]", fluentIterableType); + assertEquals(withDupes.toList(), ImmutableList.of("value", "value")); + FluentIterable 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>() { + private static final long serialVersionUID = 1L; + }.getType(); + private Type collectionResourceType = new TypeToken>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testCollection() { + Collection noNulls = collection.fromJson("[\"value\",\"a test string!\"]", collectionType); + assertEquals(noNulls, ImmutableList.of("value", "a test string!")); + Collection withNull = collection.fromJson("[null,\"a test string!\"]", collectionType); + assertEquals(withNull, ImmutableList.of("a test string!")); + Collection withDupes = collection.fromJson("[\"value\",\"value\"]", collectionType); + assertEquals(withDupes, ImmutableList.of("value", "value")); + Collection 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>() { + private static final long serialVersionUID = 1L; + }.getType(); + private Type iterableResourceType = new TypeToken>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testIterable() { + Iterable noNulls = iterable.fromJson("[\"value\",\"a test string!\"]", iterableType); + assertEquals(noNulls, ImmutableList.of("value", "a test string!")); + Iterable withNull = iterable.fromJson("[null,\"a test string!\"]", iterableType); + assertEquals(withNull, ImmutableList.of("a test string!")); + Iterable withDupes = iterable.fromJson("[\"value\",\"value\"]", iterableType); + assertEquals(withDupes, ImmutableList.of("value", "value")); + Iterable 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>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testWildcardExtends() { + Iterable 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>() { + private static final long serialVersionUID = 1L; + }.getType(); + private Type setResourceType = new TypeToken>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testSet() { + Set noNulls = set.fromJson("[\"value\",\"a test string!\"]", setType); + assertEquals(noNulls, ImmutableSet.of("value", "a test string!")); + Set withNull = set.fromJson("[null,\"a test string!\"]", setType); + assertEquals(withNull, ImmutableSet.of("a test string!")); + Set withDupes = set.fromJson("[\"value\",\"value\"]", setType); + assertEquals(withDupes, ImmutableSet.of("value")); + Set 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>() { + private static final long serialVersionUID = 1L; + }.getType(); + private Type mapResourceType = new TypeToken>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testMap() { + Map noNulls = map.fromJson("{\"value\":\"a test string!\"}", mapType); + assertEquals(noNulls, ImmutableMap.of("value", "a test string!")); + Map withNull = map.fromJson("{\"value\":null}", mapType); + assertEquals(withNull, ImmutableMap.of()); + Map withEmpty = map.fromJson("{\"value\":\"\"}", mapType); + assertEquals(withEmpty, ImmutableMap.of("value", "")); + Map 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>() { + private static final long serialVersionUID = 1L; + }.getType(); + private Type multimapResourceType = new TypeToken>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testMultimap() { + Multimap noNulls = multimap.fromJson("{\"value\":[\"a test string!\"]}", multimapType); + assertEquals(noNulls, ImmutableMultimap.of("value", "a test string!")); + Multimap withNull = multimap.fromJson("{\"value\":[null]}", multimapType); + assertEquals(withNull, ImmutableMultimap.of()); + Multimap withEmpty = multimap.fromJson("{\"value\":[\"\"]}", multimapType); + assertEquals(withEmpty, ImmutableMultimap.of("value", "")); + Multimap withDupes = multimap.fromJson("{\"key\":[\"value\",\"value\"]}", multimapType); + assertEquals(withDupes.get("key"), ImmutableList.of("value", "value")); + Multimap 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 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"))); + } +} diff --git a/core/src/test/java/org/jclouds/json/internal/OptionalTypeAdapterFactoryTest.java b/core/src/test/java/org/jclouds/json/internal/OptionalTypeAdapterFactoryTest.java index 6ccdd89816..2e5da4c543 100644 --- a/core/src/test/java/org/jclouds/json/internal/OptionalTypeAdapterFactoryTest.java +++ b/core/src/test/java/org/jclouds/json/internal/OptionalTypeAdapterFactoryTest.java @@ -42,7 +42,7 @@ public class OptionalTypeAdapterFactoryTest { * Simple type with an Optional field */ static class SimpleBean { - private Optional value; + private final Optional value; private final String someOtherValue; public SimpleBean(Optional value, String someOtherValue) { @@ -51,6 +51,7 @@ public class OptionalTypeAdapterFactoryTest { } // Required to ensure GSON doesn't initialize our Optional to null! + @SuppressWarnings("unused") private SimpleBean() { this.value = Optional.absent(); this.someOtherValue = null; @@ -64,22 +65,9 @@ public class OptionalTypeAdapterFactoryTest { return someOtherValue; } - @Override - public int hashCode() { - return Objects.hashCode(value, someOtherValue); - } - - @Override - public boolean equals(Object that) { - if (that == null || that.getClass() != getClass()) - return false; - SimpleBean other = (SimpleBean) that; - return Objects.equal(value, other.value) && Objects.equal(someOtherValue, other.someOtherValue); - } - - @Override - public String toString() { - return Objects.toStringHelper("SimpleBean").add("value", value).add("someOtherValue", someOtherValue).toString(); + public boolean equals(Object other) { + SimpleBean that = SimpleBean.class.cast(other); + return Objects.equal(value, that.value) && Objects.equal(someOtherValue, that.someOtherValue); } } diff --git a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java index 4255b5225a..b1087cc666 100644 --- a/core/src/test/java/org/jclouds/reflect/Reflection2Test.java +++ b/core/src/test/java/org/jclouds/reflect/Reflection2Test.java @@ -19,12 +19,15 @@ package org.jclouds.reflect; import static com.google.common.base.Functions.toStringFunction; +import static org.jclouds.reflect.Reflection2.constructors; import static org.jclouds.reflect.Reflection2.method; import static org.jclouds.reflect.Reflection2.methods; import static org.jclouds.reflect.Reflection2.typeToken; import static org.testng.Assert.assertEquals; +import java.util.HashSet; import java.util.Set; +import java.util.SortedSet; import org.testng.annotations.Test; @@ -32,7 +35,9 @@ import com.google.common.base.Function; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableSet; import com.google.common.reflect.Invokable; +import com.google.common.reflect.Parameter; import com.google.common.reflect.TypeToken; +import com.google.inject.TypeLiteral; /** * @@ -41,7 +46,31 @@ import com.google.common.reflect.TypeToken; @Test public class Reflection2Test { - public void testTypeToken() { + /** + * useful when converting to and from type literals from other libraries such as guice and gson. + */ + public void testTypeTokenForType() { + TypeLiteral> guice = new TypeLiteral>() { + }; + + assertEquals(typeToken(guice.getType()), new TypeToken>() { + private static final long serialVersionUID = 1L; + }); + } + + public void testConstructors() { + Set ctorParams = FluentIterable.from(constructors(TypeToken.of(HashSet.class))) + .transform(new Function, Iterable>() { + public Iterable apply(Invokable input) { + return input.getParameters(); + } + }).transform(toStringFunction()).toSet(); + + assertEquals(ctorParams, ImmutableSet.of("[]", "[java.util.Collection arg0]", + "[int arg0, float arg1]", "[int arg0]", "[int arg0, float arg1, boolean arg2]")); + } + + public void testTypeTokenForClass() { assertEquals(typeToken(String.class), TypeToken.of(String.class)); } @@ -65,15 +94,28 @@ public class Reflection2Test { assertEquals(methodInSuper.getParameters().get(0).getType().getRawType(), Object.class); } - public void testMethods() { - Set methodNames = FluentIterable.from(methods(Set.class)) - .transform(new Function, String>() { - public String apply(Invokable input) { - return input.getName(); - } - }).transform(toStringFunction()).toSet(); + ImmutableSet setMethods = ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains", + "addAll", "size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll"); - assertEquals(methodNames, ImmutableSet.of("add", "equals", "hashCode", "clear", "isEmpty", "contains", "addAll", - "size", "toArray", "iterator", "remove", "removeAll", "containsAll", "retainAll")); + public void testMethods() { + Set methodNames = FluentIterable.from(methods(Set.class)).transform(invokableToName) + .transform(toStringFunction()).toSet(); + + assertEquals(methodNames, setMethods); } + + public void testMethodsSubClass() { + Set 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, String> invokableToName = new Function, String>() { + public String apply(Invokable input) { + return input.getName(); + } + }; } diff --git a/core/src/test/java/org/jclouds/rest/config/RestModuleTest.java b/core/src/test/java/org/jclouds/rest/config/RestModuleTest.java new file mode 100644 index 0000000000..0a48f1e8c1 --- /dev/null +++ b/core/src/test/java/org/jclouds/rest/config/RestModuleTest.java @@ -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 get(); + } + + public void testPutInvokablesWhenInterfacesMatch() { + Cache, 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>() { + private static final long serialVersionUID = 1L; + }); + } + + private static interface AsyncWithException { + ListenableFuture get() throws IOException; + } + + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = ".* has different typed exceptions than target .*") + public void testPutInvokablesWhenInterfacesMatchExceptExceptions() { + Cache, Invokable> cache = CacheBuilder.newBuilder().build(); + RestModule.putInvokables(Sync.class, AsyncWithException.class, cache); + } + + private static interface AsyncWithMisnamedMethod { + ListenableFuture got(); + } + + @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "no such method .*") + public void testPutInvokablesWhenTargetMethodNotFound() { + Cache, Invokable> cache = CacheBuilder.newBuilder().build(); + RestModule.putInvokables(Sync.class, AsyncWithMisnamedMethod.class, cache); + } + + static final Predicate, Invokable>> isHttpInvokable = new Predicate, Invokable>>() { + public boolean apply(Map.Entry, Invokable> in) { + return in.getKey().getOwnerType().getRawType().equals(HttpClient.class) + && in.getValue().getOwnerType().getRawType().equals(HttpAsyncClient.class); + } + }; + + public void testSeedKnownSync2AsyncIncludesHttpClientByDefault() { + Map, Invokable> cache = RestModule.seedKnownSync2AsyncInvokables( + ImmutableMap., Class> of()).asMap(); + + assertEquals(cache.size(), 6); + assertEquals(filterEntries(cache, isHttpInvokable), cache); + } + + public void testSeedKnownSync2AsyncInvokablesInterfacesMatch() { + Map, Invokable> cache = RestModule.seedKnownSync2AsyncInvokables( + ImmutableMap., Class> of(Sync.class, Async.class)).asMap(); + + assertEquals(cache.size(), 7); + + cache = filterEntries(cache, not(isHttpInvokable)); + + assertEquals(cache.size(), 1); + } + +} diff --git a/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java b/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java index d852cc91ef..c4a9829877 100644 --- a/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java +++ b/labs/abiquo/src/main/java/org/jclouds/abiquo/domain/DomainWrapper.java @@ -22,8 +22,9 @@ package org.jclouds.abiquo.domain; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.transform; +import static org.jclouds.reflect.Reflection2.constructor; -import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.net.URI; import java.util.Collection; import java.util.List; @@ -44,6 +45,7 @@ import com.abiquo.server.core.task.TaskDto; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.reflect.Invokable; /** * This class is used to decorate transport objects with high level @@ -103,13 +105,12 @@ public abstract class DomainWrapper { } try { - Constructor cons = wrapperClass.getDeclaredConstructor(RestContext.class, target.getClass()); - if (!cons.isAccessible()) { - cons.setAccessible(true); - } - return cons.newInstance(context, target); - } catch (Exception ex) { - throw new WrapperException(wrapperClass, target, ex); + Invokable cons = constructor(wrapperClass, RestContext.class, target.getClass()); + return cons.invoke(null, context, target); + } catch (InvocationTargetException e) { + throw new WrapperException(wrapperClass, target, e.getTargetException()); + } catch (IllegalAccessException e) { + throw new WrapperException(wrapperClass, target, e); } } diff --git a/labs/google-compute/src/main/java/org/jclouds/googlecompute/domain/ListPage.java b/labs/google-compute/src/main/java/org/jclouds/googlecompute/domain/ListPage.java index e286f137b0..86865359f1 100644 --- a/labs/google-compute/src/main/java/org/jclouds/googlecompute/domain/ListPage.java +++ b/labs/google-compute/src/main/java/org/jclouds/googlecompute/domain/ListPage.java @@ -46,8 +46,8 @@ public class ListPage extends IterableWithMarker { private final Iterable items; protected ListPage(Kind kind, String id, URI selfLink, String nextPageToken, Iterable items) { - this.kind = checkNotNull(kind, "kind of %id", id); this.id = checkNotNull(id, "id"); + this.kind = checkNotNull(kind, "kind of %id", id); this.selfLink = checkNotNull(selfLink, "selfLink of %id", id); this.nextPageToken = nextPageToken; this.items = items != null ? ImmutableSet.copyOf(items) : ImmutableSet.of(); @@ -90,7 +90,7 @@ public class ListPage extends IterableWithMarker { public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; - ListPage that = ListPage.class.cast(obj); + ListPage that = ListPage.class.cast(obj); return equal(this.kind, that.kind) && equal(this.id, that.id); } diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImageDetails.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImageDetails.java index b111370720..22df7117e6 100644 --- a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImageDetails.java +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImageDetails.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseImageDetails extends ParseJson> { - static class Images extends PaginatedCollection { +public class ParseImageDetails extends ParseJson { + static class Images extends PaginatedCollection { @ConstructorProperties({ "images", "images_links" }) - protected Images(Iterable images, Iterable images_links) { + protected Images(Iterable images, Iterable images_links) { super(images, images_links); } @@ -60,8 +60,7 @@ public class ParseImageDetails extends ParseJson> @Inject public ParseImageDetails(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Images.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable { diff --git a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImages.java b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImages.java index 0b8ea223fc..7799f6e3fa 100644 --- a/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImages.java +++ b/labs/openstack-glance/src/main/java/org/jclouds/openstack/glance/v1_0/functions/internal/ParseImages.java @@ -48,11 +48,11 @@ import com.google.inject.TypeLiteral; */ @Beta @Singleton -public class ParseImages extends ParseJson> { - static class Images extends PaginatedCollection { +public class ParseImages extends ParseJson { + static class Images extends PaginatedCollection { @ConstructorProperties({ "images", "images_links" }) - protected Images(Iterable images, Iterable images_links) { + protected Images(Iterable images, Iterable images_links) { super(images, images_links); } @@ -60,8 +60,7 @@ public class ParseImages extends ParseJson> { @Inject public ParseImages(Json json) { - super(json, new TypeLiteral>() { - }); + super(json, TypeLiteral.get(Images.class)); } public static class ToPagedIterable extends CallerArg0ToPagedIterable {