Moving TypeAdapterFactories that eliminate null values in Sets, Maps and Multimaps to jclouds-core.

This commit is contained in:
Adam Lowe 2012-07-03 14:46:54 +01:00
parent ed536de3d0
commit 7ed9ebda47
6 changed files with 270 additions and 164 deletions

View File

@ -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<Set<TypeAdapterFactory>>() {
}).toInstance(ImmutableSet.<TypeAdapterFactory>of(new SetTypeAdapterFactory(), new MapTypeAdapterFactory(), new MultimapTypeAdapterFactory()));
}
@Singleton
@ -165,146 +158,4 @@ public class NovaParserModule extends AbstractModule {
}
}
}
/**
* Eliminates nulls from within a set
* <p/>
* Treats [null] as the empty set; [A, null] as [A]; etc.
*/
public static class SetTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Set.class || !(type instanceof ParameterizedType)) {
return null;
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) newSetAdapter(elementAdapter);
}
private <E> TypeAdapter<Set<E>> newSetAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Set<E>>() {
public void write(JsonWriter out, Set<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
public Set<E> read(JsonReader in) throws IOException {
Set<E> 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
* <p/>
* Treats ["a":null] as the empty map; ["a":1, "b":null] as ["a":1]; etc.
*/
public static class MapTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Map.class || !(type instanceof ParameterizedType)) {
return null;
}
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return (TypeAdapter<T>) newMapAdapter(keyAdapter, valueAdapter);
}
private <K,V> TypeAdapter<Map<K, V>> newMapAdapter(final TypeAdapter<K> keyAdapter, final TypeAdapter<V> valueAdapter) {
return new TypeAdapter<Map<K, V>>() {
public void write(JsonWriter out, Map<K, V> value) throws IOException {
out.beginObject();
for (Map.Entry<K, V> element : value.entrySet()) {
out.name(keyAdapter.toJson(element.getKey()));
valueAdapter.write(out, element.getValue());
}
out.endObject();
}
public Map<K, V> read(JsonReader in) throws IOException {
Map<K, V> result = Maps.newLinkedHashMap();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
V value = valueAdapter.read(in);
if (value != null) result.put(name, value);
}
in.endObject();
return result;
}
}.nullSafe();
}
}
/**
* Parses Multi-maps to/from json
*/
public static class MultimapTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if ((typeToken.getRawType() != Multimap.class) || !(type instanceof ParameterizedType)) {
return null;
}
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return (TypeAdapter<T>) newMapAdapter(keyAdapter, valueAdapter);
}
private <K,V> TypeAdapter<Multimap<K, V>> newMapAdapter(final TypeAdapter<K> keyAdapter, final TypeAdapter<V> valueAdapter) {
return new TypeAdapter<Multimap<K, V>>() {
public void write(JsonWriter out, Multimap<K, V> map) throws IOException {
out.beginObject();
for (K key : map.keySet()) {
out.name(keyAdapter.toJson(key));
out.beginArray();
for (V value : map.get(key)) {
valueAdapter.write(out, value);
}
out.endArray();
}
out.endObject();
}
public Multimap<K, V> read(JsonReader in) throws IOException {
ImmutableMultimap.Builder<K, V> result = ImmutableListMultimap.builder();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
in.beginArray();
while (in.hasNext()) {
V value = valueAdapter.read(in);
if (value != null) result.put(name, value);
}
in.endArray();
}
in.endObject();
return result.build();
}
}.nullSafe();
}
}
}

View File

@ -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(

View File

@ -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
* <p/>
* Treats {"a":null} as the empty map; {"a":1, "b":null} as {"a":1}; etc.
*
* @author Adam Lowe
*/
public class IgnoreNullMapTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Map.class || !(type instanceof ParameterizedType)) {
return null;
}
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return (TypeAdapter<T>) newMapAdapter(keyAdapter, valueAdapter);
}
private <K,V> TypeAdapter<Map<K, V>> newMapAdapter(final TypeAdapter<K> keyAdapter, final TypeAdapter<V> valueAdapter) {
return new TypeAdapter<Map<K, V>>() {
public void write(JsonWriter out, Map<K, V> value) throws IOException {
out.beginObject();
for (Map.Entry<K, V> element : value.entrySet()) {
out.name(String.valueOf(element.getKey()));
valueAdapter.write(out, element.getValue());
}
out.endObject();
}
public Map<K, V> read(JsonReader in) throws IOException {
Map<K, V> result = Maps.newLinkedHashMap();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
V value = valueAdapter.read(in);
if (value != null) result.put(name, value);
}
in.endObject();
return result;
}
}.nullSafe();
}
}

View File

@ -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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if ((typeToken.getRawType() != Multimap.class) || !(type instanceof ParameterizedType)) {
return null;
}
Type keyType = ((ParameterizedType) type).getActualTypeArguments()[0];
Type valueType = ((ParameterizedType) type).getActualTypeArguments()[1];
TypeAdapter<?> keyAdapter = gson.getAdapter(TypeToken.get(keyType));
TypeAdapter<?> valueAdapter = gson.getAdapter(TypeToken.get(valueType));
return (TypeAdapter<T>) newMapAdapter(keyAdapter, valueAdapter);
}
private <K,V> TypeAdapter<Multimap<K, V>> newMapAdapter(final TypeAdapter<K> keyAdapter, final TypeAdapter<V> valueAdapter) {
return new TypeAdapter<Multimap<K, V>>() {
public void write(JsonWriter out, Multimap<K, V> map) throws IOException {
out.beginObject();
for (K key : map.keySet()) {
out.name(String.valueOf(key));
out.beginArray();
for (V value : map.get(key)) {
valueAdapter.write(out, value);
}
out.endArray();
}
out.endObject();
}
public Multimap<K, V> read(JsonReader in) throws IOException {
ImmutableMultimap.Builder<K, V> result = ImmutableListMultimap.builder();
in.beginObject();
while (in.hasNext()) {
JsonReaderInternalAccess.INSTANCE.promoteNameToValue(in);
K name = keyAdapter.read(in);
in.beginArray();
while (in.hasNext()) {
V value = valueAdapter.read(in);
if (value != null) result.put(name, value);
}
in.endArray();
}
in.endObject();
return result.build();
}
}.nullSafe();
}
}

View File

@ -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
* <p/>
* Treats [null] as the empty set; [A, null] as [A]; etc.
*
* @author Adam Lowe
*/
public class IgnoreNullSetTypeAdapterFactory implements TypeAdapterFactory {
@SuppressWarnings("unchecked")
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
Type type = typeToken.getType();
if (typeToken.getRawType() != Set.class || !(type instanceof ParameterizedType)) {
return null;
}
Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
TypeAdapter<?> elementAdapter = gson.getAdapter(TypeToken.get(elementType));
return (TypeAdapter<T>) newSetAdapter(elementAdapter);
}
private <E> TypeAdapter<Set<E>> newSetAdapter(final TypeAdapter<E> elementAdapter) {
return new TypeAdapter<Set<E>>() {
public void write(JsonWriter out, Set<E> value) throws IOException {
out.beginArray();
for (E element : value) {
elementAdapter.write(out, element);
}
out.endArray();
}
public Set<E> read(JsonReader in) throws IOException {
Set<E> result = Sets.newLinkedHashSet();
in.beginArray();
while (in.hasNext()) {
E element = elementAdapter.read(in);
if (element != null) result.add(element);
}
in.endArray();
return result;
}
}.nullSafe();
}
}

View File

@ -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<Job> response = parser.apply(new HttpResponse(200, "ok", Payloads.newInputStreamPayload(is)));
Map<String, String> 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<String, String> 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));
}