From 7ed9ebda471e0c1f6296837cb8e5200b1652956e Mon Sep 17 00:00:00 2001 From: Adam Lowe Date: Tue, 3 Jul 2012 14:46:54 +0100 Subject: [PATCH] Moving TypeAdapterFactories that eliminate null values in Sets, Maps and Multimaps to jclouds-core. --- .../nova/v2_0/config/NovaParserModule.java | 163 +----------------- .../org/jclouds/json/config/GsonModule.java | 6 + .../IgnoreNullMapTypeAdapterFactory.java | 82 +++++++++ .../IgnoreNullMultimapTypeAdapterFactory.java | 90 ++++++++++ .../IgnoreNullSetTypeAdapterFactory.java | 77 +++++++++ .../ParseJobsFromJsonResponseTest.java | 16 +- 6 files changed, 270 insertions(+), 164 deletions(-) create mode 100644 core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java create mode 100644 core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java create mode 100644 core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java diff --git a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaParserModule.java b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaParserModule.java index 0768056b3a..ba96190fb8 100644 --- a/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaParserModule.java +++ b/apis/openstack-nova/src/main/java/org/jclouds/openstack/nova/v2_0/config/NovaParserModule.java @@ -19,8 +19,6 @@ package org.jclouds.openstack.nova.v2_0.config; import java.beans.ConstructorProperties; -import java.io.IOException; -import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Date; import java.util.Map; @@ -41,21 +39,18 @@ import org.jclouds.openstack.v2_0.domain.Link; import org.jclouds.openstack.v2_0.domain.Resource; import com.google.common.base.Objects; -import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMultimap; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; -import com.google.gson.*; -import com.google.gson.internal.JsonReaderInternalAccess; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import com.google.inject.AbstractModule; import com.google.inject.Provides; -import com.google.inject.TypeLiteral; /** * @author Adrian Cole @@ -76,8 +71,6 @@ public class NovaParserModule extends AbstractModule { @Override protected void configure() { bind(DateAdapter.class).to(GsonModule.Iso8601DateAdapter.class); - bind(new TypeLiteral>() { - }).toInstance(ImmutableSet.of(new SetTypeAdapterFactory(), new MapTypeAdapterFactory(), new MultimapTypeAdapterFactory())); } @Singleton @@ -165,146 +158,4 @@ public class NovaParserModule extends AbstractModule { } } } - - /** - * Eliminates nulls from within a set - *

- * Treats [null] as the empty set; [A, null] as [A]; etc. - */ - 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; - } - - Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0]; - TypeAdapter elementAdapter = gson.getAdapter(TypeToken.get(elementType)); - return (TypeAdapter) newSetAdapter(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 Set read(JsonReader in) throws IOException { - Set result = Sets.newLinkedHashSet(); - in.beginArray(); - while (in.hasNext()) { - E element = elementAdapter.read(in); - if (element != null) result.add(element); - } - in.endArray(); - return result; - } - }.nullSafe(); - } - } - - /** - * Eliminates null values from incoming maps - *

- * Treats ["a":null] as the empty map; ["a":1, "b":null] as ["a":1]; etc. - */ - public static class MapTypeAdapterFactory 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); - } - - private 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(keyAdapter.toJson(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(); - } - } - - /** - * Parses Multi-maps to/from json - */ - public static class MultimapTypeAdapterFactory 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) newMapAdapter(keyAdapter, valueAdapter); - } - - private TypeAdapter> newMapAdapter(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(keyAdapter.toJson(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/config/GsonModule.java b/core/src/main/java/org/jclouds/json/config/GsonModule.java index e5a3011b19..8d273c54ce 100644 --- a/core/src/main/java/org/jclouds/json/config/GsonModule.java +++ b/core/src/main/java/org/jclouds/json/config/GsonModule.java @@ -39,6 +39,9 @@ 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.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; @@ -94,6 +97,9 @@ public class GsonModule extends AbstractModule { builder.registerTypeAdapter(byte[].class, byteArrayAdapter.nullSafe()); builder.registerTypeAdapter(JsonBall.class, jsonAdapter.nullSafe()); builder.registerTypeAdapterFactory(new OptionalTypeAdapterFactory()); + builder.registerTypeAdapterFactory(new IgnoreNullSetTypeAdapterFactory()); + builder.registerTypeAdapterFactory(new IgnoreNullMapTypeAdapterFactory()); + builder.registerTypeAdapterFactory(new IgnoreNullMultimapTypeAdapterFactory()); AnnotationConstructorNamingStrategy deserializationPolicy = new AnnotationConstructorNamingStrategy( diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java new file mode 100644 index 0000000000..7fcfb2b9d3 --- /dev/null +++ b/core/src/main/java/org/jclouds/json/internal/IgnoreNullMapTypeAdapterFactory.java @@ -0,0 +1,82 @@ +/** + * 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); + } + + private 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(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java new file mode 100644 index 0000000000..77938bda58 --- /dev/null +++ b/core/src/main/java/org/jclouds/json/internal/IgnoreNullMultimapTypeAdapterFactory.java @@ -0,0 +1,90 @@ +/** + * 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) newMapAdapter(keyAdapter, valueAdapter); + } + + private TypeAdapter> newMapAdapter(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(); + } +} \ No newline at end of file diff --git a/core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java b/core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java new file mode 100644 index 0000000000..393a710f3a --- /dev/null +++ b/core/src/main/java/org/jclouds/json/internal/IgnoreNullSetTypeAdapterFactory.java @@ -0,0 +1,77 @@ +/** + * 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.Sets; +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); + } + + 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 Set read(JsonReader in) throws IOException { + Set result = Sets.newLinkedHashSet(); + in.beginArray(); + while (in.hasNext()) { + E element = elementAdapter.read(in); + if (element != null) result.add(element); + } + in.endArray(); + return result; + } + }.nullSafe(); + } +} diff --git a/providers/gogrid/src/test/java/org/jclouds/gogrid/functions/ParseJobsFromJsonResponseTest.java b/providers/gogrid/src/test/java/org/jclouds/gogrid/functions/ParseJobsFromJsonResponseTest.java index a5f5d7de6f..d725ee60fe 100644 --- a/providers/gogrid/src/test/java/org/jclouds/gogrid/functions/ParseJobsFromJsonResponseTest.java +++ b/providers/gogrid/src/test/java/org/jclouds/gogrid/functions/ParseJobsFromJsonResponseTest.java @@ -40,6 +40,7 @@ import org.jclouds.io.Payloads; import org.jclouds.json.config.GsonModule; import org.testng.annotations.Test; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; @@ -61,18 +62,17 @@ public class ParseJobsFromJsonResponseTest { ParseJobListFromJsonResponse parser = i.getInstance(ParseJobListFromJsonResponse.class); SortedSet response = parser.apply(new HttpResponse(200, "ok", Payloads.newInputStreamPayload(is))); - Map details = Maps.newTreeMap(); - details.put("description", null); - details.put("image", "GSI-f8979644-e646-4711-ad58-d98a5fa3612c"); - details.put("ip", "204.51.240.189"); - details.put("name", "ServerCreated40562"); - details.put("type", "virtual_server"); + Map details = ImmutableMap.of( + "image", "GSI-f8979644-e646-4711-ad58-d98a5fa3612c", + "ip", "204.51.240.189", + "name", "ServerCreated40562", + "type", "virtual_server"); Job job = new Job(250628L, new Option(7L, "DeleteVirtualServer", "Delete Virtual Server"), ObjectType.VIRTUAL_SERVER, new Date(1267404528895L), new Date(1267404538592L), JobState.SUCCEEDED, 1, "3116784158f0af2d-24076@api.gogrid.com", ImmutableSortedSet.of(new JobProperties(940263L, new Date( - 1267404528897L), JobState.CREATED, null), new JobProperties(940264L, new Date(1267404528967L), - JobState.QUEUED, null)), details); + 1267404528897L), JobState.CREATED, null), new JobProperties(940264L, new Date(1267404528967L), + JobState.QUEUED, null)), details); assertEquals(job, Iterables.getOnlyElement(response)); }