From 6e3bf0730037fb3cb7aab7fe607fbe61ba5fe5e6 Mon Sep 17 00:00:00 2001 From: Inderjeet Singh Date: Thu, 4 Aug 2011 23:02:06 +0000 Subject: [PATCH] Added support for runtime type determination while serializing array elements. Created a utility class Reflection to hold methods to find Runtime type and creating new Instances. --- .../gson/internal/bind/ArrayTypeAdapter.java | 11 +++- .../internal/bind/CollectionTypeAdapter.java | 2 +- .../google/gson/internal/bind/MiniGson.java | 26 ++------- .../google/gson/internal/bind/Reflection.java | 56 +++++++++++++++++++ .../internal/bind/ReflectiveTypeAdapter.java | 14 +---- .../bind/StringToValueMapTypeAdapter.java | 2 +- 6 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/internal/bind/Reflection.java diff --git a/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java index da7cc5dd..c0a710a3 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ArrayTypeAdapter.java @@ -44,15 +44,17 @@ public final class ArrayTypeAdapter extends TypeAdapter { TypeAdapter componentTypeAdapter = context.getAdapter(TypeToken.get(componentType)); @SuppressWarnings("unchecked") // create() doesn't define a type parameter TypeAdapter result = new ArrayTypeAdapter( - componentTypeAdapter, $Gson$Types.getRawType(componentType)); + context, componentTypeAdapter, $Gson$Types.getRawType(componentType)); return result; } }; + private final MiniGson context; private final Class componentType; private final TypeAdapter componentTypeAdapter; - public ArrayTypeAdapter(TypeAdapter componentTypeAdapter, Class componentType) { + public ArrayTypeAdapter(MiniGson context, TypeAdapter componentTypeAdapter, Class componentType) { + this.context = context; this.componentTypeAdapter = componentTypeAdapter; this.componentType = componentType; } @@ -86,7 +88,10 @@ public final class ArrayTypeAdapter extends TypeAdapter { writer.beginArray(); for (int i = 0, length = Array.getLength(array); i < length; i++) { final E value = (E) Array.get(array, i); - componentTypeAdapter.write(writer, value); + Type runtimeType = Reflection.getRuntimeTypeIfMoreSpecific(componentType, array, value); + TypeAdapter t = runtimeType != componentType ? + context.getAdapter(TypeToken.get(runtimeType)) : componentTypeAdapter; + t.write(writer, value); } writer.endArray(); } diff --git a/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapter.java index fa63a3be..9369fbfa 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/CollectionTypeAdapter.java @@ -89,7 +89,7 @@ public final class CollectionTypeAdapter extends TypeAdapter> { return null; } - Collection collection = MiniGson.newInstance(constructor); + Collection collection = Reflection.newInstance(constructor); reader.beginArray(); while (reader.hasNext()) { E instance = elementTypeAdapter.read(reader); diff --git a/gson/src/main/java/com/google/gson/internal/bind/MiniGson.java b/gson/src/main/java/com/google/gson/internal/bind/MiniGson.java index 97cd38b1..03fbef0c 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/MiniGson.java +++ b/gson/src/main/java/com/google/gson/internal/bind/MiniGson.java @@ -16,18 +16,17 @@ package com.google.gson.internal.bind; -import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + /** * A basic binding between JSON and Java objects. */ @@ -67,23 +66,6 @@ public final class MiniGson { this.factories = Collections.unmodifiableList(factories); } - // TODO: this should use Joel's unsafe constructor stuff - static T newInstance(Constructor constructor) { - try { - Object[] args = null; - return constructor.newInstance(args); - } catch (InstantiationException e) { - // TODO: JsonParseException ? - throw new RuntimeException(e); - } catch (InvocationTargetException e) { - // TODO: don't wrap if cause is unchecked! - // TODO: JsonParseException ? - throw new RuntimeException(e.getTargetException()); - } catch (IllegalAccessException e) { - throw new AssertionError(e); - } - } - /** * Returns the type adapter for {@code} type. * diff --git a/gson/src/main/java/com/google/gson/internal/bind/Reflection.java b/gson/src/main/java/com/google/gson/internal/bind/Reflection.java new file mode 100644 index 00000000..5d18cde5 --- /dev/null +++ b/gson/src/main/java/com/google/gson/internal/bind/Reflection.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Google Inc. + * + * Licensed 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 com.google.gson.internal.bind; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; + +final class Reflection { + /** + * Finds a compatible runtime type if it is more specific + * In case of a field of an object, parent is the object instance, and child is the field value. + * In case of an Array, parent is the array instance, and the child is the array element. + */ + public static Type getRuntimeTypeIfMoreSpecific(Type type, Object parent, Object child) { + if (parent == null || child == null) { + return type; + } + if (type == Object.class || type instanceof TypeVariable || type instanceof Class) { + type = (Class) child.getClass(); + } + return type; + } + + // TODO: this should use Joel's unsafe constructor stuff + public static T newInstance(Constructor constructor) { + try { + Object[] args = null; + return constructor.newInstance(args); + } catch (InstantiationException e) { + // TODO: JsonParseException ? + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + // TODO: don't wrap if cause is unchecked! + // TODO: JsonParseException ? + throw new RuntimeException(e.getTargetException()); + } catch (IllegalAccessException e) { + throw new AssertionError(e); + } + } + +} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapter.java index 421cac3c..351ea4d9 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ReflectiveTypeAdapter.java @@ -60,7 +60,7 @@ public final class ReflectiveTypeAdapter extends TypeAdapter { T instance; if (constructor != null) { - instance = (T) MiniGson.newInstance(constructor); + instance = (T) Reflection.newInstance(constructor); } else { try { instance = (T) unsafeAllocator.newInstance(rawType); @@ -122,7 +122,7 @@ public final class ReflectiveTypeAdapter extends TypeAdapter { throws IOException, IllegalAccessException { Object fieldValue = field.get(value); Type declaredTypeOfField = fieldType.getType(); - Type resolvedTypeOfField = getMoreSpecificType(declaredTypeOfField, value, fieldValue); + Type resolvedTypeOfField = Reflection.getRuntimeTypeIfMoreSpecific(declaredTypeOfField, value, fieldValue); TypeAdapter t = resolvedTypeOfField != declaredTypeOfField ? context.getAdapter(TypeToken.get(resolvedTypeOfField)) : this.typeAdapter; t.write(writer, fieldValue); @@ -135,16 +135,6 @@ public final class ReflectiveTypeAdapter extends TypeAdapter { }; } - private static Type getMoreSpecificType(Type type, Object obj, Object fieldValue) { - if (obj == null || fieldValue == null) { - return type; - } - if (type == Object.class || type instanceof TypeVariable || type instanceof Class) { - type = (Class) fieldValue.getClass(); - } - return type; - } - public static class FactoryImpl implements Factory { public boolean serializeField(Class declaringClazz, Field f, Type declaredType) { return true; diff --git a/gson/src/main/java/com/google/gson/internal/bind/StringToValueMapTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/StringToValueMapTypeAdapter.java index 7fc2063f..9fbd0cbd 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/StringToValueMapTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/StringToValueMapTypeAdapter.java @@ -79,7 +79,7 @@ public final class StringToValueMapTypeAdapter extends TypeAdapter map = MiniGson.newInstance(constructor); + Map map = Reflection.newInstance(constructor); reader.beginObject(); while (reader.hasNext()) { String key = reader.nextName();