From 454f58a7b144d2f16e3ea55b34074b8a66a9a2fe Mon Sep 17 00:00:00 2001 From: Joel Leitch Date: Tue, 29 Mar 2011 16:57:28 +0000 Subject: [PATCH] Adding in instance creator to instantiate the concrete Collection or Map class if known, otherwise fallback to a default instance. Also, added some caching as part of the default constructor lookups. --- .../gson/DefaultConstructorAllocator.java | 89 +++++++++++++++++++ .../com/google/gson/DefaultTypeAdapters.java | 86 ++++++++++-------- .../java/com/google/gson/MapTypeAdapter.java | 10 +-- .../google/gson/MappedObjectConstructor.java | 53 ++--------- .../gson/DefaultConstructorAllocatorTest.java | 66 ++++++++++++++ 5 files changed, 213 insertions(+), 91 deletions(-) create mode 100644 gson/src/main/java/com/google/gson/DefaultConstructorAllocator.java create mode 100644 gson/src/test/java/com/google/gson/DefaultConstructorAllocatorTest.java diff --git a/gson/src/main/java/com/google/gson/DefaultConstructorAllocator.java b/gson/src/main/java/com/google/gson/DefaultConstructorAllocator.java new file mode 100644 index 00000000..2774c389 --- /dev/null +++ b/gson/src/main/java/com/google/gson/DefaultConstructorAllocator.java @@ -0,0 +1,89 @@ +/* + * 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; + +import com.google.gson.internal.Cache; +import com.google.gson.internal.LruCache; + +import java.lang.reflect.Constructor; + +/** + * Use the default constructor on the class to instantiate an object. + * + * @author Joel Leitch + */ +class DefaultConstructorAllocator { + private static final Constructor NULL_CONSTRUCTOR = createNullConstructor(); + + // Package private for testing purposes. + final Cache, Constructor> constructorCache; + + public DefaultConstructorAllocator() { + this(200); + } + + public DefaultConstructorAllocator(int cacheSize) { + constructorCache = new LruCache, Constructor>(cacheSize); + } + + private static final Constructor createNullConstructor() { + try { + return getNoArgsConstructor(Null.class); + } catch (Exception e) { + return null; + } + } + + public T newInstance(Class c) throws Exception { + Constructor constructor = findConstructor(c); + return (constructor != null) ? constructor.newInstance() : null; + } + + @SuppressWarnings("unchecked") + private Constructor findConstructor(Class c) { + Constructor cachedElement = (Constructor) constructorCache.getElement(c); + if (cachedElement != null) { + if (cachedElement == NULL_CONSTRUCTOR) { + return null; + } else { + return cachedElement; + } + } + + Constructor noArgsConstructor = getNoArgsConstructor(c); + if (noArgsConstructor != null) { + constructorCache.addElement(c, noArgsConstructor); + } else { + constructorCache.addElement(c, NULL_CONSTRUCTOR); + } + return noArgsConstructor; + } + + private static Constructor getNoArgsConstructor(Class c) { + try { + Constructor declaredConstructor = c.getDeclaredConstructor(); + declaredConstructor.setAccessible(true); + return declaredConstructor; + } catch (Exception e) { + return null; + } + } + + // placeholder class for Null constructor + private static final class Null { + } +} diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index 703599ce..02649363 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -33,15 +33,18 @@ import java.sql.Timestamp; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedList; +import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Properties; +import java.util.Queue; import java.util.Set; import java.util.SortedSet; import java.util.StringTokenizer; @@ -74,7 +77,7 @@ final class DefaultTypeAdapters { private static final LocaleTypeAdapter LOCALE_TYPE_ADAPTER = new LocaleTypeAdapter(); private static final DefaultInetAddressAdapter INET_ADDRESS_ADAPTER = new DefaultInetAddressAdapter(); - private static final CollectionTypeAdapter COLLECTION_TYPE_ADAPTER = new CollectionTypeAdapter(); + private static final CollectionTypeAdapter COLLECTION_TYPE_ADAPTER = new CollectionTypeAdapter(); private static final MapTypeAdapter MAP_TYPE_ADAPTER = new MapTypeAdapter(); private static final BigDecimalTypeAdapter BIG_DECIMAL_TYPE_ADAPTER = new BigDecimalTypeAdapter(); private static final BigIntegerTypeAdapter BIG_INTEGER_TYPE_ADAPTER = new BigIntegerTypeAdapter(); @@ -90,13 +93,10 @@ final class DefaultTypeAdapters { private static final ShortTypeAdapter SHORT_TYPE_ADAPTER = new ShortTypeAdapter(); private static final StringTypeAdapter STRING_TYPE_ADAPTER = new StringTypeAdapter(); private static final StringBuilderTypeAdapter STRING_BUILDER_TYPE_ADAPTER = - new StringBuilderTypeAdapter(); + new StringBuilderTypeAdapter(); private static final StringBufferTypeAdapter STRING_BUFFER_TYPE_ADAPTER = - new StringBufferTypeAdapter(); + new StringBufferTypeAdapter(); - private static final PropertiesCreator PROPERTIES_CREATOR = new PropertiesCreator(); - private static final TreeSetCreator TREE_SET_CREATOR = new TreeSetCreator(); - private static final HashSetCreator HASH_SET_CREATOR = new HashSetCreator(); private static final GregorianCalendarTypeAdapter GREGORIAN_CALENDAR_TYPE_ADAPTER = new GregorianCalendarTypeAdapter(); @@ -215,17 +215,30 @@ final class DefaultTypeAdapters { return map; } + @SuppressWarnings("unchecked") private static ParameterizedTypeHandlerMap> createDefaultInstanceCreators() { ParameterizedTypeHandlerMap> map = new ParameterizedTypeHandlerMap>(); - map.registerForTypeHierarchy(Map.class, MAP_TYPE_ADAPTER); + DefaultConstructorAllocator allocator = new DefaultConstructorAllocator(50); + + // Map Instance Creators + map.registerForTypeHierarchy(Map.class, + new DefaultConstructorCreator(LinkedHashMap.class, allocator)); // Add Collection type instance creators - map.registerForTypeHierarchy(Collection.class, COLLECTION_TYPE_ADAPTER); + DefaultConstructorCreator listCreator = + new DefaultConstructorCreator(ArrayList.class, allocator); + DefaultConstructorCreator queueCreator = + new DefaultConstructorCreator(LinkedList.class, allocator); + DefaultConstructorCreator setCreator = + new DefaultConstructorCreator(HashSet.class, allocator); + DefaultConstructorCreator sortedSetCreator = + new DefaultConstructorCreator(TreeSet.class, allocator); + map.registerForTypeHierarchy(Collection.class, listCreator); + map.registerForTypeHierarchy(Queue.class, queueCreator); + map.registerForTypeHierarchy(Set.class, setCreator); + map.registerForTypeHierarchy(SortedSet.class, sortedSetCreator); - map.registerForTypeHierarchy(Set.class, HASH_SET_CREATOR); - map.registerForTypeHierarchy(SortedSet.class, TREE_SET_CREATOR); - map.register(Properties.class, PROPERTIES_CREATOR); map.makeUnmodifiable(); return map; } @@ -594,7 +607,7 @@ final class DefaultTypeAdapters { @SuppressWarnings("unchecked") private static final class CollectionTypeAdapter implements JsonSerializer, - JsonDeserializer, InstanceCreator { + JsonDeserializer { public JsonElement serialize(Collection src, Type typeOfSrc, JsonSerializationContext context) { if (src == null) { return JsonNull.createJsonNull(); @@ -644,16 +657,6 @@ final class DefaultTypeAdapters { ObjectConstructor objectConstructor = contextImpl.getObjectConstructor(); return (Collection) objectConstructor.construct(collectionType); } - - public Collection createInstance(Type type) { - return new LinkedList(); - } - } - - private static class PropertiesCreator implements InstanceCreator { - public Properties createInstance(Type type) { - return new Properties(); - } } private static final class BigDecimalTypeAdapter @@ -1003,7 +1006,7 @@ final class DefaultTypeAdapters { throw new JsonSyntaxException(e); } catch (IllegalStateException e) { throw new JsonSyntaxException(e); - } + } } @Override @@ -1012,23 +1015,32 @@ final class DefaultTypeAdapters { } } - private static final class TreeSetCreator implements InstanceCreator> { - public TreeSet createInstance(Type type) { - return new TreeSet(); - } - @Override - public String toString() { - return TreeSetCreator.class.getSimpleName(); - } - } + @SuppressWarnings("unchecked") + private static final class DefaultConstructorCreator implements InstanceCreator { + private final Class defaultInstance; + private final DefaultConstructorAllocator allocator; - private static final class HashSetCreator implements InstanceCreator> { - public HashSet createInstance(Type type) { - return new HashSet(); + public DefaultConstructorCreator(Class defaultInstance, + DefaultConstructorAllocator allocator) { + this.defaultInstance = defaultInstance; + this.allocator = allocator; } + + public T createInstance(Type type) { + Class rawType = Types.getRawType(type); + try { + T specificInstance = (T) allocator.newInstance(rawType); + return (specificInstance == null) + ? allocator.newInstance(defaultInstance) + : specificInstance; + } catch (Exception e) { + throw new JsonIOException(e); + } + } + @Override public String toString() { - return HashSetCreator.class.getSimpleName(); + return DefaultConstructorCreator.class.getSimpleName(); } } } diff --git a/gson/src/main/java/com/google/gson/MapTypeAdapter.java b/gson/src/main/java/com/google/gson/MapTypeAdapter.java index a51d0ad7..a497a5e2 100644 --- a/gson/src/main/java/com/google/gson/MapTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/MapTypeAdapter.java @@ -20,7 +20,6 @@ import com.google.gson.internal.Types; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; @@ -31,10 +30,9 @@ import java.util.Set; * * @author Joel Leitch */ -@SuppressWarnings("unchecked") +@SuppressWarnings("unchecked") final class MapTypeAdapter implements JsonSerializer>, - JsonDeserializer>, InstanceCreator> { - + JsonDeserializer> { public JsonElement serialize(Map src, Type typeOfSrc, JsonSerializationContext context) { JsonObject map = new JsonObject(); Type childGenericType = null; @@ -79,10 +77,6 @@ final class MapTypeAdapter implements JsonSerializer>, return (Map) objectConstructor.construct(mapType); } - public Map createInstance(Type type) { - return new LinkedHashMap(); - } - @Override public String toString() { return MapTypeAdapter.class.getSimpleName(); diff --git a/gson/src/main/java/com/google/gson/MappedObjectConstructor.java b/gson/src/main/java/com/google/gson/MappedObjectConstructor.java index a752ef05..26eedd51 100644 --- a/gson/src/main/java/com/google/gson/MappedObjectConstructor.java +++ b/gson/src/main/java/com/google/gson/MappedObjectConstructor.java @@ -16,12 +16,10 @@ package com.google.gson; -import com.google.gson.internal.LruCache; import com.google.gson.internal.Types; import com.google.gson.internal.UnsafeAllocator; import java.lang.reflect.Array; -import java.lang.reflect.Constructor; import java.lang.reflect.Type; /** @@ -36,23 +34,10 @@ import java.lang.reflect.Type; */ final class MappedObjectConstructor implements ObjectConstructor { private static final UnsafeAllocator unsafeAllocator = UnsafeAllocator.create(); + private static final DefaultConstructorAllocator defaultConstructorAllocator = + new DefaultConstructorAllocator(500); - private static final LruCache, Constructor> noArgsConstructorsCache = - new LruCache, Constructor>(500); private final ParameterizedTypeHandlerMap> instanceCreatorMap; - /** - * We need a special null value to indicate that the class does not have a no-args constructor. - * This helps avoid using reflection over and over again for such classes. For convenience, we - * use the no-args constructor of this class itself since this class would never be - * deserialized using Gson. - */ - private static final Constructor NULL_VALUE = - getNoArgsConstructorUsingReflection(MappedObjectConstructor.class); - - @SuppressWarnings("unused") - private MappedObjectConstructor() { - this(null); - } public MappedObjectConstructor( ParameterizedTypeHandlerMap> instanceCreators) { @@ -65,7 +50,7 @@ final class MappedObjectConstructor implements ObjectConstructor { if (creator != null) { return creator.createInstance(typeOfT); } - return (T) constructWithNoArgConstructor(typeOfT); + return (T) constructWithAllocators(typeOfT); } public Object constructArray(Type type, int length) { @@ -73,43 +58,19 @@ final class MappedObjectConstructor implements ObjectConstructor { } @SuppressWarnings({"unchecked", "cast"}) - private T constructWithNoArgConstructor(Type typeOfT) { + private T constructWithAllocators(Type typeOfT) { try { Class clazz = (Class) Types.getRawType(typeOfT); - Constructor constructor = getNoArgsConstructor(clazz); - return constructor == null + T obj = defaultConstructorAllocator.newInstance(clazz); + return (obj == null) ? unsafeAllocator.newInstance(clazz) - : constructor.newInstance(); + : obj; } catch (Exception e) { throw new RuntimeException(("Unable to invoke no-args constructor for " + typeOfT + ". " + "Register an InstanceCreator with Gson for this type may fix this problem."), e); } } - private Constructor getNoArgsConstructor(Class clazz) { - @SuppressWarnings("unchecked") - Constructor constructor = (Constructor)noArgsConstructorsCache.getElement(clazz); - if (constructor == NULL_VALUE) { - return null; - } - if (constructor == null) { - constructor = getNoArgsConstructorUsingReflection(clazz); - noArgsConstructorsCache.addElement(clazz, constructor); - } - return constructor == NULL_VALUE ? null : constructor; - } - - @SuppressWarnings("unchecked") - private static Constructor getNoArgsConstructorUsingReflection(Class clazz) { - try { - Constructor constructor = clazz.getDeclaredConstructor(); - constructor.setAccessible(true); - return constructor; - } catch (Exception e) { - return (Constructor) NULL_VALUE; - } - } - @Override public String toString() { return instanceCreatorMap.toString(); diff --git a/gson/src/test/java/com/google/gson/DefaultConstructorAllocatorTest.java b/gson/src/test/java/com/google/gson/DefaultConstructorAllocatorTest.java new file mode 100644 index 00000000..0a30ef25 --- /dev/null +++ b/gson/src/test/java/com/google/gson/DefaultConstructorAllocatorTest.java @@ -0,0 +1,66 @@ +/* + * 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; + +import com.google.gson.DefaultConstructorAllocator; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.LinkedList; + +/** + * Unit tests for the default constructor allocator class. + * + * @author Joel Leitch + */ +public class DefaultConstructorAllocatorTest extends TestCase { + private DefaultConstructorAllocator allocator; + + @Override + protected void setUp() throws Exception { + super.setUp(); + allocator = new DefaultConstructorAllocator(); + } + + @SuppressWarnings("unchecked") + public void testObjectConstructor() throws Exception { + ArrayList arrayList = allocator.newInstance(ArrayList.class); + assertTrue(arrayList.isEmpty()); + assertInCache(ArrayList.class); + + LinkedList linkedList = allocator.newInstance(LinkedList.class); + assertTrue(linkedList.isEmpty()); + assertInCache(LinkedList.class); + } + + public void testMissingDefaultConstructor() throws Exception { + assertNull(allocator.newInstance(NoDefaultConstructor.class)); + assertInCache(NoDefaultConstructor.class); + } + + private void assertInCache(Class clazz) { + assertNotNull(allocator.constructorCache.getElement(clazz)); + } + + private static class NoDefaultConstructor { + @SuppressWarnings("unused") + public NoDefaultConstructor(int i) { + // do nothing + } + } +}