From 0fb1b459a607f21f6a8ebd7492879d02553c4b88 Mon Sep 17 00:00:00 2001 From: Josef Cacek Date: Wed, 2 Dec 2015 23:50:15 +0100 Subject: [PATCH] JCLOUDS-1044 fix handling NULL JsonTokens in adapters under NullFilteringTypeAdapterFactories class --- .../jclouds/chef/config/ChefParserModule.java | 59 ++++--------------- .../chef/config/ChefParserModuleTest.java | 24 ++++++++ .../NullFilteringTypeAdapterFactories.java | 11 +++- ...NullFilteringTypeAdapterFactoriesTest.java | 21 +++++++ 4 files changed, 67 insertions(+), 48 deletions(-) diff --git a/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java b/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java index 3ebe2ea8d6..da4084d6c2 100644 --- a/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java +++ b/apis/chef/src/main/java/org/jclouds/chef/config/ChefParserModule.java @@ -16,8 +16,6 @@ */ package org.jclouds.chef.config; -import static com.google.common.base.Objects.equal; -import static com.google.common.base.Objects.toStringHelper; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -39,11 +37,11 @@ import org.jclouds.crypto.Crypto; import org.jclouds.crypto.Pems; import org.jclouds.json.config.GsonModule.DateAdapter; import org.jclouds.json.config.GsonModule.Iso8601DateAdapter; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories; import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.MapTypeAdapterFactory; import org.jclouds.json.internal.NullHackJsonLiteralAdapter; import com.google.common.base.Charsets; -import com.google.common.base.Objects; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; @@ -57,7 +55,7 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.TypeAdapter; import com.google.gson.internal.JsonReaderInternalAccess; import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; +import com.google.gson.stream.JsonToken; import com.google.inject.AbstractModule; import com.google.inject.ImplementedBy; import com.google.inject.Provides; @@ -205,33 +203,19 @@ public class ChefParserModule extends AbstractModule { private String id; } - // The NullFilteringTypeAdapterFactories.MapTypeAdapter class is final. Do - // the same logic here - private static final class KeepLastRepeatedKeyMapTypeAdapter extends TypeAdapter> { - - protected final TypeAdapter keyAdapter; - protected final TypeAdapter valueAdapter; + private static final class KeepLastRepeatedKeyMapTypeAdapter + extends NullFilteringTypeAdapterFactories.MapTypeAdapter { protected KeepLastRepeatedKeyMapTypeAdapter(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(); + super(keyAdapter, valueAdapter); } + @Override public Map read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } Map result = Maps.newHashMap(); in.beginObject(); while (in.hasNext()) { @@ -239,33 +223,14 @@ public class ChefParserModule extends AbstractModule { K name = keyAdapter.read(in); V value = valueAdapter.read(in); if (value != null) { - // If there are repeated keys, overwrite them to only keep the last one + // If there are repeated keys, overwrite them to only keep the + // last one result.put(name, value); } } in.endObject(); return ImmutableMap.copyOf(result); } - - @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; - KeepLastRepeatedKeyMapTypeAdapter that = KeepLastRepeatedKeyMapTypeAdapter.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 KeepLastRepeatedKeyMapTypeAdapterFactory extends MapTypeAdapterFactory { diff --git a/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java b/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java index 9b1354ee3c..8ab60ee386 100644 --- a/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java +++ b/apis/chef/src/test/java/org/jclouds/chef/config/ChefParserModuleTest.java @@ -20,12 +20,15 @@ import static com.google.common.base.Objects.equal; import static org.testng.Assert.assertEquals; import java.lang.reflect.Type; +import java.util.List; import java.util.Map; import org.jclouds.chef.config.ChefParserModule.KeepLastRepeatedKeyMapTypeAdapterFactory; +import org.jclouds.json.internal.NullFilteringTypeAdapterFactories.ListTypeAdapterFactory; import org.testng.annotations.Test; import com.google.common.base.Objects; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.reflect.TypeToken; import com.google.gson.Gson; @@ -90,4 +93,25 @@ public class ChefParserModuleTest { assertEquals(duplicates, ImmutableMap.of("i-foo", new KeyValue("i-foo", "foo2"), "i-bar", new KeyValue("i-bar", "bar2"))); } + + private Gson listInMap = new GsonBuilder().registerTypeAdapterFactory(new KeepLastRepeatedKeyMapTypeAdapterFactory()) + .registerTypeAdapterFactory(new ListTypeAdapterFactory()).create(); + private Type listInMapType = new TypeToken>>>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testListInMap() { + Map>> notNull = listInMap + .fromJson("{\"value\":[{\"x\":\"y\",\"a\":\"b\"},{\"u\":\"v\"}]}", listInMapType); + assertEquals(notNull, + ImmutableMap.of("value", ImmutableList.of(ImmutableMap.of("x", "y", "a", "b"), ImmutableMap.of("u", "v")))); + Map>> innerMapValueNull = listInMap + .fromJson("{\"value\":[{\"x\":\"y\",\"a\":null},{\"u\":\"v\"}]}", listInMapType); + assertEquals(innerMapValueNull, + ImmutableMap.of("value", ImmutableList.of(ImmutableMap.of("x", "y"), ImmutableMap.of("u", "v")))); + Map>> withNullInList = listInMap.fromJson("{\"value\":[null]}", listInMapType); + assertEquals(withNullInList, ImmutableMap.of("value", ImmutableList.of())); + Map>> withNullAsList = listInMap.fromJson("{\"parent\":null}", listInMapType); + assertEquals(withNullAsList, ImmutableMap.of()); + } } diff --git a/core/src/main/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactories.java b/core/src/main/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactories.java index 0479d874df..7ad257e868 100644 --- a/core/src/main/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactories.java +++ b/core/src/main/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactories.java @@ -30,6 +30,7 @@ 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.JsonToken; import com.google.gson.stream.JsonWriter; import java.io.IOException; @@ -85,6 +86,10 @@ public class NullFilteringTypeAdapterFactories { @SuppressWarnings("unchecked") protected , B extends ImmutableCollection.Builder> C readAndBuild(JsonReader in, B builder) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } in.beginArray(); while (in.hasNext()) { E element = elementAdapter.read(in); @@ -278,7 +283,7 @@ public class NullFilteringTypeAdapterFactories { } } - private static final class MapTypeAdapter extends TypeAdapter> { + public static class MapTypeAdapter extends TypeAdapter> { protected final TypeAdapter keyAdapter; protected final TypeAdapter valueAdapter; @@ -303,6 +308,10 @@ public class NullFilteringTypeAdapterFactories { } public Map read(JsonReader in) throws IOException { + if (in.peek() == JsonToken.NULL) { + in.nextNull(); + return null; + } ImmutableMap.Builder result = ImmutableMap.builder(); in.beginObject(); while (in.hasNext()) { diff --git a/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java b/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java index bf82bc6270..52f94abc6a 100644 --- a/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java +++ b/core/src/test/java/org/jclouds/json/internal/NullFilteringTypeAdapterFactoriesTest.java @@ -297,4 +297,25 @@ public class NullFilteringTypeAdapterFactoriesTest { assertEquals(resourceDupes.get("i-foo"), ImmutableList.of(new Resource("i-foo", "foo"), new Resource("i-bar", "bar"))); } + + private Gson listInMap = new GsonBuilder().registerTypeAdapterFactory(new MapTypeAdapterFactory()) + .registerTypeAdapterFactory(new ListTypeAdapterFactory()).create(); + private Type listInMapType = new TypeToken>>>() { + private static final long serialVersionUID = 1L; + }.getType(); + + public void testListInMap() { + Map>> notNull = listInMap + .fromJson("{\"value\":[{\"x\":\"y\",\"a\":\"b\"},{\"u\":\"v\"}]}", listInMapType); + assertEquals(notNull, + ImmutableMap.of("value", ImmutableList.of(ImmutableMap.of("x", "y", "a", "b"), ImmutableMap.of("u", "v")))); + Map>> innerMapValueNull = listInMap + .fromJson("{\"value\":[{\"x\":\"y\",\"a\":null},{\"u\":\"v\"}]}", listInMapType); + assertEquals(innerMapValueNull, + ImmutableMap.of("value", ImmutableList.of(ImmutableMap.of("x", "y"), ImmutableMap.of("u", "v")))); + Map>> withNullInList = listInMap.fromJson("{\"value\":[null]}", listInMapType); + assertEquals(withNullInList, ImmutableMap.of("value", ImmutableList.of())); + Map>> withNullAsList = listInMap.fromJson("{\"parent\":null}", listInMapType); + assertEquals(withNullAsList, ImmutableMap.of()); + } }